1 | /*
|
---|
2 | * testlimits.c: C program to run libxml2 regression tests checking various
|
---|
3 | * limits in document size. Will consume a lot of RAM and CPU cycles
|
---|
4 | *
|
---|
5 | * To compile on Unixes:
|
---|
6 | * cc -o testlimits `xml2-config --cflags` testlimits.c `xml2-config --libs` -lpthread
|
---|
7 | *
|
---|
8 | * See Copyright for the status of this software.
|
---|
9 | *
|
---|
10 | * [email protected]
|
---|
11 | */
|
---|
12 |
|
---|
13 | #include <stdio.h>
|
---|
14 | #include <stdlib.h>
|
---|
15 | #include <string.h>
|
---|
16 | #include <sys/stat.h>
|
---|
17 | #include <time.h>
|
---|
18 |
|
---|
19 | #include <libxml/parser.h>
|
---|
20 | #include <libxml/parserInternals.h>
|
---|
21 | #include <libxml/tree.h>
|
---|
22 | #include <libxml/uri.h>
|
---|
23 | #ifdef LIBXML_READER_ENABLED
|
---|
24 | #include <libxml/xmlreader.h>
|
---|
25 | #endif
|
---|
26 |
|
---|
27 | static int verbose = 0;
|
---|
28 | static int tests_quiet = 0;
|
---|
29 |
|
---|
30 | /************************************************************************
|
---|
31 | * *
|
---|
32 | * time handling *
|
---|
33 | * *
|
---|
34 | ************************************************************************/
|
---|
35 |
|
---|
36 | /* maximum time for one parsing before declaring a timeout */
|
---|
37 | #define MAX_TIME 2 /* seconds */
|
---|
38 |
|
---|
39 | static clock_t t0;
|
---|
40 | int timeout = 0;
|
---|
41 |
|
---|
42 | static void reset_timout(void) {
|
---|
43 | timeout = 0;
|
---|
44 | t0 = clock();
|
---|
45 | }
|
---|
46 |
|
---|
47 | static int check_time(void) {
|
---|
48 | clock_t tnow = clock();
|
---|
49 | if (((tnow - t0) / CLOCKS_PER_SEC) > MAX_TIME) {
|
---|
50 | timeout = 1;
|
---|
51 | return(0);
|
---|
52 | }
|
---|
53 | return(1);
|
---|
54 | }
|
---|
55 |
|
---|
56 | /************************************************************************
|
---|
57 | * *
|
---|
58 | * Huge document generator *
|
---|
59 | * *
|
---|
60 | ************************************************************************/
|
---|
61 |
|
---|
62 | #include <libxml/xmlIO.h>
|
---|
63 |
|
---|
64 | /*
|
---|
65 | * Huge documents are built using fixed start and end chunks
|
---|
66 | * and filling between the two an unconventional amount of char data
|
---|
67 | */
|
---|
68 | typedef struct hugeTest hugeTest;
|
---|
69 | typedef hugeTest *hugeTestPtr;
|
---|
70 | struct hugeTest {
|
---|
71 | const char *description;
|
---|
72 | const char *name;
|
---|
73 | const char *start;
|
---|
74 | const char *end;
|
---|
75 | };
|
---|
76 |
|
---|
77 | static struct hugeTest hugeTests[] = {
|
---|
78 | { "Huge text node", "huge:textNode", "<foo>", "</foo>" },
|
---|
79 | { "Huge attribute node", "huge:attrNode", "<foo bar='", "'/>" },
|
---|
80 | { "Huge comment node", "huge:commentNode", "<foo><!--", "--></foo>" },
|
---|
81 | { "Huge PI node", "huge:piNode", "<foo><?bar ", "?></foo>" },
|
---|
82 | };
|
---|
83 |
|
---|
84 | static const char *current;
|
---|
85 | static int rlen;
|
---|
86 | static unsigned int currentTest = 0;
|
---|
87 | static int instate = 0;
|
---|
88 |
|
---|
89 | /**
|
---|
90 | * hugeMatch:
|
---|
91 | * @URI: an URI to test
|
---|
92 | *
|
---|
93 | * Check for an huge: query
|
---|
94 | *
|
---|
95 | * Returns 1 if yes and 0 if another Input module should be used
|
---|
96 | */
|
---|
97 | static int
|
---|
98 | hugeMatch(const char * URI) {
|
---|
99 | if ((URI != NULL) && (!strncmp(URI, "huge:", 5)))
|
---|
100 | return(1);
|
---|
101 | return(0);
|
---|
102 | }
|
---|
103 |
|
---|
104 | /**
|
---|
105 | * hugeOpen:
|
---|
106 | * @URI: an URI to test
|
---|
107 | *
|
---|
108 | * Return a pointer to the huge: query handler, in this example simply
|
---|
109 | * the current pointer...
|
---|
110 | *
|
---|
111 | * Returns an Input context or NULL in case or error
|
---|
112 | */
|
---|
113 | static void *
|
---|
114 | hugeOpen(const char * URI) {
|
---|
115 | if ((URI == NULL) || (strncmp(URI, "huge:", 5)))
|
---|
116 | return(NULL);
|
---|
117 |
|
---|
118 | for (currentTest = 0;currentTest < sizeof(hugeTests)/sizeof(hugeTests[0]);
|
---|
119 | currentTest++)
|
---|
120 | if (!strcmp(hugeTests[currentTest].name, URI))
|
---|
121 | goto found;
|
---|
122 |
|
---|
123 | return(NULL);
|
---|
124 |
|
---|
125 | found:
|
---|
126 | rlen = strlen(hugeTests[currentTest].start);
|
---|
127 | current = hugeTests[currentTest].start;
|
---|
128 | instate = 0;
|
---|
129 | return((void *) current);
|
---|
130 | }
|
---|
131 |
|
---|
132 | /**
|
---|
133 | * hugeClose:
|
---|
134 | * @context: the read context
|
---|
135 | *
|
---|
136 | * Close the huge: query handler
|
---|
137 | *
|
---|
138 | * Returns 0 or -1 in case of error
|
---|
139 | */
|
---|
140 | static int
|
---|
141 | hugeClose(void * context) {
|
---|
142 | if (context == NULL) return(-1);
|
---|
143 | fprintf(stderr, "\n");
|
---|
144 | return(0);
|
---|
145 | }
|
---|
146 |
|
---|
147 | #define CHUNK 4096
|
---|
148 |
|
---|
149 | char filling[CHUNK + 1];
|
---|
150 |
|
---|
151 | static void fillFilling(void) {
|
---|
152 | int i;
|
---|
153 |
|
---|
154 | for (i = 0;i < CHUNK;i++) {
|
---|
155 | filling[i] = 'a';
|
---|
156 | }
|
---|
157 | filling[CHUNK] = 0;
|
---|
158 | }
|
---|
159 |
|
---|
160 | size_t maxlen = 64 * 1024 * 1024;
|
---|
161 | size_t curlen = 0;
|
---|
162 | size_t dotlen;
|
---|
163 |
|
---|
164 | /**
|
---|
165 | * hugeRead:
|
---|
166 | * @context: the read context
|
---|
167 | * @buffer: where to store data
|
---|
168 | * @len: number of bytes to read
|
---|
169 | *
|
---|
170 | * Implement an huge: query read.
|
---|
171 | *
|
---|
172 | * Returns the number of bytes read or -1 in case of error
|
---|
173 | */
|
---|
174 | static int
|
---|
175 | hugeRead(void *context, char *buffer, int len)
|
---|
176 | {
|
---|
177 | if ((context == NULL) || (buffer == NULL) || (len < 0))
|
---|
178 | return (-1);
|
---|
179 |
|
---|
180 | if (instate == 0) {
|
---|
181 | if (len >= rlen) {
|
---|
182 | len = rlen;
|
---|
183 | rlen = 0;
|
---|
184 | memcpy(buffer, current, len);
|
---|
185 | instate = 1;
|
---|
186 | curlen = 0;
|
---|
187 | dotlen = maxlen / 10;
|
---|
188 | } else {
|
---|
189 | memcpy(buffer, current, len);
|
---|
190 | rlen -= len;
|
---|
191 | current += len;
|
---|
192 | }
|
---|
193 | } else if (instate == 2) {
|
---|
194 | if (len >= rlen) {
|
---|
195 | len = rlen;
|
---|
196 | rlen = 0;
|
---|
197 | memcpy(buffer, current, len);
|
---|
198 | instate = 3;
|
---|
199 | curlen = 0;
|
---|
200 | } else {
|
---|
201 | memcpy(buffer, current, len);
|
---|
202 | rlen -= len;
|
---|
203 | current += len;
|
---|
204 | }
|
---|
205 | } else if (instate == 1) {
|
---|
206 | if (len > CHUNK) len = CHUNK;
|
---|
207 | memcpy(buffer, &filling[0], len);
|
---|
208 | curlen += len;
|
---|
209 | if (curlen >= maxlen) {
|
---|
210 | rlen = strlen(hugeTests[currentTest].end);
|
---|
211 | current = hugeTests[currentTest].end;
|
---|
212 | instate = 2;
|
---|
213 | } else {
|
---|
214 | if (curlen > dotlen) {
|
---|
215 | fprintf(stderr, ".");
|
---|
216 | dotlen += maxlen / 10;
|
---|
217 | }
|
---|
218 | }
|
---|
219 | } else
|
---|
220 | len = 0;
|
---|
221 | return (len);
|
---|
222 | }
|
---|
223 |
|
---|
224 | /************************************************************************
|
---|
225 | * *
|
---|
226 | * Crazy document generator *
|
---|
227 | * *
|
---|
228 | ************************************************************************/
|
---|
229 |
|
---|
230 | unsigned int crazy_indx = 0;
|
---|
231 |
|
---|
232 | const char *crazy = "<?xml version='1.0' encoding='UTF-8'?>\
|
---|
233 | <?tst ?>\
|
---|
234 | <!-- tst -->\
|
---|
235 | <!DOCTYPE foo [\
|
---|
236 | <?tst ?>\
|
---|
237 | <!-- tst -->\
|
---|
238 | <!ELEMENT foo (#PCDATA)>\
|
---|
239 | <!ELEMENT p (#PCDATA|emph)* >\
|
---|
240 | ]>\
|
---|
241 | <?tst ?>\
|
---|
242 | <!-- tst -->\
|
---|
243 | <foo bar='foo'>\
|
---|
244 | <?tst ?>\
|
---|
245 | <!-- tst -->\
|
---|
246 | foo\
|
---|
247 | <![CDATA[ ]]>\
|
---|
248 | </foo>\
|
---|
249 | <?tst ?>\
|
---|
250 | <!-- tst -->";
|
---|
251 |
|
---|
252 | /**
|
---|
253 | * crazyMatch:
|
---|
254 | * @URI: an URI to test
|
---|
255 | *
|
---|
256 | * Check for a crazy: query
|
---|
257 | *
|
---|
258 | * Returns 1 if yes and 0 if another Input module should be used
|
---|
259 | */
|
---|
260 | static int
|
---|
261 | crazyMatch(const char * URI) {
|
---|
262 | if ((URI != NULL) && (!strncmp(URI, "crazy:", 6)))
|
---|
263 | return(1);
|
---|
264 | return(0);
|
---|
265 | }
|
---|
266 |
|
---|
267 | /**
|
---|
268 | * crazyOpen:
|
---|
269 | * @URI: an URI to test
|
---|
270 | *
|
---|
271 | * Return a pointer to the crazy: query handler, in this example simply
|
---|
272 | * the current pointer...
|
---|
273 | *
|
---|
274 | * Returns an Input context or NULL in case or error
|
---|
275 | */
|
---|
276 | static void *
|
---|
277 | crazyOpen(const char * URI) {
|
---|
278 | if ((URI == NULL) || (strncmp(URI, "crazy:", 6)))
|
---|
279 | return(NULL);
|
---|
280 |
|
---|
281 | if (crazy_indx > strlen(crazy))
|
---|
282 | return(NULL);
|
---|
283 | reset_timout();
|
---|
284 | rlen = crazy_indx;
|
---|
285 | current = &crazy[0];
|
---|
286 | instate = 0;
|
---|
287 | return((void *) current);
|
---|
288 | }
|
---|
289 |
|
---|
290 | /**
|
---|
291 | * crazyClose:
|
---|
292 | * @context: the read context
|
---|
293 | *
|
---|
294 | * Close the crazy: query handler
|
---|
295 | *
|
---|
296 | * Returns 0 or -1 in case of error
|
---|
297 | */
|
---|
298 | static int
|
---|
299 | crazyClose(void * context) {
|
---|
300 | if (context == NULL) return(-1);
|
---|
301 | return(0);
|
---|
302 | }
|
---|
303 |
|
---|
304 |
|
---|
305 | /**
|
---|
306 | * crazyRead:
|
---|
307 | * @context: the read context
|
---|
308 | * @buffer: where to store data
|
---|
309 | * @len: number of bytes to read
|
---|
310 | *
|
---|
311 | * Implement an crazy: query read.
|
---|
312 | *
|
---|
313 | * Returns the number of bytes read or -1 in case of error
|
---|
314 | */
|
---|
315 | static int
|
---|
316 | crazyRead(void *context, char *buffer, int len)
|
---|
317 | {
|
---|
318 | if ((context == NULL) || (buffer == NULL) || (len < 0))
|
---|
319 | return (-1);
|
---|
320 |
|
---|
321 | if ((check_time() <= 0) && (instate == 1)) {
|
---|
322 | fprintf(stderr, "\ntimeout in crazy(%d)\n", crazy_indx);
|
---|
323 | rlen = strlen(crazy) - crazy_indx;
|
---|
324 | current = &crazy[crazy_indx];
|
---|
325 | instate = 2;
|
---|
326 | }
|
---|
327 | if (instate == 0) {
|
---|
328 | if (len >= rlen) {
|
---|
329 | len = rlen;
|
---|
330 | rlen = 0;
|
---|
331 | memcpy(buffer, current, len);
|
---|
332 | instate = 1;
|
---|
333 | curlen = 0;
|
---|
334 | } else {
|
---|
335 | memcpy(buffer, current, len);
|
---|
336 | rlen -= len;
|
---|
337 | current += len;
|
---|
338 | }
|
---|
339 | } else if (instate == 2) {
|
---|
340 | if (len >= rlen) {
|
---|
341 | len = rlen;
|
---|
342 | rlen = 0;
|
---|
343 | memcpy(buffer, current, len);
|
---|
344 | instate = 3;
|
---|
345 | curlen = 0;
|
---|
346 | } else {
|
---|
347 | memcpy(buffer, current, len);
|
---|
348 | rlen -= len;
|
---|
349 | current += len;
|
---|
350 | }
|
---|
351 | } else if (instate == 1) {
|
---|
352 | if (len > CHUNK) len = CHUNK;
|
---|
353 | memcpy(buffer, &filling[0], len);
|
---|
354 | curlen += len;
|
---|
355 | if (curlen >= maxlen) {
|
---|
356 | rlen = strlen(crazy) - crazy_indx;
|
---|
357 | current = &crazy[crazy_indx];
|
---|
358 | instate = 2;
|
---|
359 | }
|
---|
360 | } else
|
---|
361 | len = 0;
|
---|
362 | return (len);
|
---|
363 | }
|
---|
364 | /************************************************************************
|
---|
365 | * *
|
---|
366 | * Libxml2 specific routines *
|
---|
367 | * *
|
---|
368 | ************************************************************************/
|
---|
369 |
|
---|
370 | static int nb_tests = 0;
|
---|
371 | static int nb_errors = 0;
|
---|
372 | static int nb_leaks = 0;
|
---|
373 | static int extraMemoryFromResolver = 0;
|
---|
374 |
|
---|
375 | /*
|
---|
376 | * We need to trap calls to the resolver to not account memory for the catalog
|
---|
377 | * which is shared to the current running test. We also don't want to have
|
---|
378 | * network downloads modifying tests.
|
---|
379 | */
|
---|
380 | static xmlParserInputPtr
|
---|
381 | testExternalEntityLoader(const char *URL, const char *ID,
|
---|
382 | xmlParserCtxtPtr ctxt) {
|
---|
383 | xmlParserInputPtr ret;
|
---|
384 | int memused = xmlMemUsed();
|
---|
385 |
|
---|
386 | ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt);
|
---|
387 | extraMemoryFromResolver += xmlMemUsed() - memused;
|
---|
388 |
|
---|
389 | return(ret);
|
---|
390 | }
|
---|
391 |
|
---|
392 | /*
|
---|
393 | * Trapping the error messages at the generic level to grab the equivalent of
|
---|
394 | * stderr messages on CLI tools.
|
---|
395 | */
|
---|
396 | static char testErrors[32769];
|
---|
397 | static int testErrorsSize = 0;
|
---|
398 |
|
---|
399 | static void
|
---|
400 | channel(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) {
|
---|
401 | va_list args;
|
---|
402 | int res;
|
---|
403 |
|
---|
404 | if (testErrorsSize >= 32768)
|
---|
405 | return;
|
---|
406 | va_start(args, msg);
|
---|
407 | res = vsnprintf(&testErrors[testErrorsSize],
|
---|
408 | 32768 - testErrorsSize,
|
---|
409 | msg, args);
|
---|
410 | va_end(args);
|
---|
411 | if (testErrorsSize + res >= 32768) {
|
---|
412 | /* buffer is full */
|
---|
413 | testErrorsSize = 32768;
|
---|
414 | testErrors[testErrorsSize] = 0;
|
---|
415 | } else {
|
---|
416 | testErrorsSize += res;
|
---|
417 | }
|
---|
418 | testErrors[testErrorsSize] = 0;
|
---|
419 | }
|
---|
420 |
|
---|
421 | /**
|
---|
422 | * xmlParserPrintFileContext:
|
---|
423 | * @input: an xmlParserInputPtr input
|
---|
424 | *
|
---|
425 | * Displays current context within the input content for error tracking
|
---|
426 | */
|
---|
427 |
|
---|
428 | static void
|
---|
429 | xmlParserPrintFileContextInternal(xmlParserInputPtr input ,
|
---|
430 | xmlGenericErrorFunc chanl, void *data ) {
|
---|
431 | const xmlChar *cur, *base;
|
---|
432 | unsigned int n, col; /* GCC warns if signed, because compared with sizeof() */
|
---|
433 | xmlChar content[81]; /* space for 80 chars + line terminator */
|
---|
434 | xmlChar *ctnt;
|
---|
435 |
|
---|
436 | if (input == NULL) return;
|
---|
437 | cur = input->cur;
|
---|
438 | base = input->base;
|
---|
439 | /* skip backwards over any end-of-lines */
|
---|
440 | while ((cur > base) && ((*(cur) == '\n') || (*(cur) == '\r'))) {
|
---|
441 | cur--;
|
---|
442 | }
|
---|
443 | n = 0;
|
---|
444 | /* search backwards for beginning-of-line (to max buff size) */
|
---|
445 | while ((n++ < (sizeof(content)-1)) && (cur > base) &&
|
---|
446 | (*(cur) != '\n') && (*(cur) != '\r'))
|
---|
447 | cur--;
|
---|
448 | if ((*(cur) == '\n') || (*(cur) == '\r')) cur++;
|
---|
449 | /* calculate the error position in terms of the current position */
|
---|
450 | col = input->cur - cur;
|
---|
451 | /* search forward for end-of-line (to max buff size) */
|
---|
452 | n = 0;
|
---|
453 | ctnt = content;
|
---|
454 | /* copy selected text to our buffer */
|
---|
455 | while ((*cur != 0) && (*(cur) != '\n') &&
|
---|
456 | (*(cur) != '\r') && (n < sizeof(content)-1)) {
|
---|
457 | *ctnt++ = *cur++;
|
---|
458 | n++;
|
---|
459 | }
|
---|
460 | *ctnt = 0;
|
---|
461 | /* print out the selected text */
|
---|
462 | chanl(data ,"%s\n", content);
|
---|
463 | /* create blank line with problem pointer */
|
---|
464 | n = 0;
|
---|
465 | ctnt = content;
|
---|
466 | /* (leave buffer space for pointer + line terminator) */
|
---|
467 | while ((n<col) && (n++ < sizeof(content)-2) && (*ctnt != 0)) {
|
---|
468 | if (*(ctnt) != '\t')
|
---|
469 | *(ctnt) = ' ';
|
---|
470 | ctnt++;
|
---|
471 | }
|
---|
472 | *ctnt++ = '^';
|
---|
473 | *ctnt = 0;
|
---|
474 | chanl(data ,"%s\n", content);
|
---|
475 | }
|
---|
476 |
|
---|
477 | static void
|
---|
478 | testStructuredErrorHandler(void *ctx ATTRIBUTE_UNUSED, const xmlError *err) {
|
---|
479 | char *file = NULL;
|
---|
480 | int line = 0;
|
---|
481 | int code = -1;
|
---|
482 | int domain;
|
---|
483 | void *data = NULL;
|
---|
484 | const char *str;
|
---|
485 | const xmlChar *name = NULL;
|
---|
486 | xmlNodePtr node;
|
---|
487 | xmlErrorLevel level;
|
---|
488 | xmlParserInputPtr input = NULL;
|
---|
489 | xmlParserInputPtr cur = NULL;
|
---|
490 | xmlParserCtxtPtr ctxt = NULL;
|
---|
491 |
|
---|
492 | if (err == NULL)
|
---|
493 | return;
|
---|
494 |
|
---|
495 | file = err->file;
|
---|
496 | line = err->line;
|
---|
497 | code = err->code;
|
---|
498 | domain = err->domain;
|
---|
499 | level = err->level;
|
---|
500 | node = err->node;
|
---|
501 | if ((domain == XML_FROM_PARSER) || (domain == XML_FROM_HTML) ||
|
---|
502 | (domain == XML_FROM_DTD) || (domain == XML_FROM_NAMESPACE) ||
|
---|
503 | (domain == XML_FROM_IO) || (domain == XML_FROM_VALID)) {
|
---|
504 | ctxt = err->ctxt;
|
---|
505 | }
|
---|
506 | str = err->message;
|
---|
507 |
|
---|
508 | if (code == XML_ERR_OK)
|
---|
509 | return;
|
---|
510 |
|
---|
511 | if ((node != NULL) && (node->type == XML_ELEMENT_NODE))
|
---|
512 | name = node->name;
|
---|
513 |
|
---|
514 | /*
|
---|
515 | * Maintain the compatibility with the legacy error handling
|
---|
516 | */
|
---|
517 | if (ctxt != NULL) {
|
---|
518 | input = ctxt->input;
|
---|
519 | if ((input != NULL) && (input->filename == NULL) &&
|
---|
520 | (ctxt->inputNr > 1)) {
|
---|
521 | cur = input;
|
---|
522 | input = ctxt->inputTab[ctxt->inputNr - 2];
|
---|
523 | }
|
---|
524 | if (input != NULL) {
|
---|
525 | if (input->filename)
|
---|
526 | channel(data, "%s:%d: ", input->filename, input->line);
|
---|
527 | else if ((line != 0) && (domain == XML_FROM_PARSER))
|
---|
528 | channel(data, "Entity: line %d: ", input->line);
|
---|
529 | }
|
---|
530 | } else {
|
---|
531 | if (file != NULL)
|
---|
532 | channel(data, "%s:%d: ", file, line);
|
---|
533 | else if ((line != 0) && (domain == XML_FROM_PARSER))
|
---|
534 | channel(data, "Entity: line %d: ", line);
|
---|
535 | }
|
---|
536 | if (name != NULL) {
|
---|
537 | channel(data, "element %s: ", name);
|
---|
538 | }
|
---|
539 | if (code == XML_ERR_OK)
|
---|
540 | return;
|
---|
541 | switch (domain) {
|
---|
542 | case XML_FROM_PARSER:
|
---|
543 | channel(data, "parser ");
|
---|
544 | break;
|
---|
545 | case XML_FROM_NAMESPACE:
|
---|
546 | channel(data, "namespace ");
|
---|
547 | break;
|
---|
548 | case XML_FROM_DTD:
|
---|
549 | case XML_FROM_VALID:
|
---|
550 | channel(data, "validity ");
|
---|
551 | break;
|
---|
552 | case XML_FROM_HTML:
|
---|
553 | channel(data, "HTML parser ");
|
---|
554 | break;
|
---|
555 | case XML_FROM_MEMORY:
|
---|
556 | channel(data, "memory ");
|
---|
557 | break;
|
---|
558 | case XML_FROM_OUTPUT:
|
---|
559 | channel(data, "output ");
|
---|
560 | break;
|
---|
561 | case XML_FROM_IO:
|
---|
562 | channel(data, "I/O ");
|
---|
563 | break;
|
---|
564 | case XML_FROM_XINCLUDE:
|
---|
565 | channel(data, "XInclude ");
|
---|
566 | break;
|
---|
567 | case XML_FROM_XPATH:
|
---|
568 | channel(data, "XPath ");
|
---|
569 | break;
|
---|
570 | case XML_FROM_XPOINTER:
|
---|
571 | channel(data, "parser ");
|
---|
572 | break;
|
---|
573 | case XML_FROM_REGEXP:
|
---|
574 | channel(data, "regexp ");
|
---|
575 | break;
|
---|
576 | case XML_FROM_MODULE:
|
---|
577 | channel(data, "module ");
|
---|
578 | break;
|
---|
579 | case XML_FROM_SCHEMASV:
|
---|
580 | channel(data, "Schemas validity ");
|
---|
581 | break;
|
---|
582 | case XML_FROM_SCHEMASP:
|
---|
583 | channel(data, "Schemas parser ");
|
---|
584 | break;
|
---|
585 | case XML_FROM_RELAXNGP:
|
---|
586 | channel(data, "Relax-NG parser ");
|
---|
587 | break;
|
---|
588 | case XML_FROM_RELAXNGV:
|
---|
589 | channel(data, "Relax-NG validity ");
|
---|
590 | break;
|
---|
591 | case XML_FROM_CATALOG:
|
---|
592 | channel(data, "Catalog ");
|
---|
593 | break;
|
---|
594 | case XML_FROM_C14N:
|
---|
595 | channel(data, "C14N ");
|
---|
596 | break;
|
---|
597 | case XML_FROM_XSLT:
|
---|
598 | channel(data, "XSLT ");
|
---|
599 | break;
|
---|
600 | default:
|
---|
601 | break;
|
---|
602 | }
|
---|
603 | if (code == XML_ERR_OK)
|
---|
604 | return;
|
---|
605 | switch (level) {
|
---|
606 | case XML_ERR_NONE:
|
---|
607 | channel(data, ": ");
|
---|
608 | break;
|
---|
609 | case XML_ERR_WARNING:
|
---|
610 | channel(data, "warning : ");
|
---|
611 | break;
|
---|
612 | case XML_ERR_ERROR:
|
---|
613 | channel(data, "error : ");
|
---|
614 | break;
|
---|
615 | case XML_ERR_FATAL:
|
---|
616 | channel(data, "error : ");
|
---|
617 | break;
|
---|
618 | }
|
---|
619 | if (code == XML_ERR_OK)
|
---|
620 | return;
|
---|
621 | if (str != NULL) {
|
---|
622 | int len;
|
---|
623 | len = xmlStrlen((const xmlChar *)str);
|
---|
624 | if ((len > 0) && (str[len - 1] != '\n'))
|
---|
625 | channel(data, "%s\n", str);
|
---|
626 | else
|
---|
627 | channel(data, "%s", str);
|
---|
628 | } else {
|
---|
629 | channel(data, "%s\n", "out of memory error");
|
---|
630 | }
|
---|
631 | if (code == XML_ERR_OK)
|
---|
632 | return;
|
---|
633 |
|
---|
634 | if (ctxt != NULL) {
|
---|
635 | xmlParserPrintFileContextInternal(input, channel, data);
|
---|
636 | if (cur != NULL) {
|
---|
637 | if (cur->filename)
|
---|
638 | channel(data, "%s:%d: \n", cur->filename, cur->line);
|
---|
639 | else if ((line != 0) && (domain == XML_FROM_PARSER))
|
---|
640 | channel(data, "Entity: line %d: \n", cur->line);
|
---|
641 | xmlParserPrintFileContextInternal(cur, channel, data);
|
---|
642 | }
|
---|
643 | }
|
---|
644 | if ((domain == XML_FROM_XPATH) && (err->str1 != NULL) &&
|
---|
645 | (err->int1 < 100) &&
|
---|
646 | (err->int1 < xmlStrlen((const xmlChar *)err->str1))) {
|
---|
647 | xmlChar buf[150];
|
---|
648 | int i;
|
---|
649 |
|
---|
650 | channel(data, "%s\n", err->str1);
|
---|
651 | for (i=0;i < err->int1;i++)
|
---|
652 | buf[i] = ' ';
|
---|
653 | buf[i++] = '^';
|
---|
654 | buf[i] = 0;
|
---|
655 | channel(data, "%s\n", buf);
|
---|
656 | }
|
---|
657 | }
|
---|
658 |
|
---|
659 | static void
|
---|
660 | initializeLibxml2(void) {
|
---|
661 | xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
|
---|
662 | xmlInitParser();
|
---|
663 | xmlSetExternalEntityLoader(testExternalEntityLoader);
|
---|
664 | xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler);
|
---|
665 | /*
|
---|
666 | * register the new I/O handlers
|
---|
667 | */
|
---|
668 | if (xmlRegisterInputCallbacks(hugeMatch, hugeOpen,
|
---|
669 | hugeRead, hugeClose) < 0) {
|
---|
670 | fprintf(stderr, "failed to register Huge handlers\n");
|
---|
671 | exit(1);
|
---|
672 | }
|
---|
673 | if (xmlRegisterInputCallbacks(crazyMatch, crazyOpen,
|
---|
674 | crazyRead, crazyClose) < 0) {
|
---|
675 | fprintf(stderr, "failed to register Crazy handlers\n");
|
---|
676 | exit(1);
|
---|
677 | }
|
---|
678 | }
|
---|
679 |
|
---|
680 | /************************************************************************
|
---|
681 | * *
|
---|
682 | * SAX empty callbacks *
|
---|
683 | * *
|
---|
684 | ************************************************************************/
|
---|
685 |
|
---|
686 | unsigned long callbacks = 0;
|
---|
687 |
|
---|
688 | /**
|
---|
689 | * isStandaloneCallback:
|
---|
690 | * @ctxt: An XML parser context
|
---|
691 | *
|
---|
692 | * Is this document tagged standalone ?
|
---|
693 | *
|
---|
694 | * Returns 1 if true
|
---|
695 | */
|
---|
696 | static int
|
---|
697 | isStandaloneCallback(void *ctx ATTRIBUTE_UNUSED)
|
---|
698 | {
|
---|
699 | callbacks++;
|
---|
700 | return (0);
|
---|
701 | }
|
---|
702 |
|
---|
703 | /**
|
---|
704 | * hasInternalSubsetCallback:
|
---|
705 | * @ctxt: An XML parser context
|
---|
706 | *
|
---|
707 | * Does this document has an internal subset
|
---|
708 | *
|
---|
709 | * Returns 1 if true
|
---|
710 | */
|
---|
711 | static int
|
---|
712 | hasInternalSubsetCallback(void *ctx ATTRIBUTE_UNUSED)
|
---|
713 | {
|
---|
714 | callbacks++;
|
---|
715 | return (0);
|
---|
716 | }
|
---|
717 |
|
---|
718 | /**
|
---|
719 | * hasExternalSubsetCallback:
|
---|
720 | * @ctxt: An XML parser context
|
---|
721 | *
|
---|
722 | * Does this document has an external subset
|
---|
723 | *
|
---|
724 | * Returns 1 if true
|
---|
725 | */
|
---|
726 | static int
|
---|
727 | hasExternalSubsetCallback(void *ctx ATTRIBUTE_UNUSED)
|
---|
728 | {
|
---|
729 | callbacks++;
|
---|
730 | return (0);
|
---|
731 | }
|
---|
732 |
|
---|
733 | /**
|
---|
734 | * internalSubsetCallback:
|
---|
735 | * @ctxt: An XML parser context
|
---|
736 | *
|
---|
737 | * Does this document has an internal subset
|
---|
738 | */
|
---|
739 | static void
|
---|
740 | internalSubsetCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
741 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
742 | const xmlChar * ExternalID ATTRIBUTE_UNUSED,
|
---|
743 | const xmlChar * SystemID ATTRIBUTE_UNUSED)
|
---|
744 | {
|
---|
745 | callbacks++;
|
---|
746 | return;
|
---|
747 | }
|
---|
748 |
|
---|
749 | /**
|
---|
750 | * externalSubsetCallback:
|
---|
751 | * @ctxt: An XML parser context
|
---|
752 | *
|
---|
753 | * Does this document has an external subset
|
---|
754 | */
|
---|
755 | static void
|
---|
756 | externalSubsetCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
757 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
758 | const xmlChar * ExternalID ATTRIBUTE_UNUSED,
|
---|
759 | const xmlChar * SystemID ATTRIBUTE_UNUSED)
|
---|
760 | {
|
---|
761 | callbacks++;
|
---|
762 | return;
|
---|
763 | }
|
---|
764 |
|
---|
765 | /**
|
---|
766 | * resolveEntityCallback:
|
---|
767 | * @ctxt: An XML parser context
|
---|
768 | * @publicId: The public ID of the entity
|
---|
769 | * @systemId: The system ID of the entity
|
---|
770 | *
|
---|
771 | * Special entity resolver, better left to the parser, it has
|
---|
772 | * more context than the application layer.
|
---|
773 | * The default behaviour is to NOT resolve the entities, in that case
|
---|
774 | * the ENTITY_REF nodes are built in the structure (and the parameter
|
---|
775 | * values).
|
---|
776 | *
|
---|
777 | * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour.
|
---|
778 | */
|
---|
779 | static xmlParserInputPtr
|
---|
780 | resolveEntityCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
781 | const xmlChar * publicId ATTRIBUTE_UNUSED,
|
---|
782 | const xmlChar * systemId ATTRIBUTE_UNUSED)
|
---|
783 | {
|
---|
784 | callbacks++;
|
---|
785 | return (NULL);
|
---|
786 | }
|
---|
787 |
|
---|
788 | /**
|
---|
789 | * getEntityCallback:
|
---|
790 | * @ctxt: An XML parser context
|
---|
791 | * @name: The entity name
|
---|
792 | *
|
---|
793 | * Get an entity by name
|
---|
794 | *
|
---|
795 | * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour.
|
---|
796 | */
|
---|
797 | static xmlEntityPtr
|
---|
798 | getEntityCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
799 | const xmlChar * name ATTRIBUTE_UNUSED)
|
---|
800 | {
|
---|
801 | callbacks++;
|
---|
802 | return (NULL);
|
---|
803 | }
|
---|
804 |
|
---|
805 | /**
|
---|
806 | * getParameterEntityCallback:
|
---|
807 | * @ctxt: An XML parser context
|
---|
808 | * @name: The entity name
|
---|
809 | *
|
---|
810 | * Get a parameter entity by name
|
---|
811 | *
|
---|
812 | * Returns the xmlParserInputPtr
|
---|
813 | */
|
---|
814 | static xmlEntityPtr
|
---|
815 | getParameterEntityCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
816 | const xmlChar * name ATTRIBUTE_UNUSED)
|
---|
817 | {
|
---|
818 | callbacks++;
|
---|
819 | return (NULL);
|
---|
820 | }
|
---|
821 |
|
---|
822 |
|
---|
823 | /**
|
---|
824 | * entityDeclCallback:
|
---|
825 | * @ctxt: An XML parser context
|
---|
826 | * @name: the entity name
|
---|
827 | * @type: the entity type
|
---|
828 | * @publicId: The public ID of the entity
|
---|
829 | * @systemId: The system ID of the entity
|
---|
830 | * @content: the entity value (without processing).
|
---|
831 | *
|
---|
832 | * An entity definition has been parsed
|
---|
833 | */
|
---|
834 | static void
|
---|
835 | entityDeclCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
836 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
837 | int type ATTRIBUTE_UNUSED,
|
---|
838 | const xmlChar * publicId ATTRIBUTE_UNUSED,
|
---|
839 | const xmlChar * systemId ATTRIBUTE_UNUSED,
|
---|
840 | xmlChar * content ATTRIBUTE_UNUSED)
|
---|
841 | {
|
---|
842 | callbacks++;
|
---|
843 | return;
|
---|
844 | }
|
---|
845 |
|
---|
846 | /**
|
---|
847 | * attributeDeclCallback:
|
---|
848 | * @ctxt: An XML parser context
|
---|
849 | * @name: the attribute name
|
---|
850 | * @type: the attribute type
|
---|
851 | *
|
---|
852 | * An attribute definition has been parsed
|
---|
853 | */
|
---|
854 | static void
|
---|
855 | attributeDeclCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
856 | const xmlChar * elem ATTRIBUTE_UNUSED,
|
---|
857 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
858 | int type ATTRIBUTE_UNUSED, int def ATTRIBUTE_UNUSED,
|
---|
859 | const xmlChar * defaultValue ATTRIBUTE_UNUSED,
|
---|
860 | xmlEnumerationPtr tree ATTRIBUTE_UNUSED)
|
---|
861 | {
|
---|
862 | callbacks++;
|
---|
863 | return;
|
---|
864 | }
|
---|
865 |
|
---|
866 | /**
|
---|
867 | * elementDeclCallback:
|
---|
868 | * @ctxt: An XML parser context
|
---|
869 | * @name: the element name
|
---|
870 | * @type: the element type
|
---|
871 | * @content: the element value (without processing).
|
---|
872 | *
|
---|
873 | * An element definition has been parsed
|
---|
874 | */
|
---|
875 | static void
|
---|
876 | elementDeclCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
877 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
878 | int type ATTRIBUTE_UNUSED,
|
---|
879 | xmlElementContentPtr content ATTRIBUTE_UNUSED)
|
---|
880 | {
|
---|
881 | callbacks++;
|
---|
882 | return;
|
---|
883 | }
|
---|
884 |
|
---|
885 | /**
|
---|
886 | * notationDeclCallback:
|
---|
887 | * @ctxt: An XML parser context
|
---|
888 | * @name: The name of the notation
|
---|
889 | * @publicId: The public ID of the entity
|
---|
890 | * @systemId: The system ID of the entity
|
---|
891 | *
|
---|
892 | * What to do when a notation declaration has been parsed.
|
---|
893 | */
|
---|
894 | static void
|
---|
895 | notationDeclCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
896 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
897 | const xmlChar * publicId ATTRIBUTE_UNUSED,
|
---|
898 | const xmlChar * systemId ATTRIBUTE_UNUSED)
|
---|
899 | {
|
---|
900 | callbacks++;
|
---|
901 | return;
|
---|
902 | }
|
---|
903 |
|
---|
904 | /**
|
---|
905 | * unparsedEntityDeclCallback:
|
---|
906 | * @ctxt: An XML parser context
|
---|
907 | * @name: The name of the entity
|
---|
908 | * @publicId: The public ID of the entity
|
---|
909 | * @systemId: The system ID of the entity
|
---|
910 | * @notationName: the name of the notation
|
---|
911 | *
|
---|
912 | * What to do when an unparsed entity declaration is parsed
|
---|
913 | */
|
---|
914 | static void
|
---|
915 | unparsedEntityDeclCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
916 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
917 | const xmlChar * publicId ATTRIBUTE_UNUSED,
|
---|
918 | const xmlChar * systemId ATTRIBUTE_UNUSED,
|
---|
919 | const xmlChar * notationName ATTRIBUTE_UNUSED)
|
---|
920 | {
|
---|
921 | callbacks++;
|
---|
922 | return;
|
---|
923 | }
|
---|
924 |
|
---|
925 | /**
|
---|
926 | * setDocumentLocatorCallback:
|
---|
927 | * @ctxt: An XML parser context
|
---|
928 | * @loc: A SAX Locator
|
---|
929 | *
|
---|
930 | * Receive the document locator at startup, actually xmlDefaultSAXLocator
|
---|
931 | * Everything is available on the context, so this is useless in our case.
|
---|
932 | */
|
---|
933 | static void
|
---|
934 | setDocumentLocatorCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
935 | xmlSAXLocatorPtr loc ATTRIBUTE_UNUSED)
|
---|
936 | {
|
---|
937 | callbacks++;
|
---|
938 | return;
|
---|
939 | }
|
---|
940 |
|
---|
941 | /**
|
---|
942 | * startDocumentCallback:
|
---|
943 | * @ctxt: An XML parser context
|
---|
944 | *
|
---|
945 | * called when the document start being processed.
|
---|
946 | */
|
---|
947 | static void
|
---|
948 | startDocumentCallback(void *ctx ATTRIBUTE_UNUSED)
|
---|
949 | {
|
---|
950 | callbacks++;
|
---|
951 | return;
|
---|
952 | }
|
---|
953 |
|
---|
954 | /**
|
---|
955 | * endDocumentCallback:
|
---|
956 | * @ctxt: An XML parser context
|
---|
957 | *
|
---|
958 | * called when the document end has been detected.
|
---|
959 | */
|
---|
960 | static void
|
---|
961 | endDocumentCallback(void *ctx ATTRIBUTE_UNUSED)
|
---|
962 | {
|
---|
963 | callbacks++;
|
---|
964 | return;
|
---|
965 | }
|
---|
966 |
|
---|
967 | #if 0
|
---|
968 | /**
|
---|
969 | * startElementCallback:
|
---|
970 | * @ctxt: An XML parser context
|
---|
971 | * @name: The element name
|
---|
972 | *
|
---|
973 | * called when an opening tag has been processed.
|
---|
974 | */
|
---|
975 | static void
|
---|
976 | startElementCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
977 | const xmlChar * name ATTRIBUTE_UNUSED,
|
---|
978 | const xmlChar ** atts ATTRIBUTE_UNUSED)
|
---|
979 | {
|
---|
980 | callbacks++;
|
---|
981 | return;
|
---|
982 | }
|
---|
983 |
|
---|
984 | /**
|
---|
985 | * endElementCallback:
|
---|
986 | * @ctxt: An XML parser context
|
---|
987 | * @name: The element name
|
---|
988 | *
|
---|
989 | * called when the end of an element has been detected.
|
---|
990 | */
|
---|
991 | static void
|
---|
992 | endElementCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
993 | const xmlChar * name ATTRIBUTE_UNUSED)
|
---|
994 | {
|
---|
995 | callbacks++;
|
---|
996 | return;
|
---|
997 | }
|
---|
998 | #endif
|
---|
999 |
|
---|
1000 | /**
|
---|
1001 | * charactersCallback:
|
---|
1002 | * @ctxt: An XML parser context
|
---|
1003 | * @ch: a xmlChar string
|
---|
1004 | * @len: the number of xmlChar
|
---|
1005 | *
|
---|
1006 | * receiving some chars from the parser.
|
---|
1007 | * Question: how much at a time ???
|
---|
1008 | */
|
---|
1009 | static void
|
---|
1010 | charactersCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1011 | const xmlChar * ch ATTRIBUTE_UNUSED,
|
---|
1012 | int len ATTRIBUTE_UNUSED)
|
---|
1013 | {
|
---|
1014 | callbacks++;
|
---|
1015 | return;
|
---|
1016 | }
|
---|
1017 |
|
---|
1018 | /**
|
---|
1019 | * referenceCallback:
|
---|
1020 | * @ctxt: An XML parser context
|
---|
1021 | * @name: The entity name
|
---|
1022 | *
|
---|
1023 | * called when an entity reference is detected.
|
---|
1024 | */
|
---|
1025 | static void
|
---|
1026 | referenceCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1027 | const xmlChar * name ATTRIBUTE_UNUSED)
|
---|
1028 | {
|
---|
1029 | callbacks++;
|
---|
1030 | return;
|
---|
1031 | }
|
---|
1032 |
|
---|
1033 | /**
|
---|
1034 | * ignorableWhitespaceCallback:
|
---|
1035 | * @ctxt: An XML parser context
|
---|
1036 | * @ch: a xmlChar string
|
---|
1037 | * @start: the first char in the string
|
---|
1038 | * @len: the number of xmlChar
|
---|
1039 | *
|
---|
1040 | * receiving some ignorable whitespaces from the parser.
|
---|
1041 | * Question: how much at a time ???
|
---|
1042 | */
|
---|
1043 | static void
|
---|
1044 | ignorableWhitespaceCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1045 | const xmlChar * ch ATTRIBUTE_UNUSED,
|
---|
1046 | int len ATTRIBUTE_UNUSED)
|
---|
1047 | {
|
---|
1048 | callbacks++;
|
---|
1049 | return;
|
---|
1050 | }
|
---|
1051 |
|
---|
1052 | /**
|
---|
1053 | * processingInstructionCallback:
|
---|
1054 | * @ctxt: An XML parser context
|
---|
1055 | * @target: the target name
|
---|
1056 | * @data: the PI data's
|
---|
1057 | * @len: the number of xmlChar
|
---|
1058 | *
|
---|
1059 | * A processing instruction has been parsed.
|
---|
1060 | */
|
---|
1061 | static void
|
---|
1062 | processingInstructionCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1063 | const xmlChar * target ATTRIBUTE_UNUSED,
|
---|
1064 | const xmlChar * data ATTRIBUTE_UNUSED)
|
---|
1065 | {
|
---|
1066 | callbacks++;
|
---|
1067 | return;
|
---|
1068 | }
|
---|
1069 |
|
---|
1070 | /**
|
---|
1071 | * cdataBlockCallback:
|
---|
1072 | * @ctx: the user data (XML parser context)
|
---|
1073 | * @value: The pcdata content
|
---|
1074 | * @len: the block length
|
---|
1075 | *
|
---|
1076 | * called when a pcdata block has been parsed
|
---|
1077 | */
|
---|
1078 | static void
|
---|
1079 | cdataBlockCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1080 | const xmlChar * value ATTRIBUTE_UNUSED,
|
---|
1081 | int len ATTRIBUTE_UNUSED)
|
---|
1082 | {
|
---|
1083 | callbacks++;
|
---|
1084 | return;
|
---|
1085 | }
|
---|
1086 |
|
---|
1087 | /**
|
---|
1088 | * commentCallback:
|
---|
1089 | * @ctxt: An XML parser context
|
---|
1090 | * @value: the comment content
|
---|
1091 | *
|
---|
1092 | * A comment has been parsed.
|
---|
1093 | */
|
---|
1094 | static void
|
---|
1095 | commentCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1096 | const xmlChar * value ATTRIBUTE_UNUSED)
|
---|
1097 | {
|
---|
1098 | callbacks++;
|
---|
1099 | return;
|
---|
1100 | }
|
---|
1101 |
|
---|
1102 | /**
|
---|
1103 | * warningCallback:
|
---|
1104 | * @ctxt: An XML parser context
|
---|
1105 | * @msg: the message to display/transmit
|
---|
1106 | * @...: extra parameters for the message display
|
---|
1107 | *
|
---|
1108 | * Display and format a warning messages, gives file, line, position and
|
---|
1109 | * extra parameters.
|
---|
1110 | */
|
---|
1111 | static void
|
---|
1112 | warningCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1113 | const char *msg ATTRIBUTE_UNUSED, ...)
|
---|
1114 | {
|
---|
1115 | callbacks++;
|
---|
1116 | return;
|
---|
1117 | }
|
---|
1118 |
|
---|
1119 | /**
|
---|
1120 | * errorCallback:
|
---|
1121 | * @ctxt: An XML parser context
|
---|
1122 | * @msg: the message to display/transmit
|
---|
1123 | * @...: extra parameters for the message display
|
---|
1124 | *
|
---|
1125 | * Display and format a error messages, gives file, line, position and
|
---|
1126 | * extra parameters.
|
---|
1127 | */
|
---|
1128 | static void
|
---|
1129 | errorCallback(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
|
---|
1130 | ...)
|
---|
1131 | {
|
---|
1132 | callbacks++;
|
---|
1133 | return;
|
---|
1134 | }
|
---|
1135 |
|
---|
1136 | /**
|
---|
1137 | * fatalErrorCallback:
|
---|
1138 | * @ctxt: An XML parser context
|
---|
1139 | * @msg: the message to display/transmit
|
---|
1140 | * @...: extra parameters for the message display
|
---|
1141 | *
|
---|
1142 | * Display and format a fatalError messages, gives file, line, position and
|
---|
1143 | * extra parameters.
|
---|
1144 | */
|
---|
1145 | static void
|
---|
1146 | fatalErrorCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1147 | const char *msg ATTRIBUTE_UNUSED, ...)
|
---|
1148 | {
|
---|
1149 | return;
|
---|
1150 | }
|
---|
1151 |
|
---|
1152 |
|
---|
1153 | /*
|
---|
1154 | * SAX2 specific callbacks
|
---|
1155 | */
|
---|
1156 |
|
---|
1157 | /**
|
---|
1158 | * startElementNsCallback:
|
---|
1159 | * @ctxt: An XML parser context
|
---|
1160 | * @name: The element name
|
---|
1161 | *
|
---|
1162 | * called when an opening tag has been processed.
|
---|
1163 | */
|
---|
1164 | static void
|
---|
1165 | startElementNsCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1166 | const xmlChar * localname ATTRIBUTE_UNUSED,
|
---|
1167 | const xmlChar * prefix ATTRIBUTE_UNUSED,
|
---|
1168 | const xmlChar * URI ATTRIBUTE_UNUSED,
|
---|
1169 | int nb_namespaces ATTRIBUTE_UNUSED,
|
---|
1170 | const xmlChar ** namespaces ATTRIBUTE_UNUSED,
|
---|
1171 | int nb_attributes ATTRIBUTE_UNUSED,
|
---|
1172 | int nb_defaulted ATTRIBUTE_UNUSED,
|
---|
1173 | const xmlChar ** attributes ATTRIBUTE_UNUSED)
|
---|
1174 | {
|
---|
1175 | callbacks++;
|
---|
1176 | return;
|
---|
1177 | }
|
---|
1178 |
|
---|
1179 | /**
|
---|
1180 | * endElementCallback:
|
---|
1181 | * @ctxt: An XML parser context
|
---|
1182 | * @name: The element name
|
---|
1183 | *
|
---|
1184 | * called when the end of an element has been detected.
|
---|
1185 | */
|
---|
1186 | static void
|
---|
1187 | endElementNsCallback(void *ctx ATTRIBUTE_UNUSED,
|
---|
1188 | const xmlChar * localname ATTRIBUTE_UNUSED,
|
---|
1189 | const xmlChar * prefix ATTRIBUTE_UNUSED,
|
---|
1190 | const xmlChar * URI ATTRIBUTE_UNUSED)
|
---|
1191 | {
|
---|
1192 | callbacks++;
|
---|
1193 | return;
|
---|
1194 | }
|
---|
1195 |
|
---|
1196 | static xmlSAXHandler callbackSAX2HandlerStruct = {
|
---|
1197 | internalSubsetCallback,
|
---|
1198 | isStandaloneCallback,
|
---|
1199 | hasInternalSubsetCallback,
|
---|
1200 | hasExternalSubsetCallback,
|
---|
1201 | resolveEntityCallback,
|
---|
1202 | getEntityCallback,
|
---|
1203 | entityDeclCallback,
|
---|
1204 | notationDeclCallback,
|
---|
1205 | attributeDeclCallback,
|
---|
1206 | elementDeclCallback,
|
---|
1207 | unparsedEntityDeclCallback,
|
---|
1208 | setDocumentLocatorCallback,
|
---|
1209 | startDocumentCallback,
|
---|
1210 | endDocumentCallback,
|
---|
1211 | NULL,
|
---|
1212 | NULL,
|
---|
1213 | referenceCallback,
|
---|
1214 | charactersCallback,
|
---|
1215 | ignorableWhitespaceCallback,
|
---|
1216 | processingInstructionCallback,
|
---|
1217 | commentCallback,
|
---|
1218 | warningCallback,
|
---|
1219 | errorCallback,
|
---|
1220 | fatalErrorCallback,
|
---|
1221 | getParameterEntityCallback,
|
---|
1222 | cdataBlockCallback,
|
---|
1223 | externalSubsetCallback,
|
---|
1224 | XML_SAX2_MAGIC,
|
---|
1225 | NULL,
|
---|
1226 | startElementNsCallback,
|
---|
1227 | endElementNsCallback,
|
---|
1228 | NULL
|
---|
1229 | };
|
---|
1230 |
|
---|
1231 | static xmlSAXHandlerPtr callbackSAX2Handler = &callbackSAX2HandlerStruct;
|
---|
1232 |
|
---|
1233 | /************************************************************************
|
---|
1234 | * *
|
---|
1235 | * The tests front-ends *
|
---|
1236 | * *
|
---|
1237 | ************************************************************************/
|
---|
1238 |
|
---|
1239 | /**
|
---|
1240 | * readerTest:
|
---|
1241 | * @filename: the file to parse
|
---|
1242 | * @max_size: size of the limit to test
|
---|
1243 | * @options: parsing options
|
---|
1244 | * @fail: should a failure be reported
|
---|
1245 | *
|
---|
1246 | * Parse a memory generated file using SAX
|
---|
1247 | *
|
---|
1248 | * Returns 0 in case of success, an error code otherwise
|
---|
1249 | */
|
---|
1250 | static int
|
---|
1251 | saxTest(const char *filename, size_t limit, int options, int fail) {
|
---|
1252 | int res = 0;
|
---|
1253 | xmlParserCtxtPtr ctxt;
|
---|
1254 | xmlDocPtr doc;
|
---|
1255 |
|
---|
1256 | nb_tests++;
|
---|
1257 |
|
---|
1258 | maxlen = limit;
|
---|
1259 | ctxt = xmlNewSAXParserCtxt(callbackSAX2Handler, NULL);
|
---|
1260 | if (ctxt == NULL) {
|
---|
1261 | fprintf(stderr, "Failed to create parser context\n");
|
---|
1262 | return(1);
|
---|
1263 | }
|
---|
1264 | doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
|
---|
1265 |
|
---|
1266 | if (doc != NULL) {
|
---|
1267 | fprintf(stderr, "SAX parsing generated a document !\n");
|
---|
1268 | xmlFreeDoc(doc);
|
---|
1269 | res = 0;
|
---|
1270 | } else if (ctxt->wellFormed == 0) {
|
---|
1271 | if (fail)
|
---|
1272 | res = 0;
|
---|
1273 | else {
|
---|
1274 | fprintf(stderr, "Failed to parse '%s' %lu\n", filename,
|
---|
1275 | (unsigned long) limit);
|
---|
1276 | res = 1;
|
---|
1277 | }
|
---|
1278 | } else {
|
---|
1279 | if (fail) {
|
---|
1280 | fprintf(stderr, "Failed to get failure for '%s' %lu\n",
|
---|
1281 | filename, (unsigned long) limit);
|
---|
1282 | res = 1;
|
---|
1283 | } else
|
---|
1284 | res = 0;
|
---|
1285 | }
|
---|
1286 | xmlFreeParserCtxt(ctxt);
|
---|
1287 |
|
---|
1288 | return(res);
|
---|
1289 | }
|
---|
1290 | #ifdef LIBXML_READER_ENABLED
|
---|
1291 | /**
|
---|
1292 | * readerTest:
|
---|
1293 | * @filename: the file to parse
|
---|
1294 | * @max_size: size of the limit to test
|
---|
1295 | * @options: parsing options
|
---|
1296 | * @fail: should a failure be reported
|
---|
1297 | *
|
---|
1298 | * Parse a memory generated file using the xmlReader
|
---|
1299 | *
|
---|
1300 | * Returns 0 in case of success, an error code otherwise
|
---|
1301 | */
|
---|
1302 | static int
|
---|
1303 | readerTest(const char *filename, size_t limit, int options, int fail) {
|
---|
1304 | xmlTextReaderPtr reader;
|
---|
1305 | int res = 0;
|
---|
1306 | int ret;
|
---|
1307 |
|
---|
1308 | nb_tests++;
|
---|
1309 |
|
---|
1310 | maxlen = limit;
|
---|
1311 | reader = xmlReaderForFile(filename , NULL, options);
|
---|
1312 | if (reader == NULL) {
|
---|
1313 | fprintf(stderr, "Failed to open '%s' test\n", filename);
|
---|
1314 | return(1);
|
---|
1315 | }
|
---|
1316 | ret = xmlTextReaderRead(reader);
|
---|
1317 | while (ret == 1) {
|
---|
1318 | ret = xmlTextReaderRead(reader);
|
---|
1319 | }
|
---|
1320 | if (ret != 0) {
|
---|
1321 | if (fail)
|
---|
1322 | res = 0;
|
---|
1323 | else {
|
---|
1324 | if (strncmp(filename, "crazy:", 6) == 0)
|
---|
1325 | fprintf(stderr, "Failed to parse '%s' %u\n",
|
---|
1326 | filename, crazy_indx);
|
---|
1327 | else
|
---|
1328 | fprintf(stderr, "Failed to parse '%s' %lu\n",
|
---|
1329 | filename, (unsigned long) limit);
|
---|
1330 | res = 1;
|
---|
1331 | }
|
---|
1332 | } else {
|
---|
1333 | if (fail) {
|
---|
1334 | if (strncmp(filename, "crazy:", 6) == 0)
|
---|
1335 | fprintf(stderr, "Failed to get failure for '%s' %u\n",
|
---|
1336 | filename, crazy_indx);
|
---|
1337 | else
|
---|
1338 | fprintf(stderr, "Failed to get failure for '%s' %lu\n",
|
---|
1339 | filename, (unsigned long) limit);
|
---|
1340 | res = 1;
|
---|
1341 | } else
|
---|
1342 | res = 0;
|
---|
1343 | }
|
---|
1344 | if (timeout)
|
---|
1345 | res = 1;
|
---|
1346 | xmlFreeTextReader(reader);
|
---|
1347 |
|
---|
1348 | return(res);
|
---|
1349 | }
|
---|
1350 | #endif
|
---|
1351 |
|
---|
1352 | /************************************************************************
|
---|
1353 | * *
|
---|
1354 | * Tests descriptions *
|
---|
1355 | * *
|
---|
1356 | ************************************************************************/
|
---|
1357 |
|
---|
1358 | typedef int (*functest) (const char *filename, size_t limit, int options,
|
---|
1359 | int fail);
|
---|
1360 |
|
---|
1361 | typedef struct limitDesc limitDesc;
|
---|
1362 | typedef limitDesc *limitDescPtr;
|
---|
1363 | struct limitDesc {
|
---|
1364 | const char *name; /* the huge generator name */
|
---|
1365 | size_t limit; /* the limit to test */
|
---|
1366 | int options; /* extra parser options */
|
---|
1367 | int fail; /* whether the test should fail */
|
---|
1368 | };
|
---|
1369 |
|
---|
1370 | static limitDesc limitDescriptions[] = {
|
---|
1371 | /* max length of a text node in content */
|
---|
1372 | {"huge:textNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0},
|
---|
1373 | {"huge:textNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1},
|
---|
1374 | {"huge:textNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0},
|
---|
1375 | /* max length of a text node in content */
|
---|
1376 | {"huge:attrNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0},
|
---|
1377 | {"huge:attrNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1},
|
---|
1378 | {"huge:attrNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0},
|
---|
1379 | /* max length of a comment node */
|
---|
1380 | {"huge:commentNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0},
|
---|
1381 | {"huge:commentNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1},
|
---|
1382 | {"huge:commentNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0},
|
---|
1383 | /* max length of a PI node */
|
---|
1384 | {"huge:piNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0},
|
---|
1385 | {"huge:piNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1},
|
---|
1386 | {"huge:piNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0},
|
---|
1387 | };
|
---|
1388 |
|
---|
1389 | typedef struct testDesc testDesc;
|
---|
1390 | typedef testDesc *testDescPtr;
|
---|
1391 | struct testDesc {
|
---|
1392 | const char *desc; /* description of the test */
|
---|
1393 | functest func; /* function implementing the test */
|
---|
1394 | };
|
---|
1395 |
|
---|
1396 | static
|
---|
1397 | testDesc testDescriptions[] = {
|
---|
1398 | { "Parsing of huge files with the sax parser", saxTest},
|
---|
1399 | /* { "Parsing of huge files with the tree parser", treeTest}, */
|
---|
1400 | #ifdef LIBXML_READER_ENABLED
|
---|
1401 | { "Parsing of huge files with the reader", readerTest},
|
---|
1402 | #endif
|
---|
1403 | {NULL, NULL}
|
---|
1404 | };
|
---|
1405 |
|
---|
1406 | typedef struct testException testException;
|
---|
1407 | typedef testException *testExceptionPtr;
|
---|
1408 | struct testException {
|
---|
1409 | unsigned int test; /* the parser test number */
|
---|
1410 | unsigned int limit; /* the limit test number */
|
---|
1411 | int fail; /* new fail value or -1*/
|
---|
1412 | size_t size; /* new limit value or 0 */
|
---|
1413 | };
|
---|
1414 |
|
---|
1415 | static
|
---|
1416 | testException testExceptions[] = {
|
---|
1417 | /* the SAX parser doesn't hit a limit of XML_MAX_TEXT_LENGTH text nodes */
|
---|
1418 | { 0, 1, 0, 0},
|
---|
1419 | };
|
---|
1420 |
|
---|
1421 | static int
|
---|
1422 | launchTests(testDescPtr tst, unsigned int test) {
|
---|
1423 | int res = 0, err = 0;
|
---|
1424 | unsigned int i, j;
|
---|
1425 | size_t limit;
|
---|
1426 | int fail;
|
---|
1427 |
|
---|
1428 | if (tst == NULL) return(-1);
|
---|
1429 |
|
---|
1430 | for (i = 0;i < sizeof(limitDescriptions)/sizeof(limitDescriptions[0]);i++) {
|
---|
1431 | limit = limitDescriptions[i].limit;
|
---|
1432 | fail = limitDescriptions[i].fail;
|
---|
1433 | /*
|
---|
1434 | * Handle exceptions if any
|
---|
1435 | */
|
---|
1436 | for (j = 0;j < sizeof(testExceptions)/sizeof(testExceptions[0]);j++) {
|
---|
1437 | if ((testExceptions[j].test == test) &&
|
---|
1438 | (testExceptions[j].limit == i)) {
|
---|
1439 | if (testExceptions[j].fail != -1)
|
---|
1440 | fail = testExceptions[j].fail;
|
---|
1441 | if (testExceptions[j].size != 0)
|
---|
1442 | limit = testExceptions[j].size;
|
---|
1443 | break;
|
---|
1444 | }
|
---|
1445 | }
|
---|
1446 | res = tst->func(limitDescriptions[i].name, limit,
|
---|
1447 | limitDescriptions[i].options, fail);
|
---|
1448 | if (res != 0) {
|
---|
1449 | nb_errors++;
|
---|
1450 | err++;
|
---|
1451 | }
|
---|
1452 | }
|
---|
1453 | return(err);
|
---|
1454 | }
|
---|
1455 |
|
---|
1456 |
|
---|
1457 | static int
|
---|
1458 | runtest(unsigned int i) {
|
---|
1459 | int ret = 0, res;
|
---|
1460 | int old_errors, old_tests, old_leaks;
|
---|
1461 |
|
---|
1462 | old_errors = nb_errors;
|
---|
1463 | old_tests = nb_tests;
|
---|
1464 | old_leaks = nb_leaks;
|
---|
1465 | if ((tests_quiet == 0) && (testDescriptions[i].desc != NULL))
|
---|
1466 | printf("## %s\n", testDescriptions[i].desc);
|
---|
1467 | res = launchTests(&testDescriptions[i], i);
|
---|
1468 | if (res != 0)
|
---|
1469 | ret++;
|
---|
1470 | if (verbose) {
|
---|
1471 | if ((nb_errors == old_errors) && (nb_leaks == old_leaks))
|
---|
1472 | printf("Ran %d tests, no errors\n", nb_tests - old_tests);
|
---|
1473 | else
|
---|
1474 | printf("Ran %d tests, %d errors, %d leaks\n",
|
---|
1475 | nb_tests - old_tests,
|
---|
1476 | nb_errors - old_errors,
|
---|
1477 | nb_leaks - old_leaks);
|
---|
1478 | }
|
---|
1479 | return(ret);
|
---|
1480 | }
|
---|
1481 |
|
---|
1482 | static int
|
---|
1483 | launchCrazySAX(unsigned int test, int fail) {
|
---|
1484 | int res = 0, err = 0;
|
---|
1485 |
|
---|
1486 | crazy_indx = test;
|
---|
1487 |
|
---|
1488 | res = saxTest("crazy::test", XML_MAX_LOOKUP_LIMIT - CHUNK, 0, fail);
|
---|
1489 | if (res != 0) {
|
---|
1490 | nb_errors++;
|
---|
1491 | err++;
|
---|
1492 | }
|
---|
1493 | if (tests_quiet == 0)
|
---|
1494 | fprintf(stderr, "%c", crazy[test]);
|
---|
1495 |
|
---|
1496 | return(err);
|
---|
1497 | }
|
---|
1498 |
|
---|
1499 | #ifdef LIBXML_READER_ENABLED
|
---|
1500 | static int
|
---|
1501 | launchCrazy(unsigned int test, int fail) {
|
---|
1502 | int res = 0, err = 0;
|
---|
1503 |
|
---|
1504 | crazy_indx = test;
|
---|
1505 |
|
---|
1506 | res = readerTest("crazy::test", XML_MAX_LOOKUP_LIMIT - CHUNK, 0, fail);
|
---|
1507 | if (res != 0) {
|
---|
1508 | nb_errors++;
|
---|
1509 | err++;
|
---|
1510 | }
|
---|
1511 | if (tests_quiet == 0)
|
---|
1512 | fprintf(stderr, "%c", crazy[test]);
|
---|
1513 |
|
---|
1514 | return(err);
|
---|
1515 | }
|
---|
1516 | #endif
|
---|
1517 |
|
---|
1518 | static int get_crazy_fail(int test) {
|
---|
1519 | /*
|
---|
1520 | * adding 1000000 of character 'a' leads to parser failure mostly
|
---|
1521 | * everywhere except in those special spots. Need to be updated
|
---|
1522 | * each time crazy is updated
|
---|
1523 | */
|
---|
1524 | int fail = 1;
|
---|
1525 | if ((test == 44) || /* PI in Misc */
|
---|
1526 | ((test >= 50) && (test <= 55)) || /* Comment in Misc */
|
---|
1527 | (test == 79) || /* PI in DTD */
|
---|
1528 | ((test >= 85) && (test <= 90)) || /* Comment in DTD */
|
---|
1529 | (test == 154) || /* PI in Misc */
|
---|
1530 | ((test >= 160) && (test <= 165)) || /* Comment in Misc */
|
---|
1531 | ((test >= 178) && (test <= 181)) || /* attribute value */
|
---|
1532 | (test == 183) || /* Text */
|
---|
1533 | (test == 189) || /* PI in Content */
|
---|
1534 | (test == 191) || /* Text */
|
---|
1535 | ((test >= 195) && (test <= 200)) || /* Comment in Content */
|
---|
1536 | ((test >= 203) && (test <= 206)) || /* Text */
|
---|
1537 | (test == 215) || (test == 216) || /* in CDATA */
|
---|
1538 | (test == 219) || /* Text */
|
---|
1539 | (test == 231) || /* PI in Misc */
|
---|
1540 | ((test >= 237) && (test <= 242))) /* Comment in Misc */
|
---|
1541 | fail = 0;
|
---|
1542 | return(fail);
|
---|
1543 | }
|
---|
1544 |
|
---|
1545 | static int
|
---|
1546 | runcrazy(void) {
|
---|
1547 | int ret = 0, res = 0;
|
---|
1548 | int old_errors, old_tests, old_leaks;
|
---|
1549 | unsigned int i;
|
---|
1550 |
|
---|
1551 | old_errors = nb_errors;
|
---|
1552 | old_tests = nb_tests;
|
---|
1553 | old_leaks = nb_leaks;
|
---|
1554 |
|
---|
1555 | #ifdef LIBXML_READER_ENABLED
|
---|
1556 | if (tests_quiet == 0) {
|
---|
1557 | printf("## Crazy tests on reader\n");
|
---|
1558 | }
|
---|
1559 | for (i = 0;i < strlen(crazy);i++) {
|
---|
1560 | res += launchCrazy(i, get_crazy_fail(i));
|
---|
1561 | if (res != 0)
|
---|
1562 | ret++;
|
---|
1563 | }
|
---|
1564 | #endif
|
---|
1565 |
|
---|
1566 | if (tests_quiet == 0) {
|
---|
1567 | printf("\n## Crazy tests on SAX\n");
|
---|
1568 | }
|
---|
1569 | for (i = 0;i < strlen(crazy);i++) {
|
---|
1570 | res += launchCrazySAX(i, get_crazy_fail(i));
|
---|
1571 | if (res != 0)
|
---|
1572 | ret++;
|
---|
1573 | }
|
---|
1574 | if (tests_quiet == 0)
|
---|
1575 | fprintf(stderr, "\n");
|
---|
1576 | if (verbose) {
|
---|
1577 | if ((nb_errors == old_errors) && (nb_leaks == old_leaks))
|
---|
1578 | printf("Ran %d tests, no errors\n", nb_tests - old_tests);
|
---|
1579 | else
|
---|
1580 | printf("Ran %d tests, %d errors, %d leaks\n",
|
---|
1581 | nb_tests - old_tests,
|
---|
1582 | nb_errors - old_errors,
|
---|
1583 | nb_leaks - old_leaks);
|
---|
1584 | }
|
---|
1585 | return(ret);
|
---|
1586 | }
|
---|
1587 |
|
---|
1588 |
|
---|
1589 | int
|
---|
1590 | main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
|
---|
1591 | int i, a, ret = 0;
|
---|
1592 | int subset = 0;
|
---|
1593 |
|
---|
1594 | fillFilling();
|
---|
1595 | initializeLibxml2();
|
---|
1596 |
|
---|
1597 | for (a = 1; a < argc;a++) {
|
---|
1598 | if (!strcmp(argv[a], "-v"))
|
---|
1599 | verbose = 1;
|
---|
1600 | else if (!strcmp(argv[a], "-quiet"))
|
---|
1601 | tests_quiet = 1;
|
---|
1602 | else if (!strcmp(argv[a], "-crazy"))
|
---|
1603 | subset = 1;
|
---|
1604 | }
|
---|
1605 | if (subset == 0) {
|
---|
1606 | for (i = 0; testDescriptions[i].func != NULL; i++) {
|
---|
1607 | ret += runtest(i);
|
---|
1608 | }
|
---|
1609 | }
|
---|
1610 | ret += runcrazy();
|
---|
1611 | if ((nb_errors == 0) && (nb_leaks == 0)) {
|
---|
1612 | ret = 0;
|
---|
1613 | printf("Total %d tests, no errors\n",
|
---|
1614 | nb_tests);
|
---|
1615 | } else {
|
---|
1616 | ret = 1;
|
---|
1617 | printf("Total %d tests, %d errors, %d leaks\n",
|
---|
1618 | nb_tests, nb_errors, nb_leaks);
|
---|
1619 | }
|
---|
1620 | xmlCleanupParser();
|
---|
1621 |
|
---|
1622 | return(ret);
|
---|
1623 | }
|
---|