/* * testlimits.c: C program to run libxml2 regression tests checking various * limits in document size. Will consume a lot of RAM and CPU cycles * * To compile on Unixes: * cc -o testlimits `xml2-config --cflags` testlimits.c `xml2-config --libs` -lpthread * * See Copyright for the status of this software. * * daniel@veillard.com */ #include "libxml.h" #include #if !defined(_WIN32) || defined(__CYGWIN__) #include #endif #include #include #include #include #include #include #include #include #include #ifdef LIBXML_READER_ENABLED #include #endif static int verbose = 0; static int tests_quiet = 0; /************************************************************************ * * * time handling * * * ************************************************************************/ /* maximum time for one parsing before declaring a timeout */ #define MAX_TIME 2 /* seconds */ static clock_t t0; int timeout = 0; static void reset_timout(void) { timeout = 0; t0 = clock(); } static int check_time(void) { clock_t tnow = clock(); if (((tnow - t0) / CLOCKS_PER_SEC) > MAX_TIME) { timeout = 1; return(0); } return(1); } /************************************************************************ * * * Huge document generator * * * ************************************************************************/ #include /* * Huge documents are built using fixed start and end chunks * and filling between the two an unconventional amount of char data */ typedef struct hugeTest hugeTest; typedef hugeTest *hugeTestPtr; struct hugeTest { const char *description; const char *name; const char *start; const char *end; }; static struct hugeTest hugeTests[] = { { "Huge text node", "huge:textNode", "", "" }, { "Huge attribute node", "huge:attrNode", "" }, { "Huge comment node", "huge:commentNode", "" }, { "Huge PI node", "huge:piNode", "" }, }; static const char *current; static int rlen; static unsigned int currentTest = 0; static int instate = 0; /** * hugeMatch: * @URI: an URI to test * * Check for an huge: query * * Returns 1 if yes and 0 if another Input module should be used */ static int hugeMatch(const char * URI) { if ((URI != NULL) && (!strncmp(URI, "huge:", 5))) return(1); return(0); } /** * hugeOpen: * @URI: an URI to test * * Return a pointer to the huge: query handler, in this example simply * the current pointer... * * Returns an Input context or NULL in case or error */ static void * hugeOpen(const char * URI) { if ((URI == NULL) || (strncmp(URI, "huge:", 5))) return(NULL); for (currentTest = 0;currentTest < sizeof(hugeTests)/sizeof(hugeTests[0]); currentTest++) if (!strcmp(hugeTests[currentTest].name, URI)) goto found; return(NULL); found: rlen = strlen(hugeTests[currentTest].start); current = hugeTests[currentTest].start; instate = 0; return((void *) current); } /** * hugeClose: * @context: the read context * * Close the huge: query handler * * Returns 0 or -1 in case of error */ static int hugeClose(void * context) { if (context == NULL) return(-1); fprintf(stderr, "\n"); return(0); } #define CHUNK 4096 char filling[CHUNK + 1]; static void fillFilling(void) { int i; for (i = 0;i < CHUNK;i++) { filling[i] = 'a'; } filling[CHUNK] = 0; } size_t maxlen = 64 * 1024 * 1024; size_t curlen = 0; size_t dotlen; /** * hugeRead: * @context: the read context * @buffer: where to store data * @len: number of bytes to read * * Implement an huge: query read. * * Returns the number of bytes read or -1 in case of error */ static int hugeRead(void *context, char *buffer, int len) { if ((context == NULL) || (buffer == NULL) || (len < 0)) return (-1); if (instate == 0) { if (len >= rlen) { len = rlen; rlen = 0; memcpy(buffer, current, len); instate = 1; curlen = 0; dotlen = maxlen / 10; } else { memcpy(buffer, current, len); rlen -= len; current += len; } } else if (instate == 2) { if (len >= rlen) { len = rlen; rlen = 0; memcpy(buffer, current, len); instate = 3; curlen = 0; } else { memcpy(buffer, current, len); rlen -= len; current += len; } } else if (instate == 1) { if (len > CHUNK) len = CHUNK; memcpy(buffer, &filling[0], len); curlen += len; if (curlen >= maxlen) { rlen = strlen(hugeTests[currentTest].end); current = hugeTests[currentTest].end; instate = 2; } else { if (curlen > dotlen) { fprintf(stderr, "."); dotlen += maxlen / 10; } } } else len = 0; return (len); } /************************************************************************ * * * Crazy document generator * * * ************************************************************************/ unsigned int crazy_indx = 0; const char *crazy = "\ \ \ \ \ \ \ ]>\ \ \ \ \ \ foo\ \ \ \ "; /** * crazyMatch: * @URI: an URI to test * * Check for a crazy: query * * Returns 1 if yes and 0 if another Input module should be used */ static int crazyMatch(const char * URI) { if ((URI != NULL) && (!strncmp(URI, "crazy:", 6))) return(1); return(0); } /** * crazyOpen: * @URI: an URI to test * * Return a pointer to the crazy: query handler, in this example simply * the current pointer... * * Returns an Input context or NULL in case or error */ static void * crazyOpen(const char * URI) { if ((URI == NULL) || (strncmp(URI, "crazy:", 6))) return(NULL); if (crazy_indx > strlen(crazy)) return(NULL); reset_timout(); rlen = crazy_indx; current = &crazy[0]; instate = 0; return((void *) current); } /** * crazyClose: * @context: the read context * * Close the crazy: query handler * * Returns 0 or -1 in case of error */ static int crazyClose(void * context) { if (context == NULL) return(-1); return(0); } /** * crazyRead: * @context: the read context * @buffer: where to store data * @len: number of bytes to read * * Implement an crazy: query read. * * Returns the number of bytes read or -1 in case of error */ static int crazyRead(void *context, char *buffer, int len) { if ((context == NULL) || (buffer == NULL) || (len < 0)) return (-1); if ((check_time() <= 0) && (instate == 1)) { fprintf(stderr, "\ntimeout in crazy(%d)\n", crazy_indx); rlen = strlen(crazy) - crazy_indx; current = &crazy[crazy_indx]; instate = 2; } if (instate == 0) { if (len >= rlen) { len = rlen; rlen = 0; memcpy(buffer, current, len); instate = 1; curlen = 0; } else { memcpy(buffer, current, len); rlen -= len; current += len; } } else if (instate == 2) { if (len >= rlen) { len = rlen; rlen = 0; memcpy(buffer, current, len); instate = 3; curlen = 0; } else { memcpy(buffer, current, len); rlen -= len; current += len; } } else if (instate == 1) { if (len > CHUNK) len = CHUNK; memcpy(buffer, &filling[0], len); curlen += len; if (curlen >= maxlen) { rlen = strlen(crazy) - crazy_indx; current = &crazy[crazy_indx]; instate = 2; } } else len = 0; return (len); } /************************************************************************ * * * Libxml2 specific routines * * * ************************************************************************/ static int nb_tests = 0; static int nb_errors = 0; static int nb_leaks = 0; static int extraMemoryFromResolver = 0; /* * We need to trap calls to the resolver to not account memory for the catalog * which is shared to the current running test. We also don't want to have * network downloads modifying tests. */ static xmlParserInputPtr testExternalEntityLoader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt) { xmlParserInputPtr ret; int memused = xmlMemUsed(); ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt); extraMemoryFromResolver += xmlMemUsed() - memused; return(ret); } /* * Trapping the error messages at the generic level to grab the equivalent of * stderr messages on CLI tools. */ static char testErrors[32769]; static int testErrorsSize = 0; static void XMLCDECL channel(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) { va_list args; int res; if (testErrorsSize >= 32768) return; va_start(args, msg); res = vsnprintf(&testErrors[testErrorsSize], 32768 - testErrorsSize, msg, args); va_end(args); if (testErrorsSize + res >= 32768) { /* buffer is full */ testErrorsSize = 32768; testErrors[testErrorsSize] = 0; } else { testErrorsSize += res; } testErrors[testErrorsSize] = 0; } /** * xmlParserPrintFileContext: * @input: an xmlParserInputPtr input * * Displays current context within the input content for error tracking */ static void xmlParserPrintFileContextInternal(xmlParserInputPtr input , xmlGenericErrorFunc chanl, void *data ) { const xmlChar *cur, *base; unsigned int n, col; /* GCC warns if signed, because compared with sizeof() */ xmlChar content[81]; /* space for 80 chars + line terminator */ xmlChar *ctnt; if (input == NULL) return; cur = input->cur; base = input->base; /* skip backwards over any end-of-lines */ while ((cur > base) && ((*(cur) == '\n') || (*(cur) == '\r'))) { cur--; } n = 0; /* search backwards for beginning-of-line (to max buff size) */ while ((n++ < (sizeof(content)-1)) && (cur > base) && (*(cur) != '\n') && (*(cur) != '\r')) cur--; if ((*(cur) == '\n') || (*(cur) == '\r')) cur++; /* calculate the error position in terms of the current position */ col = input->cur - cur; /* search forward for end-of-line (to max buff size) */ n = 0; ctnt = content; /* copy selected text to our buffer */ while ((*cur != 0) && (*(cur) != '\n') && (*(cur) != '\r') && (n < sizeof(content)-1)) { *ctnt++ = *cur++; n++; } *ctnt = 0; /* print out the selected text */ chanl(data ,"%s\n", content); /* create blank line with problem pointer */ n = 0; ctnt = content; /* (leave buffer space for pointer + line terminator) */ while ((nfile; line = err->line; code = err->code; domain = err->domain; level = err->level; node = err->node; if ((domain == XML_FROM_PARSER) || (domain == XML_FROM_HTML) || (domain == XML_FROM_DTD) || (domain == XML_FROM_NAMESPACE) || (domain == XML_FROM_IO) || (domain == XML_FROM_VALID)) { ctxt = err->ctxt; } str = err->message; if (code == XML_ERR_OK) return; if ((node != NULL) && (node->type == XML_ELEMENT_NODE)) name = node->name; /* * Maintain the compatibility with the legacy error handling */ if (ctxt != NULL) { input = ctxt->input; if ((input != NULL) && (input->filename == NULL) && (ctxt->inputNr > 1)) { cur = input; input = ctxt->inputTab[ctxt->inputNr - 2]; } if (input != NULL) { if (input->filename) channel(data, "%s:%d: ", input->filename, input->line); else if ((line != 0) && (domain == XML_FROM_PARSER)) channel(data, "Entity: line %d: ", input->line); } } else { if (file != NULL) channel(data, "%s:%d: ", file, line); else if ((line != 0) && (domain == XML_FROM_PARSER)) channel(data, "Entity: line %d: ", line); } if (name != NULL) { channel(data, "element %s: ", name); } if (code == XML_ERR_OK) return; switch (domain) { case XML_FROM_PARSER: channel(data, "parser "); break; case XML_FROM_NAMESPACE: channel(data, "namespace "); break; case XML_FROM_DTD: case XML_FROM_VALID: channel(data, "validity "); break; case XML_FROM_HTML: channel(data, "HTML parser "); break; case XML_FROM_MEMORY: channel(data, "memory "); break; case XML_FROM_OUTPUT: channel(data, "output "); break; case XML_FROM_IO: channel(data, "I/O "); break; case XML_FROM_XINCLUDE: channel(data, "XInclude "); break; case XML_FROM_XPATH: channel(data, "XPath "); break; case XML_FROM_XPOINTER: channel(data, "parser "); break; case XML_FROM_REGEXP: channel(data, "regexp "); break; case XML_FROM_MODULE: channel(data, "module "); break; case XML_FROM_SCHEMASV: channel(data, "Schemas validity "); break; case XML_FROM_SCHEMASP: channel(data, "Schemas parser "); break; case XML_FROM_RELAXNGP: channel(data, "Relax-NG parser "); break; case XML_FROM_RELAXNGV: channel(data, "Relax-NG validity "); break; case XML_FROM_CATALOG: channel(data, "Catalog "); break; case XML_FROM_C14N: channel(data, "C14N "); break; case XML_FROM_XSLT: channel(data, "XSLT "); break; default: break; } if (code == XML_ERR_OK) return; switch (level) { case XML_ERR_NONE: channel(data, ": "); break; case XML_ERR_WARNING: channel(data, "warning : "); break; case XML_ERR_ERROR: channel(data, "error : "); break; case XML_ERR_FATAL: channel(data, "error : "); break; } if (code == XML_ERR_OK) return; if (str != NULL) { int len; len = xmlStrlen((const xmlChar *)str); if ((len > 0) && (str[len - 1] != '\n')) channel(data, "%s\n", str); else channel(data, "%s", str); } else { channel(data, "%s\n", "out of memory error"); } if (code == XML_ERR_OK) return; if (ctxt != NULL) { xmlParserPrintFileContextInternal(input, channel, data); if (cur != NULL) { if (cur->filename) channel(data, "%s:%d: \n", cur->filename, cur->line); else if ((line != 0) && (domain == XML_FROM_PARSER)) channel(data, "Entity: line %d: \n", cur->line); xmlParserPrintFileContextInternal(cur, channel, data); } } if ((domain == XML_FROM_XPATH) && (err->str1 != NULL) && (err->int1 < 100) && (err->int1 < xmlStrlen((const xmlChar *)err->str1))) { xmlChar buf[150]; int i; channel(data, "%s\n", err->str1); for (i=0;i < err->int1;i++) buf[i] = ' '; buf[i++] = '^'; buf[i] = 0; channel(data, "%s\n", buf); } } static void initializeLibxml2(void) { xmlGetWarningsDefaultValue = 0; xmlPedanticParserDefault(0); xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup); xmlInitParser(); xmlSetExternalEntityLoader(testExternalEntityLoader); xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler); /* * register the new I/O handlers */ if (xmlRegisterInputCallbacks(hugeMatch, hugeOpen, hugeRead, hugeClose) < 0) { fprintf(stderr, "failed to register Huge handlers\n"); exit(1); } if (xmlRegisterInputCallbacks(crazyMatch, crazyOpen, crazyRead, crazyClose) < 0) { fprintf(stderr, "failed to register Crazy handlers\n"); exit(1); } } /************************************************************************ * * * SAX empty callbacks * * * ************************************************************************/ unsigned long callbacks = 0; /** * isStandaloneCallback: * @ctxt: An XML parser context * * Is this document tagged standalone ? * * Returns 1 if true */ static int isStandaloneCallback(void *ctx ATTRIBUTE_UNUSED) { callbacks++; return (0); } /** * hasInternalSubsetCallback: * @ctxt: An XML parser context * * Does this document has an internal subset * * Returns 1 if true */ static int hasInternalSubsetCallback(void *ctx ATTRIBUTE_UNUSED) { callbacks++; return (0); } /** * hasExternalSubsetCallback: * @ctxt: An XML parser context * * Does this document has an external subset * * Returns 1 if true */ static int hasExternalSubsetCallback(void *ctx ATTRIBUTE_UNUSED) { callbacks++; return (0); } /** * internalSubsetCallback: * @ctxt: An XML parser context * * Does this document has an internal subset */ static void internalSubsetCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, const xmlChar * ExternalID ATTRIBUTE_UNUSED, const xmlChar * SystemID ATTRIBUTE_UNUSED) { callbacks++; return; } /** * externalSubsetCallback: * @ctxt: An XML parser context * * Does this document has an external subset */ static void externalSubsetCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, const xmlChar * ExternalID ATTRIBUTE_UNUSED, const xmlChar * SystemID ATTRIBUTE_UNUSED) { callbacks++; return; } /** * resolveEntityCallback: * @ctxt: An XML parser context * @publicId: The public ID of the entity * @systemId: The system ID of the entity * * Special entity resolver, better left to the parser, it has * more context than the application layer. * The default behaviour is to NOT resolve the entities, in that case * the ENTITY_REF nodes are built in the structure (and the parameter * values). * * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour. */ static xmlParserInputPtr resolveEntityCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * publicId ATTRIBUTE_UNUSED, const xmlChar * systemId ATTRIBUTE_UNUSED) { callbacks++; return (NULL); } /** * getEntityCallback: * @ctxt: An XML parser context * @name: The entity name * * Get an entity by name * * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour. */ static xmlEntityPtr getEntityCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED) { callbacks++; return (NULL); } /** * getParameterEntityCallback: * @ctxt: An XML parser context * @name: The entity name * * Get a parameter entity by name * * Returns the xmlParserInputPtr */ static xmlEntityPtr getParameterEntityCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED) { callbacks++; return (NULL); } /** * entityDeclCallback: * @ctxt: An XML parser context * @name: the entity name * @type: the entity type * @publicId: The public ID of the entity * @systemId: The system ID of the entity * @content: the entity value (without processing). * * An entity definition has been parsed */ static void entityDeclCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, int type ATTRIBUTE_UNUSED, const xmlChar * publicId ATTRIBUTE_UNUSED, const xmlChar * systemId ATTRIBUTE_UNUSED, xmlChar * content ATTRIBUTE_UNUSED) { callbacks++; return; } /** * attributeDeclCallback: * @ctxt: An XML parser context * @name: the attribute name * @type: the attribute type * * An attribute definition has been parsed */ static void attributeDeclCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * elem ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, int type ATTRIBUTE_UNUSED, int def ATTRIBUTE_UNUSED, const xmlChar * defaultValue ATTRIBUTE_UNUSED, xmlEnumerationPtr tree ATTRIBUTE_UNUSED) { callbacks++; return; } /** * elementDeclCallback: * @ctxt: An XML parser context * @name: the element name * @type: the element type * @content: the element value (without processing). * * An element definition has been parsed */ static void elementDeclCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, int type ATTRIBUTE_UNUSED, xmlElementContentPtr content ATTRIBUTE_UNUSED) { callbacks++; return; } /** * notationDeclCallback: * @ctxt: An XML parser context * @name: The name of the notation * @publicId: The public ID of the entity * @systemId: The system ID of the entity * * What to do when a notation declaration has been parsed. */ static void notationDeclCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, const xmlChar * publicId ATTRIBUTE_UNUSED, const xmlChar * systemId ATTRIBUTE_UNUSED) { callbacks++; return; } /** * unparsedEntityDeclCallback: * @ctxt: An XML parser context * @name: The name of the entity * @publicId: The public ID of the entity * @systemId: The system ID of the entity * @notationName: the name of the notation * * What to do when an unparsed entity declaration is parsed */ static void unparsedEntityDeclCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, const xmlChar * publicId ATTRIBUTE_UNUSED, const xmlChar * systemId ATTRIBUTE_UNUSED, const xmlChar * notationName ATTRIBUTE_UNUSED) { callbacks++; return; } /** * setDocumentLocatorCallback: * @ctxt: An XML parser context * @loc: A SAX Locator * * Receive the document locator at startup, actually xmlDefaultSAXLocator * Everything is available on the context, so this is useless in our case. */ static void setDocumentLocatorCallback(void *ctx ATTRIBUTE_UNUSED, xmlSAXLocatorPtr loc ATTRIBUTE_UNUSED) { callbacks++; return; } /** * startDocumentCallback: * @ctxt: An XML parser context * * called when the document start being processed. */ static void startDocumentCallback(void *ctx ATTRIBUTE_UNUSED) { callbacks++; return; } /** * endDocumentCallback: * @ctxt: An XML parser context * * called when the document end has been detected. */ static void endDocumentCallback(void *ctx ATTRIBUTE_UNUSED) { callbacks++; return; } #if 0 /** * startElementCallback: * @ctxt: An XML parser context * @name: The element name * * called when an opening tag has been processed. */ static void startElementCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED, const xmlChar ** atts ATTRIBUTE_UNUSED) { callbacks++; return; } /** * endElementCallback: * @ctxt: An XML parser context * @name: The element name * * called when the end of an element has been detected. */ static void endElementCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED) { callbacks++; return; } #endif /** * charactersCallback: * @ctxt: An XML parser context * @ch: a xmlChar string * @len: the number of xmlChar * * receiving some chars from the parser. * Question: how much at a time ??? */ static void charactersCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * ch ATTRIBUTE_UNUSED, int len ATTRIBUTE_UNUSED) { callbacks++; return; } /** * referenceCallback: * @ctxt: An XML parser context * @name: The entity name * * called when an entity reference is detected. */ static void referenceCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * name ATTRIBUTE_UNUSED) { callbacks++; return; } /** * ignorableWhitespaceCallback: * @ctxt: An XML parser context * @ch: a xmlChar string * @start: the first char in the string * @len: the number of xmlChar * * receiving some ignorable whitespaces from the parser. * Question: how much at a time ??? */ static void ignorableWhitespaceCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * ch ATTRIBUTE_UNUSED, int len ATTRIBUTE_UNUSED) { callbacks++; return; } /** * processingInstructionCallback: * @ctxt: An XML parser context * @target: the target name * @data: the PI data's * @len: the number of xmlChar * * A processing instruction has been parsed. */ static void processingInstructionCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * target ATTRIBUTE_UNUSED, const xmlChar * data ATTRIBUTE_UNUSED) { callbacks++; return; } /** * cdataBlockCallback: * @ctx: the user data (XML parser context) * @value: The pcdata content * @len: the block length * * called when a pcdata block has been parsed */ static void cdataBlockCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * value ATTRIBUTE_UNUSED, int len ATTRIBUTE_UNUSED) { callbacks++; return; } /** * commentCallback: * @ctxt: An XML parser context * @value: the comment content * * A comment has been parsed. */ static void commentCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * value ATTRIBUTE_UNUSED) { callbacks++; return; } /** * warningCallback: * @ctxt: An XML parser context * @msg: the message to display/transmit * @...: extra parameters for the message display * * Display and format a warning messages, gives file, line, position and * extra parameters. */ static void XMLCDECL warningCallback(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED, ...) { callbacks++; return; } /** * errorCallback: * @ctxt: An XML parser context * @msg: the message to display/transmit * @...: extra parameters for the message display * * Display and format a error messages, gives file, line, position and * extra parameters. */ static void XMLCDECL errorCallback(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED, ...) { callbacks++; return; } /** * fatalErrorCallback: * @ctxt: An XML parser context * @msg: the message to display/transmit * @...: extra parameters for the message display * * Display and format a fatalError messages, gives file, line, position and * extra parameters. */ static void XMLCDECL fatalErrorCallback(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED, ...) { return; } /* * SAX2 specific callbacks */ /** * startElementNsCallback: * @ctxt: An XML parser context * @name: The element name * * called when an opening tag has been processed. */ static void startElementNsCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * localname ATTRIBUTE_UNUSED, const xmlChar * prefix ATTRIBUTE_UNUSED, const xmlChar * URI ATTRIBUTE_UNUSED, int nb_namespaces ATTRIBUTE_UNUSED, const xmlChar ** namespaces ATTRIBUTE_UNUSED, int nb_attributes ATTRIBUTE_UNUSED, int nb_defaulted ATTRIBUTE_UNUSED, const xmlChar ** attributes ATTRIBUTE_UNUSED) { callbacks++; return; } /** * endElementCallback: * @ctxt: An XML parser context * @name: The element name * * called when the end of an element has been detected. */ static void endElementNsCallback(void *ctx ATTRIBUTE_UNUSED, const xmlChar * localname ATTRIBUTE_UNUSED, const xmlChar * prefix ATTRIBUTE_UNUSED, const xmlChar * URI ATTRIBUTE_UNUSED) { callbacks++; return; } static xmlSAXHandler callbackSAX2HandlerStruct = { internalSubsetCallback, isStandaloneCallback, hasInternalSubsetCallback, hasExternalSubsetCallback, resolveEntityCallback, getEntityCallback, entityDeclCallback, notationDeclCallback, attributeDeclCallback, elementDeclCallback, unparsedEntityDeclCallback, setDocumentLocatorCallback, startDocumentCallback, endDocumentCallback, NULL, NULL, referenceCallback, charactersCallback, ignorableWhitespaceCallback, processingInstructionCallback, commentCallback, warningCallback, errorCallback, fatalErrorCallback, getParameterEntityCallback, cdataBlockCallback, externalSubsetCallback, XML_SAX2_MAGIC, NULL, startElementNsCallback, endElementNsCallback, NULL }; static xmlSAXHandlerPtr callbackSAX2Handler = &callbackSAX2HandlerStruct; /************************************************************************ * * * The tests front-ends * * * ************************************************************************/ /** * readerTest: * @filename: the file to parse * @max_size: size of the limit to test * @options: parsing options * @fail: should a failure be reported * * Parse a memory generated file using SAX * * Returns 0 in case of success, an error code otherwise */ static int saxTest(const char *filename, size_t limit, int options, int fail) { int res = 0; xmlParserCtxtPtr ctxt; xmlDocPtr doc; xmlSAXHandlerPtr old_sax; nb_tests++; maxlen = limit; ctxt = xmlNewParserCtxt(); if (ctxt == NULL) { fprintf(stderr, "Failed to create parser context\n"); return(1); } old_sax = ctxt->sax; ctxt->sax = callbackSAX2Handler; ctxt->userData = NULL; doc = xmlCtxtReadFile(ctxt, filename, NULL, options); if (doc != NULL) { fprintf(stderr, "SAX parsing generated a document !\n"); xmlFreeDoc(doc); res = 0; } else if (ctxt->wellFormed == 0) { if (fail) res = 0; else { fprintf(stderr, "Failed to parse '%s' %lu\n", filename, (unsigned long) limit); res = 1; } } else { if (fail) { fprintf(stderr, "Failed to get failure for '%s' %lu\n", filename, (unsigned long) limit); res = 1; } else res = 0; } ctxt->sax = old_sax; xmlFreeParserCtxt(ctxt); return(res); } #ifdef LIBXML_READER_ENABLED /** * readerTest: * @filename: the file to parse * @max_size: size of the limit to test * @options: parsing options * @fail: should a failure be reported * * Parse a memory generated file using the xmlReader * * Returns 0 in case of success, an error code otherwise */ static int readerTest(const char *filename, size_t limit, int options, int fail) { xmlTextReaderPtr reader; int res = 0; int ret; nb_tests++; maxlen = limit; reader = xmlReaderForFile(filename , NULL, options); if (reader == NULL) { fprintf(stderr, "Failed to open '%s' test\n", filename); return(1); } ret = xmlTextReaderRead(reader); while (ret == 1) { ret = xmlTextReaderRead(reader); } if (ret != 0) { if (fail) res = 0; else { if (strncmp(filename, "crazy:", 6) == 0) fprintf(stderr, "Failed to parse '%s' %u\n", filename, crazy_indx); else fprintf(stderr, "Failed to parse '%s' %lu\n", filename, (unsigned long) limit); res = 1; } } else { if (fail) { if (strncmp(filename, "crazy:", 6) == 0) fprintf(stderr, "Failed to get failure for '%s' %u\n", filename, crazy_indx); else fprintf(stderr, "Failed to get failure for '%s' %lu\n", filename, (unsigned long) limit); res = 1; } else res = 0; } if (timeout) res = 1; xmlFreeTextReader(reader); return(res); } #endif /************************************************************************ * * * Tests descriptions * * * ************************************************************************/ typedef int (*functest) (const char *filename, size_t limit, int options, int fail); typedef struct limitDesc limitDesc; typedef limitDesc *limitDescPtr; struct limitDesc { const char *name; /* the huge generator name */ size_t limit; /* the limit to test */ int options; /* extra parser options */ int fail; /* whether the test should fail */ }; static limitDesc limitDescriptions[] = { /* max length of a text node in content */ {"huge:textNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, {"huge:textNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, {"huge:textNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, /* max length of a text node in content */ {"huge:attrNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, {"huge:attrNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, {"huge:attrNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, /* max length of a comment node */ {"huge:commentNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, {"huge:commentNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, {"huge:commentNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, /* max length of a PI node */ {"huge:piNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, {"huge:piNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, {"huge:piNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, }; typedef struct testDesc testDesc; typedef testDesc *testDescPtr; struct testDesc { const char *desc; /* descripton of the test */ functest func; /* function implementing the test */ }; static testDesc testDescriptions[] = { { "Parsing of huge files with the sax parser", saxTest}, /* { "Parsing of huge files with the tree parser", treeTest}, */ #ifdef LIBXML_READER_ENABLED { "Parsing of huge files with the reader", readerTest}, #endif {NULL, NULL} }; typedef struct testException testException; typedef testException *testExceptionPtr; struct testException { unsigned int test; /* the parser test number */ unsigned int limit; /* the limit test number */ int fail; /* new fail value or -1*/ size_t size; /* new limit value or 0 */ }; static testException testExceptions[] = { /* the SAX parser doesn't hit a limit of XML_MAX_TEXT_LENGTH text nodes */ { 0, 1, 0, 0}, }; static int launchTests(testDescPtr tst, unsigned int test) { int res = 0, err = 0; unsigned int i, j; size_t limit; int fail; if (tst == NULL) return(-1); for (i = 0;i < sizeof(limitDescriptions)/sizeof(limitDescriptions[0]);i++) { limit = limitDescriptions[i].limit; fail = limitDescriptions[i].fail; /* * Handle exceptions if any */ for (j = 0;j < sizeof(testExceptions)/sizeof(testExceptions[0]);j++) { if ((testExceptions[j].test == test) && (testExceptions[j].limit == i)) { if (testExceptions[j].fail != -1) fail = testExceptions[j].fail; if (testExceptions[j].size != 0) limit = testExceptions[j].size; break; } } res = tst->func(limitDescriptions[i].name, limit, limitDescriptions[i].options, fail); if (res != 0) { nb_errors++; err++; } } return(err); } static int runtest(unsigned int i) { int ret = 0, res; int old_errors, old_tests, old_leaks; old_errors = nb_errors; old_tests = nb_tests; old_leaks = nb_leaks; if ((tests_quiet == 0) && (testDescriptions[i].desc != NULL)) printf("## %s\n", testDescriptions[i].desc); res = launchTests(&testDescriptions[i], i); if (res != 0) ret++; if (verbose) { if ((nb_errors == old_errors) && (nb_leaks == old_leaks)) printf("Ran %d tests, no errors\n", nb_tests - old_tests); else printf("Ran %d tests, %d errors, %d leaks\n", nb_tests - old_tests, nb_errors - old_errors, nb_leaks - old_leaks); } return(ret); } static int launchCrazySAX(unsigned int test, int fail) { int res = 0, err = 0; crazy_indx = test; res = saxTest("crazy::test", XML_MAX_LOOKUP_LIMIT - CHUNK, 0, fail); if (res != 0) { nb_errors++; err++; } if (tests_quiet == 0) fprintf(stderr, "%c", crazy[test]); return(err); } #ifdef LIBXML_READER_ENABLED static int launchCrazy(unsigned int test, int fail) { int res = 0, err = 0; crazy_indx = test; res = readerTest("crazy::test", XML_MAX_LOOKUP_LIMIT - CHUNK, 0, fail); if (res != 0) { nb_errors++; err++; } if (tests_quiet == 0) fprintf(stderr, "%c", crazy[test]); return(err); } #endif static int get_crazy_fail(int test) { /* * adding 1000000 of character 'a' leads to parser failure mostly * everywhere except in those special spots. Need to be updated * each time crazy is updated */ int fail = 1; if ((test == 44) || /* PI in Misc */ ((test >= 50) && (test <= 55)) || /* Comment in Misc */ (test == 79) || /* PI in DTD */ ((test >= 85) && (test <= 90)) || /* Comment in DTD */ (test == 154) || /* PI in Misc */ ((test >= 160) && (test <= 165)) || /* Comment in Misc */ ((test >= 178) && (test <= 181)) || /* attribute value */ (test == 183) || /* Text */ (test == 189) || /* PI in Content */ (test == 191) || /* Text */ ((test >= 195) && (test <= 200)) || /* Comment in Content */ ((test >= 203) && (test <= 206)) || /* Text */ (test == 215) || (test == 216) || /* in CDATA */ (test == 219) || /* Text */ (test == 231) || /* PI in Misc */ ((test >= 237) && (test <= 242))) /* Comment in Misc */ fail = 0; return(fail); } static int runcrazy(void) { int ret = 0, res = 0; int old_errors, old_tests, old_leaks; unsigned int i; old_errors = nb_errors; old_tests = nb_tests; old_leaks = nb_leaks; #ifdef LIBXML_READER_ENABLED if (tests_quiet == 0) { printf("## Crazy tests on reader\n"); } for (i = 0;i < strlen(crazy);i++) { res += launchCrazy(i, get_crazy_fail(i)); if (res != 0) ret++; } #endif if (tests_quiet == 0) { printf("\n## Crazy tests on SAX\n"); } for (i = 0;i < strlen(crazy);i++) { res += launchCrazySAX(i, get_crazy_fail(i)); if (res != 0) ret++; } if (tests_quiet == 0) fprintf(stderr, "\n"); if (verbose) { if ((nb_errors == old_errors) && (nb_leaks == old_leaks)) printf("Ran %d tests, no errors\n", nb_tests - old_tests); else printf("Ran %d tests, %d errors, %d leaks\n", nb_tests - old_tests, nb_errors - old_errors, nb_leaks - old_leaks); } return(ret); } int main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) { int i, a, ret = 0; int subset = 0; fillFilling(); initializeLibxml2(); for (a = 1; a < argc;a++) { if (!strcmp(argv[a], "-v")) verbose = 1; else if (!strcmp(argv[a], "-quiet")) tests_quiet = 1; else if (!strcmp(argv[a], "-crazy")) subset = 1; } if (subset == 0) { for (i = 0; testDescriptions[i].func != NULL; i++) { ret += runtest(i); } } ret += runcrazy(); if ((nb_errors == 0) && (nb_leaks == 0)) { ret = 0; printf("Total %d tests, no errors\n", nb_tests); } else { ret = 1; printf("Total %d tests, %d errors, %d leaks\n", nb_tests, nb_errors, nb_leaks); } xmlCleanupParser(); xmlMemoryDump(); return(ret); }