/* * xinclude.c : Code to implement XInclude processing * * World Wide Web Consortium W3C Last Call Working Draft 10 November 2003 * http://www.w3.org/TR/2003/WD-xinclude-20031110 * * See Copyright for the status of this software. * * daniel@veillard.com */ #define IN_LIBXML #include "libxml.h" #include #include #include #include #include #include #include #include #include #include #ifdef LIBXML_XINCLUDE_ENABLED #include #include "private/buf.h" #include "private/error.h" #include "private/tree.h" #include "private/xinclude.h" #define XINCLUDE_MAX_DEPTH 40 /************************************************************************ * * * XInclude context handling * * * ************************************************************************/ /* * An XInclude context */ typedef xmlChar *xmlURL; typedef struct _xmlXIncludeRef xmlXIncludeRef; typedef xmlXIncludeRef *xmlXIncludeRefPtr; struct _xmlXIncludeRef { xmlChar *URI; /* the fully resolved resource URL */ xmlChar *fragment; /* the fragment in the URI */ xmlChar *base; /* base URI of xi:include element */ xmlNodePtr elem; /* the xi:include element */ xmlNodePtr inc; /* the included copy */ int xml; /* xml or txt */ int fallback; /* fallback was loaded */ int expanding; /* flag to detect inclusion loops */ int replace; /* should the node be replaced? */ }; typedef struct _xmlXIncludeDoc xmlXIncludeDoc; typedef xmlXIncludeDoc *xmlXIncludeDocPtr; struct _xmlXIncludeDoc { xmlDocPtr doc; /* the parsed document */ xmlChar *url; /* the URL */ int expanding; /* flag to detect inclusion loops */ }; typedef struct _xmlXIncludeTxt xmlXIncludeTxt; typedef xmlXIncludeTxt *xmlXIncludeTxtPtr; struct _xmlXIncludeTxt { xmlChar *text; /* text string */ xmlChar *url; /* the URL */ }; struct _xmlXIncludeCtxt { xmlDocPtr doc; /* the source document */ int incNr; /* number of includes */ int incMax; /* size of includes tab */ xmlXIncludeRefPtr *incTab; /* array of included references */ int txtNr; /* number of unparsed documents */ int txtMax; /* size of unparsed documents tab */ xmlXIncludeTxt *txtTab; /* array of unparsed documents */ int urlNr; /* number of documents stacked */ int urlMax; /* size of document stack */ xmlXIncludeDoc *urlTab; /* document stack */ int nbErrors; /* the number of errors detected */ int fatalErr; /* abort processing */ int errNo; /* error code */ int legacy; /* using XINCLUDE_OLD_NS */ int parseFlags; /* the flags used for parsing XML documents */ void *_private; /* application data */ #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION unsigned long incTotal; /* total number of processed inclusions */ #endif int depth; /* recursion depth */ int isStream; /* streaming mode */ xmlXPathContextPtr xpctxt; xmlStructuredErrorFunc errorHandler; void *errorCtxt; }; static xmlXIncludeRefPtr xmlXIncludeExpandNode(xmlXIncludeCtxtPtr ctxt, xmlNodePtr node); static int xmlXIncludeLoadNode(xmlXIncludeCtxtPtr ctxt, xmlXIncludeRefPtr ref); static int xmlXIncludeDoProcess(xmlXIncludeCtxtPtr ctxt, xmlNodePtr tree); /************************************************************************ * * * XInclude error handler * * * ************************************************************************/ /** * xmlXIncludeErrMemory: * @extra: extra information * * Handle an out of memory condition */ static void xmlXIncludeErrMemory(xmlXIncludeCtxtPtr ctxt) { ctxt->errNo = XML_ERR_NO_MEMORY; ctxt->fatalErr = 1; ctxt->nbErrors++; xmlRaiseMemoryError(ctxt->errorHandler, NULL, ctxt->errorCtxt, XML_FROM_XINCLUDE, NULL); } /** * xmlXIncludeErr: * @ctxt: the XInclude context * @node: the context node * @msg: the error message * @extra: extra information * * Handle an XInclude error */ static void LIBXML_ATTR_FORMAT(4,0) xmlXIncludeErr(xmlXIncludeCtxtPtr ctxt, xmlNodePtr node, int error, const char *msg, const xmlChar *extra) { xmlStructuredErrorFunc schannel = NULL; xmlGenericErrorFunc channel = NULL; void *data = NULL; int res; if (ctxt->fatalErr != 0) return; ctxt->nbErrors++; schannel = ctxt->errorHandler; data = ctxt->errorCtxt; if (schannel == NULL) { channel = xmlGenericError; data = xmlGenericErrorContext; } res = __xmlRaiseError(schannel, channel, data, ctxt, node, XML_FROM_XINCLUDE, error, XML_ERR_ERROR, NULL, 0, (const char *) extra, NULL, NULL, 0, 0, msg, (const char *) extra); if (res < 0) { ctxt->errNo = XML_ERR_NO_MEMORY; ctxt->fatalErr = 1; } else { ctxt->errNo = error; } } /** * xmlXIncludeGetProp: * @ctxt: the XInclude context * @cur: the node * @name: the attribute name * * Get an XInclude attribute * * Returns the value (to be freed) or NULL if not found */ static xmlChar * xmlXIncludeGetProp(xmlXIncludeCtxtPtr ctxt, xmlNodePtr cur, const xmlChar *name) { xmlChar *ret; if (xmlNodeGetAttrValue(cur, name, XINCLUDE_NS, &ret) < 0) xmlXIncludeErrMemory(ctxt); if (ret != NULL) return(ret); if (ctxt->legacy != 0) { if (xmlNodeGetAttrValue(cur, name, XINCLUDE_OLD_NS, &ret) < 0) xmlXIncludeErrMemory(ctxt); if (ret != NULL) return(ret); } if (xmlNodeGetAttrValue(cur, name, NULL, &ret) < 0) xmlXIncludeErrMemory(ctxt); return(ret); } /** * xmlXIncludeFreeRef: * @ref: the XInclude reference * * Free an XInclude reference */ static void xmlXIncludeFreeRef(xmlXIncludeRefPtr ref) { if (ref == NULL) return; if (ref->URI != NULL) xmlFree(ref->URI); if (ref->fragment != NULL) xmlFree(ref->fragment); if (ref->base != NULL) xmlFree(ref->base); xmlFree(ref); } /** * xmlXIncludeNewContext: * @doc: an XML Document * * Creates a new XInclude context * * Returns the new set */ xmlXIncludeCtxtPtr xmlXIncludeNewContext(xmlDocPtr doc) { xmlXIncludeCtxtPtr ret; if (doc == NULL) return(NULL); ret = (xmlXIncludeCtxtPtr) xmlMalloc(sizeof(xmlXIncludeCtxt)); if (ret == NULL) return(NULL); memset(ret, 0, sizeof(xmlXIncludeCtxt)); ret->doc = doc; ret->incNr = 0; ret->incMax = 0; ret->incTab = NULL; ret->nbErrors = 0; return(ret); } /** * xmlXIncludeFreeContext: * @ctxt: the XInclude context * * Free an XInclude context */ void xmlXIncludeFreeContext(xmlXIncludeCtxtPtr ctxt) { int i; if (ctxt == NULL) return; if (ctxt->urlTab != NULL) { for (i = 0; i < ctxt->urlNr; i++) { xmlFreeDoc(ctxt->urlTab[i].doc); xmlFree(ctxt->urlTab[i].url); } xmlFree(ctxt->urlTab); } for (i = 0;i < ctxt->incNr;i++) { if (ctxt->incTab[i] != NULL) xmlXIncludeFreeRef(ctxt->incTab[i]); } if (ctxt->incTab != NULL) xmlFree(ctxt->incTab); if (ctxt->txtTab != NULL) { for (i = 0;i < ctxt->txtNr;i++) { xmlFree(ctxt->txtTab[i].text); xmlFree(ctxt->txtTab[i].url); } xmlFree(ctxt->txtTab); } if (ctxt->xpctxt != NULL) xmlXPathFreeContext(ctxt->xpctxt); xmlFree(ctxt); } /** * xmlXIncludeParseFile: * @ctxt: the XInclude context * @URL: the URL or file path * * parse a document for XInclude */ static xmlDocPtr xmlXIncludeParseFile(xmlXIncludeCtxtPtr ctxt, const char *URL) { xmlDocPtr ret = NULL; xmlParserCtxtPtr pctxt; xmlParserInputPtr inputStream; xmlInitParser(); pctxt = xmlNewParserCtxt(); if (pctxt == NULL) { xmlXIncludeErrMemory(ctxt); return(NULL); } if (ctxt->errorHandler != NULL) xmlCtxtSetErrorHandler(pctxt, ctxt->errorHandler, ctxt->errorCtxt); /* * pass in the application data to the parser context. */ pctxt->_private = ctxt->_private; /* * try to ensure that new documents included are actually * built with the same dictionary as the including document. */ if ((ctxt->doc != NULL) && (ctxt->doc->dict != NULL)) { if (pctxt->dict != NULL) xmlDictFree(pctxt->dict); pctxt->dict = ctxt->doc->dict; xmlDictReference(pctxt->dict); } xmlCtxtUseOptions(pctxt, ctxt->parseFlags); inputStream = xmlLoadExternalEntity(URL, NULL, pctxt); if (inputStream == NULL) goto error; inputPush(pctxt, inputStream); xmlParseDocument(pctxt); if (pctxt->wellFormed) { ret = pctxt->myDoc; } else { ret = NULL; if (pctxt->myDoc != NULL) xmlFreeDoc(pctxt->myDoc); pctxt->myDoc = NULL; } error: if (pctxt->errNo == XML_ERR_NO_MEMORY) xmlXIncludeErrMemory(ctxt); xmlFreeParserCtxt(pctxt); return(ret); } /** * xmlXIncludeAddNode: * @ctxt: the XInclude context * @cur: the new node * * Add a new node to process to an XInclude context */ static xmlXIncludeRefPtr xmlXIncludeAddNode(xmlXIncludeCtxtPtr ctxt, xmlNodePtr cur) { xmlXIncludeRefPtr ref = NULL; xmlXIncludeRefPtr ret = NULL; xmlURIPtr uri = NULL; xmlChar *href = NULL; xmlChar *parse = NULL; xmlChar *fragment = NULL; xmlChar *base = NULL; xmlChar *tmp; int xml = 1; int local = 0; int res; if (ctxt == NULL) return(NULL); if (cur == NULL) return(NULL); /* * read the attributes */ fragment = xmlXIncludeGetProp(ctxt, cur, XINCLUDE_PARSE_XPOINTER); href = xmlXIncludeGetProp(ctxt, cur, XINCLUDE_HREF); if (href == NULL) { if (fragment == NULL) { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_NO_HREF, "href or xpointer must be present\n", parse); goto error; } href = xmlStrdup(BAD_CAST ""); /* @@@@ href is now optional */ if (href == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } } parse = xmlXIncludeGetProp(ctxt, cur, XINCLUDE_PARSE); if (parse != NULL) { if (xmlStrEqual(parse, XINCLUDE_PARSE_XML)) xml = 1; else if (xmlStrEqual(parse, XINCLUDE_PARSE_TEXT)) xml = 0; else { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_PARSE_VALUE, "invalid value %s for 'parse'\n", parse); goto error; } } /* * Check the URL and remove any fragment identifier */ res = xmlParseURISafe((const char *)href, &uri); if (uri == NULL) { if (res < 0) xmlXIncludeErrMemory(ctxt); else xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_HREF_URI, "invalid value href %s\n", href); goto error; } if (uri->fragment != NULL) { if (ctxt->legacy != 0) { if (fragment == NULL) { fragment = (xmlChar *) uri->fragment; } else { xmlFree(uri->fragment); } } else { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_FRAGMENT_ID, "Invalid fragment identifier in URI %s use the xpointer attribute\n", href); goto error; } uri->fragment = NULL; } tmp = xmlSaveUri(uri); if (tmp == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } xmlFree(href); href = tmp; /* * Resolve URI */ if (xmlNodeGetBaseSafe(ctxt->doc, cur, &base) < 0) { xmlXIncludeErrMemory(ctxt); goto error; } if (href[0] != 0) { if (xmlBuildURISafe(href, base, &tmp) < 0) { xmlXIncludeErrMemory(ctxt); goto error; } if (tmp == NULL) { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_HREF_URI, "failed build URL\n", NULL); goto error; } xmlFree(href); href = tmp; if (xmlStrEqual(href, ctxt->doc->URL)) local = 1; } else { local = 1; } /* * If local and xml then we need a fragment */ if ((local == 1) && (xml == 1) && ((fragment == NULL) || (fragment[0] == 0))) { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_RECURSION, "detected a local recursion with no xpointer in %s\n", href); goto error; } ref = (xmlXIncludeRefPtr) xmlMalloc(sizeof(xmlXIncludeRef)); if (ref == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } memset(ref, 0, sizeof(xmlXIncludeRef)); ref->elem = cur; ref->xml = xml; ref->URI = href; href = NULL; ref->fragment = fragment; fragment = NULL; /* * xml:base fixup */ if (((ctxt->parseFlags & XML_PARSE_NOBASEFIX) == 0) && (cur->doc != NULL) && ((cur->doc->parseFlags & XML_PARSE_NOBASEFIX) == 0)) { if (base != NULL) { ref->base = base; base = NULL; } else { ref->base = xmlStrdup(BAD_CAST ""); if (ref->base == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } } } if (ctxt->incNr >= ctxt->incMax) { xmlXIncludeRefPtr *table; #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION size_t newSize = ctxt->incMax ? ctxt->incMax * 2 : 1; #else size_t newSize = ctxt->incMax ? ctxt->incMax * 2 : 4; #endif table = (xmlXIncludeRefPtr *) xmlRealloc(ctxt->incTab, newSize * sizeof(ctxt->incTab[0])); if (table == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } ctxt->incTab = table; ctxt->incMax = newSize; } ctxt->incTab[ctxt->incNr++] = ref; ret = ref; ref = NULL; error: xmlXIncludeFreeRef(ref); xmlFreeURI(uri); xmlFree(href); xmlFree(parse); xmlFree(fragment); xmlFree(base); return(ret); } /** * xmlXIncludeRecurseDoc: * @ctxt: the XInclude context * @doc: the new document * @url: the associated URL * * The XInclude recursive nature is handled at this point. */ static void xmlXIncludeRecurseDoc(xmlXIncludeCtxtPtr ctxt, xmlDocPtr doc) { xmlDocPtr oldDoc; xmlXIncludeRefPtr *oldIncTab; int oldIncMax, oldIncNr, oldIsStream; int i; oldDoc = ctxt->doc; oldIncMax = ctxt->incMax; oldIncNr = ctxt->incNr; oldIncTab = ctxt->incTab; oldIsStream = ctxt->isStream; ctxt->doc = doc; ctxt->incMax = 0; ctxt->incNr = 0; ctxt->incTab = NULL; ctxt->isStream = 0; xmlXIncludeDoProcess(ctxt, xmlDocGetRootElement(doc)); if (ctxt->incTab != NULL) { for (i = 0; i < ctxt->incNr; i++) xmlXIncludeFreeRef(ctxt->incTab[i]); xmlFree(ctxt->incTab); } ctxt->doc = oldDoc; ctxt->incMax = oldIncMax; ctxt->incNr = oldIncNr; ctxt->incTab = oldIncTab; ctxt->isStream = oldIsStream; } /************************************************************************ * * * Node copy with specific semantic * * * ************************************************************************/ static void xmlXIncludeBaseFixup(xmlXIncludeCtxtPtr ctxt, xmlNodePtr cur, xmlNodePtr copy, const xmlChar *targetBase) { xmlChar *base = NULL; xmlChar *relBase = NULL; xmlNs ns; int res; if (cur->type != XML_ELEMENT_NODE) return; if (xmlNodeGetBaseSafe(cur->doc, cur, &base) < 0) xmlXIncludeErrMemory(ctxt); if ((base != NULL) && !xmlStrEqual(base, targetBase)) { if (xmlBuildRelativeURISafe(base, targetBase, &relBase) < 0) { xmlXIncludeErrMemory(ctxt); goto done; } if (relBase == NULL) { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_HREF_URI, "Building relative URI failed: %s\n", base); goto done; } /* * If the new base doesn't contain a slash, it can be omitted. */ if (xmlStrchr(relBase, '/') != NULL) { res = xmlNodeSetBase(copy, relBase); if (res < 0) xmlXIncludeErrMemory(ctxt); goto done; } } /* * Delete existing xml:base if bases are equal */ memset(&ns, 0, sizeof(ns)); ns.href = XML_XML_NAMESPACE; xmlUnsetNsProp(copy, &ns, BAD_CAST "base"); done: xmlFree(base); xmlFree(relBase); } /** * xmlXIncludeCopyNode: * @ctxt: the XInclude context * @elem: the element * @copyChildren: copy children instead of node if true * * Make a copy of the node while expanding nested XIncludes. * * Returns a node list, not a single node. */ static xmlNodePtr xmlXIncludeCopyNode(xmlXIncludeCtxtPtr ctxt, xmlNodePtr elem, int copyChildren, const xmlChar *targetBase) { xmlNodePtr result = NULL; xmlNodePtr insertParent = NULL; xmlNodePtr insertLast = NULL; xmlNodePtr cur; xmlNodePtr item; int depth = 0; if (copyChildren) { cur = elem->children; if (cur == NULL) return(NULL); } else { cur = elem; } while (1) { xmlNodePtr copy = NULL; int recurse = 0; if ((cur->type == XML_DOCUMENT_NODE) || (cur->type == XML_DTD_NODE)) { ; } else if ((cur->type == XML_ELEMENT_NODE) && (cur->ns != NULL) && (xmlStrEqual(cur->name, XINCLUDE_NODE)) && ((xmlStrEqual(cur->ns->href, XINCLUDE_NS)) || (xmlStrEqual(cur->ns->href, XINCLUDE_OLD_NS)))) { xmlXIncludeRefPtr ref = xmlXIncludeExpandNode(ctxt, cur); if (ref == NULL) goto error; /* * TODO: Insert XML_XINCLUDE_START and XML_XINCLUDE_END nodes */ for (item = ref->inc; item != NULL; item = item->next) { copy = xmlStaticCopyNode(item, ctxt->doc, insertParent, 1); if (copy == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } if (result == NULL) result = copy; if (insertLast != NULL) { insertLast->next = copy; copy->prev = insertLast; } else if (insertParent != NULL) { insertParent->children = copy; } insertLast = copy; if ((depth == 0) && (targetBase != NULL)) xmlXIncludeBaseFixup(ctxt, item, copy, targetBase); } } else { copy = xmlStaticCopyNode(cur, ctxt->doc, insertParent, 2); if (copy == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } if (result == NULL) result = copy; if (insertLast != NULL) { insertLast->next = copy; copy->prev = insertLast; } else if (insertParent != NULL) { insertParent->children = copy; } insertLast = copy; if ((depth == 0) && (targetBase != NULL)) xmlXIncludeBaseFixup(ctxt, cur, copy, targetBase); recurse = (cur->type != XML_ENTITY_REF_NODE) && (cur->children != NULL); } if (recurse) { cur = cur->children; insertParent = insertLast; insertLast = NULL; depth += 1; continue; } if (cur == elem) return(result); while (cur->next == NULL) { if (insertParent != NULL) insertParent->last = insertLast; cur = cur->parent; if (cur == elem) return(result); insertLast = insertParent; insertParent = insertParent->parent; depth -= 1; } cur = cur->next; } error: xmlFreeNodeList(result); return(NULL); } #ifdef LIBXML_XPTR_LOCS_ENABLED /** * xmlXIncludeGetNthChild: * @cur: the node * @no: the child number * * Returns the @n'th element child of @cur or NULL */ static xmlNodePtr xmlXIncludeGetNthChild(xmlNodePtr cur, int no) { int i; if ((cur == NULL) || (cur->type == XML_NAMESPACE_DECL)) return(NULL); cur = cur->children; for (i = 0;i <= no;cur = cur->next) { if (cur == NULL) return(cur); if ((cur->type == XML_ELEMENT_NODE) || (cur->type == XML_DOCUMENT_NODE) || (cur->type == XML_HTML_DOCUMENT_NODE)) { i++; if (i == no) break; } } return(cur); } xmlNodePtr xmlXPtrAdvanceNode(xmlNodePtr cur, int *level); /* in xpointer.c */ /** * xmlXIncludeCopyRange: * @ctxt: the XInclude context * @obj: the XPointer result from the evaluation. * * Build a node list tree copy of the XPointer result. * * Returns an xmlNodePtr list or NULL. * The caller has to free the node tree. */ static xmlNodePtr xmlXIncludeCopyRange(xmlXIncludeCtxtPtr ctxt, xmlXPathObjectPtr range) { /* pointers to generated nodes */ xmlNodePtr list = NULL, last = NULL, listParent = NULL; xmlNodePtr tmp, tmp2; /* pointers to traversal nodes */ xmlNodePtr start, cur, end; int index1, index2; int level = 0, lastLevel = 0, endLevel = 0, endFlag = 0; if ((ctxt == NULL) || (range == NULL)) return(NULL); if (range->type != XPATH_RANGE) return(NULL); start = (xmlNodePtr) range->user; if ((start == NULL) || (start->type == XML_NAMESPACE_DECL)) return(NULL); end = range->user2; if (end == NULL) return(xmlDocCopyNode(start, ctxt->doc, 1)); if (end->type == XML_NAMESPACE_DECL) return(NULL); cur = start; index1 = range->index; index2 = range->index2; /* * level is depth of the current node under consideration * list is the pointer to the root of the output tree * listParent is a pointer to the parent of output tree (within the included file) in case we need to add another level * last is a pointer to the last node added to the output tree * lastLevel is the depth of last (relative to the root) */ while (cur != NULL) { /* * Check if our output tree needs a parent */ if (level < 0) { while (level < 0) { /* copy must include namespaces and properties */ tmp2 = xmlDocCopyNode(listParent, ctxt->doc, 2); xmlAddChild(tmp2, list); list = tmp2; listParent = listParent->parent; level++; } last = list; lastLevel = 0; } /* * Check whether we need to change our insertion point */ while (level < lastLevel) { last = last->parent; lastLevel --; } if (cur == end) { /* Are we at the end of the range? */ if (cur->type == XML_TEXT_NODE) { const xmlChar *content = cur->content; int len; if (content == NULL) { tmp = xmlNewDocTextLen(ctxt->doc, NULL, 0); } else { len = index2; if ((cur == start) && (index1 > 1)) { content += (index1 - 1); len -= (index1 - 1); } else { len = index2; } tmp = xmlNewDocTextLen(ctxt->doc, content, len); } /* single sub text node selection */ if (list == NULL) return(tmp); /* prune and return full set */ if (level == lastLevel) xmlAddNextSibling(last, tmp); else xmlAddChild(last, tmp); return(list); } else { /* ending node not a text node */ endLevel = level; /* remember the level of the end node */ endFlag = 1; /* last node - need to take care of properties + namespaces */ tmp = xmlDocCopyNode(cur, ctxt->doc, 2); if (list == NULL) { list = tmp; listParent = cur->parent; last = tmp; } else { if (level == lastLevel) last = xmlAddNextSibling(last, tmp); else { last = xmlAddChild(last, tmp); lastLevel = level; } } if (index2 > 1) { end = xmlXIncludeGetNthChild(cur, index2 - 1); index2 = 0; } if ((cur == start) && (index1 > 1)) { cur = xmlXIncludeGetNthChild(cur, index1 - 1); index1 = 0; } else { cur = cur->children; } level++; /* increment level to show change */ /* * Now gather the remaining nodes from cur to end */ continue; /* while */ } } else if (cur == start) { /* Not at the end, are we at start? */ if ((cur->type == XML_TEXT_NODE) || (cur->type == XML_CDATA_SECTION_NODE)) { const xmlChar *content = cur->content; if (content == NULL) { tmp = xmlNewDocTextLen(ctxt->doc, NULL, 0); } else { if (index1 > 1) { content += (index1 - 1); index1 = 0; } tmp = xmlNewDocText(ctxt->doc, content); } last = list = tmp; listParent = cur->parent; } else { /* Not text node */ /* * start of the range - need to take care of * properties and namespaces */ tmp = xmlDocCopyNode(cur, ctxt->doc, 2); list = last = tmp; listParent = cur->parent; if (index1 > 1) { /* Do we need to position? */ cur = xmlXIncludeGetNthChild(cur, index1 - 1); level = lastLevel = 1; index1 = 0; /* * Now gather the remaining nodes from cur to end */ continue; /* while */ } } } else { tmp = NULL; switch (cur->type) { case XML_DTD_NODE: case XML_ELEMENT_DECL: case XML_ATTRIBUTE_DECL: case XML_ENTITY_NODE: /* Do not copy DTD information */ break; case XML_ENTITY_DECL: /* handle crossing entities -> stack needed */ break; case XML_XINCLUDE_START: case XML_XINCLUDE_END: /* don't consider it part of the tree content */ break; case XML_ATTRIBUTE_NODE: /* Humm, should not happen ! */ break; default: /* * Middle of the range - need to take care of * properties and namespaces */ tmp = xmlDocCopyNode(cur, ctxt->doc, 2); break; } if (tmp != NULL) { if (level == lastLevel) last = xmlAddNextSibling(last, tmp); else { last = xmlAddChild(last, tmp); lastLevel = level; } } } /* * Skip to next node in document order */ cur = xmlXPtrAdvanceNode(cur, &level); if (endFlag && (level >= endLevel)) break; } return(list); } #endif /* LIBXML_XPTR_LOCS_ENABLED */ /** * xmlXIncludeCopyXPointer: * @ctxt: the XInclude context * @obj: the XPointer result from the evaluation. * * Build a node list tree copy of the XPointer result. * This will drop Attributes and Namespace declarations. * * Returns an xmlNodePtr list or NULL. * the caller has to free the node tree. */ static xmlNodePtr xmlXIncludeCopyXPointer(xmlXIncludeCtxtPtr ctxt, xmlXPathObjectPtr obj, const xmlChar *targetBase) { xmlNodePtr list = NULL, last = NULL, copy; int i; if ((ctxt == NULL) || (obj == NULL)) return(NULL); switch (obj->type) { case XPATH_NODESET: { xmlNodeSetPtr set = obj->nodesetval; if (set == NULL) break; for (i = 0;i < set->nodeNr;i++) { xmlNodePtr node; if (set->nodeTab[i] == NULL) continue; switch (set->nodeTab[i]->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: node = xmlDocGetRootElement( (xmlDocPtr) set->nodeTab[i]); if (node == NULL) { xmlXIncludeErr(ctxt, set->nodeTab[i], XML_ERR_INTERNAL_ERROR, "document without root\n", NULL); continue; } break; case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_ELEMENT_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: node = set->nodeTab[i]; break; default: xmlXIncludeErr(ctxt, set->nodeTab[i], XML_XINCLUDE_XPTR_RESULT, "invalid node type in XPtr result\n", NULL); continue; /* for */ } /* * OPTIMIZE TODO: External documents should already be * expanded, so xmlDocCopyNode should work as well. * xmlXIncludeCopyNode is only required for the initial * document. */ copy = xmlXIncludeCopyNode(ctxt, node, 0, targetBase); if (copy == NULL) { xmlFreeNodeList(list); return(NULL); } if (last == NULL) { list = copy; } else { while (last->next != NULL) last = last->next; copy->prev = last; last->next = copy; } last = copy; } break; } #ifdef LIBXML_XPTR_LOCS_ENABLED case XPATH_LOCATIONSET: { xmlLocationSetPtr set = (xmlLocationSetPtr) obj->user; if (set == NULL) return(NULL); for (i = 0;i < set->locNr;i++) { if (last == NULL) list = last = xmlXIncludeCopyXPointer(ctxt, set->locTab[i], targetBase); else xmlAddNextSibling(last, xmlXIncludeCopyXPointer(ctxt, set->locTab[i], targetBase)); if (last != NULL) { while (last->next != NULL) last = last->next; } } break; } case XPATH_RANGE: return(xmlXIncludeCopyRange(ctxt, obj)); case XPATH_POINT: /* points are ignored in XInclude */ break; #endif default: break; } return(list); } /************************************************************************ * * * XInclude I/O handling * * * ************************************************************************/ typedef struct _xmlXIncludeMergeData xmlXIncludeMergeData; typedef xmlXIncludeMergeData *xmlXIncludeMergeDataPtr; struct _xmlXIncludeMergeData { xmlDocPtr doc; xmlXIncludeCtxtPtr ctxt; }; /** * xmlXIncludeMergeOneEntity: * @ent: the entity * @doc: the including doc * @name: the entity name * * Implements the merge of one entity */ static void xmlXIncludeMergeEntity(void *payload, void *vdata, const xmlChar *name ATTRIBUTE_UNUSED) { xmlEntityPtr ent = (xmlEntityPtr) payload; xmlXIncludeMergeDataPtr data = (xmlXIncludeMergeDataPtr) vdata; xmlEntityPtr ret, prev; xmlDocPtr doc; xmlXIncludeCtxtPtr ctxt; if ((ent == NULL) || (data == NULL)) return; ctxt = data->ctxt; doc = data->doc; if ((ctxt == NULL) || (doc == NULL)) return; switch (ent->etype) { case XML_INTERNAL_PARAMETER_ENTITY: case XML_EXTERNAL_PARAMETER_ENTITY: case XML_INTERNAL_PREDEFINED_ENTITY: return; case XML_INTERNAL_GENERAL_ENTITY: case XML_EXTERNAL_GENERAL_PARSED_ENTITY: case XML_EXTERNAL_GENERAL_UNPARSED_ENTITY: break; } prev = xmlGetDocEntity(doc, ent->name); if (prev == NULL) { ret = xmlAddDocEntity(doc, ent->name, ent->etype, ent->ExternalID, ent->SystemID, ent->content); if (ret == NULL) { xmlXIncludeErrMemory(ctxt); return; } if (ent->URI != NULL) { ret->URI = xmlStrdup(ent->URI); if (ret->URI == 0) xmlXIncludeErrMemory(ctxt); } } else { if (ent->etype != prev->etype) goto error; if ((ent->SystemID != NULL) && (prev->SystemID != NULL)) { if (!xmlStrEqual(ent->SystemID, prev->SystemID)) goto error; } else if ((ent->ExternalID != NULL) && (prev->ExternalID != NULL)) { if (!xmlStrEqual(ent->ExternalID, prev->ExternalID)) goto error; } else if ((ent->content != NULL) && (prev->content != NULL)) { if (!xmlStrEqual(ent->content, prev->content)) goto error; } else { goto error; } } return; error: switch (ent->etype) { case XML_INTERNAL_PARAMETER_ENTITY: case XML_EXTERNAL_PARAMETER_ENTITY: case XML_INTERNAL_PREDEFINED_ENTITY: case XML_INTERNAL_GENERAL_ENTITY: case XML_EXTERNAL_GENERAL_PARSED_ENTITY: return; case XML_EXTERNAL_GENERAL_UNPARSED_ENTITY: break; } xmlXIncludeErr(ctxt, (xmlNodePtr) ent, XML_XINCLUDE_ENTITY_DEF_MISMATCH, "mismatch in redefinition of entity %s\n", ent->name); } /** * xmlXIncludeMergeEntities: * @ctxt: an XInclude context * @doc: the including doc * @from: the included doc * * Implements the entity merge * * Returns 0 if merge succeeded, -1 if some processing failed */ static int xmlXIncludeMergeEntities(xmlXIncludeCtxtPtr ctxt, xmlDocPtr doc, xmlDocPtr from) { xmlNodePtr cur; xmlDtdPtr target, source; if (ctxt == NULL) return(-1); if ((from == NULL) || (from->intSubset == NULL)) return(0); target = doc->intSubset; if (target == NULL) { cur = xmlDocGetRootElement(doc); if (cur == NULL) return(-1); target = xmlCreateIntSubset(doc, cur->name, NULL, NULL); if (target == NULL) { xmlXIncludeErrMemory(ctxt); return(-1); } } source = from->intSubset; if ((source != NULL) && (source->entities != NULL)) { xmlXIncludeMergeData data; data.ctxt = ctxt; data.doc = doc; xmlHashScan((xmlHashTablePtr) source->entities, xmlXIncludeMergeEntity, &data); } source = from->extSubset; if ((source != NULL) && (source->entities != NULL)) { xmlXIncludeMergeData data; data.ctxt = ctxt; data.doc = doc; /* * don't duplicate existing stuff when external subsets are the same */ if ((!xmlStrEqual(target->ExternalID, source->ExternalID)) && (!xmlStrEqual(target->SystemID, source->SystemID))) { xmlHashScan((xmlHashTablePtr) source->entities, xmlXIncludeMergeEntity, &data); } } return(0); } /** * xmlXIncludeLoadDoc: * @ctxt: the XInclude context * @url: the associated URL * @ref: an XMLXincludeRefPtr * * Load the document, and store the result in the XInclude context * * Returns 0 in case of success, -1 in case of failure */ static int xmlXIncludeLoadDoc(xmlXIncludeCtxtPtr ctxt, xmlXIncludeRefPtr ref) { xmlXIncludeDocPtr cache; xmlDocPtr doc; const xmlChar *url = ref->URI; const xmlChar *fragment = ref->fragment; int i = 0; int ret = -1; int cacheNr; #ifdef LIBXML_XPTR_ENABLED int saveFlags; #endif /* * Handling of references to the local document are done * directly through ctxt->doc. */ if ((url[0] == 0) || (url[0] == '#') || ((ctxt->doc != NULL) && (xmlStrEqual(url, ctxt->doc->URL)))) { doc = ctxt->doc; goto loaded; } /* * Prevent reloading the document twice. */ for (i = 0; i < ctxt->urlNr; i++) { if (xmlStrEqual(url, ctxt->urlTab[i].url)) { if (ctxt->urlTab[i].expanding) { xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_RECURSION, "inclusion loop detected\n", NULL); goto error; } doc = ctxt->urlTab[i].doc; if (doc == NULL) goto error; goto loaded; } } /* * Load it. */ #ifdef LIBXML_XPTR_ENABLED /* * If this is an XPointer evaluation, we want to assure that * all entities have been resolved prior to processing the * referenced document */ saveFlags = ctxt->parseFlags; if (fragment != NULL) { /* if this is an XPointer eval */ ctxt->parseFlags |= XML_PARSE_NOENT; } #endif doc = xmlXIncludeParseFile(ctxt, (const char *)url); #ifdef LIBXML_XPTR_ENABLED ctxt->parseFlags = saveFlags; #endif /* Also cache NULL docs */ if (ctxt->urlNr >= ctxt->urlMax) { xmlXIncludeDoc *tmp; #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION size_t newSize = ctxt->urlMax ? ctxt->urlMax * 2 : 1; #else size_t newSize = ctxt->urlMax ? ctxt->urlMax * 2 : 8; #endif tmp = xmlRealloc(ctxt->urlTab, sizeof(xmlXIncludeDoc) * newSize); if (tmp == NULL) { xmlXIncludeErrMemory(ctxt); xmlFreeDoc(doc); goto error; } ctxt->urlMax = newSize; ctxt->urlTab = tmp; } cache = &ctxt->urlTab[ctxt->urlNr]; cache->doc = doc; cache->url = xmlStrdup(url); if (cache->url == NULL) { xmlXIncludeErrMemory(ctxt); xmlFreeDoc(doc); goto error; } cache->expanding = 0; cacheNr = ctxt->urlNr++; if (doc == NULL) goto error; /* * It's possible that the requested URL has been mapped to a * completely different location (e.g. through a catalog entry). * To check for this, we compare the URL with that of the doc * and change it if they disagree (bug 146988). */ if ((doc->URL != NULL) && (!xmlStrEqual(url, doc->URL))) url = doc->URL; /* * Make sure we have all entities fixed up */ xmlXIncludeMergeEntities(ctxt, ctxt->doc, doc); /* * We don't need the DTD anymore, free up space if (doc->intSubset != NULL) { xmlUnlinkNode((xmlNodePtr) doc->intSubset); xmlFreeNode((xmlNodePtr) doc->intSubset); doc->intSubset = NULL; } if (doc->extSubset != NULL) { xmlUnlinkNode((xmlNodePtr) doc->extSubset); xmlFreeNode((xmlNodePtr) doc->extSubset); doc->extSubset = NULL; } */ cache->expanding = 1; xmlXIncludeRecurseDoc(ctxt, doc); /* urlTab might be reallocated. */ cache = &ctxt->urlTab[cacheNr]; cache->expanding = 0; loaded: if (fragment == NULL) { xmlNodePtr root; root = xmlDocGetRootElement(doc); if (root == NULL) { xmlXIncludeErr(ctxt, ref->elem, XML_ERR_INTERNAL_ERROR, "document without root\n", NULL); goto error; } ref->inc = xmlDocCopyNode(root, ctxt->doc, 1); if (ref->inc == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } if (ref->base != NULL) xmlXIncludeBaseFixup(ctxt, root, ref->inc, ref->base); } #ifdef LIBXML_XPTR_ENABLED else { /* * Computes the XPointer expression and make a copy used * as the replacement copy. */ xmlXPathObjectPtr xptr; xmlNodeSetPtr set; if (ctxt->isStream && doc == ctxt->doc) { xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_XPTR_FAILED, "XPointer expressions not allowed in streaming" " mode\n", NULL); goto error; } if (ctxt->xpctxt == NULL) { ctxt->xpctxt = xmlXPtrNewContext(doc, NULL, NULL); if (ctxt->xpctxt == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } if (ctxt->errorHandler != NULL) xmlXPathSetErrorHandler(ctxt->xpctxt, ctxt->errorHandler, ctxt->errorCtxt); #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION ctxt->xpctxt->opLimit = 100000; #endif } xptr = xmlXPtrEval(fragment, ctxt->xpctxt); if (xptr == NULL) { if (ctxt->xpctxt->lastError.code == XML_ERR_NO_MEMORY) xmlXIncludeErrMemory(ctxt); else xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_XPTR_FAILED, "XPointer evaluation failed: #%s\n", fragment); goto error; } switch (xptr->type) { case XPATH_UNDEFINED: case XPATH_BOOLEAN: case XPATH_NUMBER: case XPATH_STRING: #ifdef LIBXML_XPTR_LOCS_ENABLED case XPATH_POINT: #endif case XPATH_USERS: case XPATH_XSLT_TREE: xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_XPTR_RESULT, "XPointer is not a range: #%s\n", fragment); xmlXPathFreeObject(xptr); goto error; case XPATH_NODESET: break; #ifdef LIBXML_XPTR_LOCS_ENABLED case XPATH_RANGE: case XPATH_LOCATIONSET: break; #endif } set = xptr->nodesetval; if (set != NULL) { for (i = 0;i < set->nodeNr;i++) { if (set->nodeTab[i] == NULL) continue; switch (set->nodeTab[i]->type) { case XML_ELEMENT_NODE: case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_ENTITY_REF_NODE: case XML_ENTITY_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: continue; case XML_ATTRIBUTE_NODE: xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_XPTR_RESULT, "XPointer selects an attribute: #%s\n", fragment); set->nodeTab[i] = NULL; continue; case XML_NAMESPACE_DECL: xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_XPTR_RESULT, "XPointer selects a namespace: #%s\n", fragment); set->nodeTab[i] = NULL; continue; case XML_DOCUMENT_TYPE_NODE: case XML_DOCUMENT_FRAG_NODE: case XML_NOTATION_NODE: case XML_DTD_NODE: case XML_ELEMENT_DECL: case XML_ATTRIBUTE_DECL: case XML_ENTITY_DECL: case XML_XINCLUDE_START: case XML_XINCLUDE_END: xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_XPTR_RESULT, "XPointer selects unexpected nodes: #%s\n", fragment); set->nodeTab[i] = NULL; set->nodeTab[i] = NULL; continue; /* for */ } } } ref->inc = xmlXIncludeCopyXPointer(ctxt, xptr, ref->base); xmlXPathFreeObject(xptr); } #endif ret = 0; error: return(ret); } /** * xmlXIncludeLoadTxt: * @ctxt: the XInclude context * @ref: an XMLXincludeRefPtr * * Load the content, and store the result in the XInclude context * * Returns 0 in case of success, -1 in case of failure */ static int xmlXIncludeLoadTxt(xmlXIncludeCtxtPtr ctxt, xmlXIncludeRefPtr ref) { xmlParserInputBufferPtr buf; xmlNodePtr node = NULL; const xmlChar *url = ref->URI; int i; int ret = -1; xmlChar *encoding = NULL; xmlCharEncodingHandlerPtr handler = NULL; xmlParserCtxtPtr pctxt = NULL; xmlParserInputPtr inputStream = NULL; int len; int res; const xmlChar *content; /* * Handling of references to the local document are done * directly through ctxt->doc. */ if (url[0] == 0) { xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_TEXT_DOCUMENT, "text serialization of document not available\n", NULL); goto error; } /* * Prevent reloading the document twice. */ for (i = 0; i < ctxt->txtNr; i++) { if (xmlStrEqual(url, ctxt->txtTab[i].url)) { node = xmlNewDocText(ctxt->doc, ctxt->txtTab[i].text); if (node == NULL) xmlXIncludeErrMemory(ctxt); goto loaded; } } /* * Try to get the encoding if available */ if (ref->elem != NULL) { encoding = xmlXIncludeGetProp(ctxt, ref->elem, XINCLUDE_PARSE_ENCODING); } if (encoding != NULL) { res = xmlOpenCharEncodingHandler((const char *) encoding, /* output */ 0, &handler); if (res != 0) { if (res == XML_ERR_NO_MEMORY) { xmlXIncludeErrMemory(ctxt); } else if (res == XML_ERR_UNSUPPORTED_ENCODING) { xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_UNKNOWN_ENCODING, "encoding %s not supported\n", encoding); goto error; } else { xmlXIncludeErr(ctxt, ref->elem, res, "unexpected error from iconv or ICU\n", NULL); goto error; } } } /* * Load it. */ pctxt = xmlNewParserCtxt(); if (pctxt == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } inputStream = xmlLoadExternalEntity((const char*)url, NULL, pctxt); if (inputStream == NULL) { if (pctxt->errNo == XML_ERR_NO_MEMORY) xmlXIncludeErrMemory(ctxt); else xmlXIncludeErr(ctxt, NULL, pctxt->errNo, "load error", NULL); goto error; } buf = inputStream->buf; if (buf == NULL) goto error; if (buf->encoder) xmlCharEncCloseFunc(buf->encoder); buf->encoder = handler; handler = NULL; node = xmlNewDocText(ctxt->doc, NULL); if (node == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } /* * Scan all chars from the resource and add the to the node */ do { res = xmlParserInputBufferRead(buf, 4096); } while (res > 0); if (res < 0) { if (buf->error == XML_ERR_NO_MEMORY) xmlXIncludeErrMemory(ctxt); else xmlXIncludeErr(ctxt, NULL, buf->error, "read error", NULL); goto error; } content = xmlBufContent(buf->buffer); len = xmlBufLength(buf->buffer); for (i = 0; i < len;) { int cur; int l; l = len - i; cur = xmlGetUTF8Char(&content[i], &l); if ((cur < 0) || (!IS_CHAR(cur))) { xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_INVALID_CHAR, "%s contains invalid char\n", url); goto error; } i += l; } if (xmlNodeAddContentLen(node, content, len) < 0) xmlXIncludeErrMemory(ctxt); if (ctxt->txtNr >= ctxt->txtMax) { xmlXIncludeTxt *tmp; #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION size_t newSize = ctxt->txtMax ? ctxt->txtMax * 2 : 1; #else size_t newSize = ctxt->txtMax ? ctxt->txtMax * 2 : 8; #endif tmp = xmlRealloc(ctxt->txtTab, sizeof(xmlXIncludeTxt) * newSize); if (tmp == NULL) { xmlXIncludeErrMemory(ctxt); goto error; } ctxt->txtMax = newSize; ctxt->txtTab = tmp; } ctxt->txtTab[ctxt->txtNr].text = xmlStrdup(node->content); if ((node->content != NULL) && (ctxt->txtTab[ctxt->txtNr].text == NULL)) { xmlXIncludeErrMemory(ctxt); goto error; } ctxt->txtTab[ctxt->txtNr].url = xmlStrdup(url); if (ctxt->txtTab[ctxt->txtNr].url == NULL) { xmlXIncludeErrMemory(ctxt); xmlFree(ctxt->txtTab[ctxt->txtNr].text); goto error; } ctxt->txtNr++; loaded: /* * Add the element as the replacement copy. */ ref->inc = node; node = NULL; ret = 0; error: xmlFreeNode(node); xmlFreeInputStream(inputStream); xmlFreeParserCtxt(pctxt); xmlCharEncCloseFunc(handler); xmlFree(encoding); return(ret); } /** * xmlXIncludeLoadFallback: * @ctxt: the XInclude context * @fallback: the fallback node * @ref: an XMLXincludeRefPtr * * Load the content of the fallback node, and store the result * in the XInclude context * * Returns 0 in case of success, -1 in case of failure */ static int xmlXIncludeLoadFallback(xmlXIncludeCtxtPtr ctxt, xmlNodePtr fallback, xmlXIncludeRefPtr ref) { int ret = 0; int oldNbErrors; if ((fallback == NULL) || (fallback->type == XML_NAMESPACE_DECL) || (ctxt == NULL)) return(-1); if (fallback->children != NULL) { /* * It's possible that the fallback also has 'includes' * (Bug 129969), so we re-process the fallback just in case */ oldNbErrors = ctxt->nbErrors; ref->inc = xmlXIncludeCopyNode(ctxt, fallback, 1, ref->base); if (ctxt->nbErrors > oldNbErrors) ret = -1; } else { ref->inc = NULL; } ref->fallback = 1; return(ret); } /************************************************************************ * * * XInclude Processing * * * ************************************************************************/ /** * xmlXIncludeExpandNode: * @ctxt: an XInclude context * @node: an XInclude node * * If the XInclude node wasn't processed yet, create a new RefPtr, * add it to ctxt->incTab and load the included items. * * Returns the new or existing xmlXIncludeRefPtr, or NULL in case of error. */ static xmlXIncludeRefPtr xmlXIncludeExpandNode(xmlXIncludeCtxtPtr ctxt, xmlNodePtr node) { xmlXIncludeRefPtr ref; int i; if (ctxt->fatalErr) return(NULL); if (ctxt->depth >= XINCLUDE_MAX_DEPTH) { xmlXIncludeErr(ctxt, node, XML_XINCLUDE_RECURSION, "maximum recursion depth exceeded\n", NULL); ctxt->fatalErr = 1; return(NULL); } #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* * The XInclude engine offers no protection against exponential * expansion attacks similar to "billion laughs". Avoid timeouts by * limiting the total number of replacements when fuzzing. * * Unfortuately, a single XInclude can already result in quadratic * behavior: * * * * * * * * * * * */ if (ctxt->incTotal >= 20) return(NULL); ctxt->incTotal++; #endif for (i = 0; i < ctxt->incNr; i++) { if (ctxt->incTab[i]->elem == node) { if (ctxt->incTab[i]->expanding) { xmlXIncludeErr(ctxt, node, XML_XINCLUDE_RECURSION, "inclusion loop detected\n", NULL); return(NULL); } return(ctxt->incTab[i]); } } ref = xmlXIncludeAddNode(ctxt, node); if (ref == NULL) return(NULL); ref->expanding = 1; ctxt->depth++; xmlXIncludeLoadNode(ctxt, ref); ctxt->depth--; ref->expanding = 0; return(ref); } /** * xmlXIncludeLoadNode: * @ctxt: an XInclude context * @ref: an xmlXIncludeRefPtr * * Find and load the infoset replacement for the given node. * * Returns 0 if substitution succeeded, -1 if some processing failed */ static int xmlXIncludeLoadNode(xmlXIncludeCtxtPtr ctxt, xmlXIncludeRefPtr ref) { xmlNodePtr cur; int ret; if ((ctxt == NULL) || (ref == NULL)) return(-1); cur = ref->elem; if (cur == NULL) return(-1); if (ref->xml) { ret = xmlXIncludeLoadDoc(ctxt, ref); /* xmlXIncludeGetFragment(ctxt, cur, URI); */ } else { ret = xmlXIncludeLoadTxt(ctxt, ref); } if (ret < 0) { xmlNodePtr children; /* * Time to try a fallback if available */ children = cur->children; while (children != NULL) { if ((children->type == XML_ELEMENT_NODE) && (children->ns != NULL) && (xmlStrEqual(children->name, XINCLUDE_FALLBACK)) && ((xmlStrEqual(children->ns->href, XINCLUDE_NS)) || (xmlStrEqual(children->ns->href, XINCLUDE_OLD_NS)))) { ret = xmlXIncludeLoadFallback(ctxt, children, ref); break; } children = children->next; } } if (ret < 0) { xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_NO_FALLBACK, "could not load %s, and no fallback was found\n", ref->URI); } return(0); } /** * xmlXIncludeIncludeNode: * @ctxt: an XInclude context * @ref: an xmlXIncludeRefPtr * * Implement the infoset replacement for the given node * * Returns 0 if substitution succeeded, -1 if some processing failed */ static int xmlXIncludeIncludeNode(xmlXIncludeCtxtPtr ctxt, xmlXIncludeRefPtr ref) { xmlNodePtr cur, end, list, tmp; if ((ctxt == NULL) || (ref == NULL)) return(-1); cur = ref->elem; if ((cur == NULL) || (cur->type == XML_NAMESPACE_DECL)) return(-1); list = ref->inc; ref->inc = NULL; /* * Check against the risk of generating a multi-rooted document */ if ((cur->parent != NULL) && (cur->parent->type != XML_ELEMENT_NODE)) { int nb_elem = 0; tmp = list; while (tmp != NULL) { if (tmp->type == XML_ELEMENT_NODE) nb_elem++; tmp = tmp->next; } if (nb_elem > 1) { xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_MULTIPLE_ROOT, "XInclude error: would result in multiple root nodes\n", NULL); xmlFreeNodeList(list); return(-1); } } if (ctxt->parseFlags & XML_PARSE_NOXINCNODE) { /* * Add the list of nodes * * TODO: Coalesce text nodes unless we are streaming mode. */ while (list != NULL) { end = list; list = list->next; if (xmlAddPrevSibling(cur, end) == NULL) { xmlUnlinkNode(end); xmlFreeNode(end); goto err_memory; } } xmlUnlinkNode(cur); xmlFreeNode(cur); } else { xmlNodePtr child, next; /* * Change the current node as an XInclude start one, and add an * XInclude end one */ if (ref->fallback) xmlUnsetProp(cur, BAD_CAST "href"); cur->type = XML_XINCLUDE_START; /* Remove fallback children */ for (child = cur->children; child != NULL; child = next) { next = child->next; xmlUnlinkNode(child); xmlFreeNode(child); } end = xmlNewDocNode(cur->doc, cur->ns, cur->name, NULL); if (end == NULL) goto err_memory; end->type = XML_XINCLUDE_END; if (xmlAddNextSibling(cur, end) == NULL) { xmlFreeNode(end); goto err_memory; } /* * Add the list of nodes */ while (list != NULL) { cur = list; list = list->next; if (xmlAddPrevSibling(end, cur) == NULL) { xmlUnlinkNode(cur); xmlFreeNode(cur); goto err_memory; } } } return(0); err_memory: xmlXIncludeErrMemory(ctxt); xmlFreeNodeList(list); return(-1); } /** * xmlXIncludeTestNode: * @ctxt: the XInclude processing context * @node: an XInclude node * * test if the node is an XInclude node * * Returns 1 true, 0 otherwise */ static int xmlXIncludeTestNode(xmlXIncludeCtxtPtr ctxt, xmlNodePtr node) { if (node == NULL) return(0); if (node->type != XML_ELEMENT_NODE) return(0); if (node->ns == NULL) return(0); if ((xmlStrEqual(node->ns->href, XINCLUDE_NS)) || (xmlStrEqual(node->ns->href, XINCLUDE_OLD_NS))) { if (xmlStrEqual(node->ns->href, XINCLUDE_OLD_NS)) { if (ctxt->legacy == 0) { #if 0 /* wait for the XML Core Working Group to get something stable ! */ xmlXIncludeWarn(ctxt, node, XML_XINCLUDE_DEPRECATED_NS, "Deprecated XInclude namespace found, use %s", XINCLUDE_NS); #endif ctxt->legacy = 1; } } if (xmlStrEqual(node->name, XINCLUDE_NODE)) { xmlNodePtr child = node->children; int nb_fallback = 0; while (child != NULL) { if ((child->type == XML_ELEMENT_NODE) && (child->ns != NULL) && ((xmlStrEqual(child->ns->href, XINCLUDE_NS)) || (xmlStrEqual(child->ns->href, XINCLUDE_OLD_NS)))) { if (xmlStrEqual(child->name, XINCLUDE_NODE)) { xmlXIncludeErr(ctxt, node, XML_XINCLUDE_INCLUDE_IN_INCLUDE, "%s has an 'include' child\n", XINCLUDE_NODE); return(0); } if (xmlStrEqual(child->name, XINCLUDE_FALLBACK)) { nb_fallback++; } } child = child->next; } if (nb_fallback > 1) { xmlXIncludeErr(ctxt, node, XML_XINCLUDE_FALLBACKS_IN_INCLUDE, "%s has multiple fallback children\n", XINCLUDE_NODE); return(0); } return(1); } if (xmlStrEqual(node->name, XINCLUDE_FALLBACK)) { if ((node->parent == NULL) || (node->parent->type != XML_ELEMENT_NODE) || (node->parent->ns == NULL) || ((!xmlStrEqual(node->parent->ns->href, XINCLUDE_NS)) && (!xmlStrEqual(node->parent->ns->href, XINCLUDE_OLD_NS))) || (!xmlStrEqual(node->parent->name, XINCLUDE_NODE))) { xmlXIncludeErr(ctxt, node, XML_XINCLUDE_FALLBACK_NOT_IN_INCLUDE, "%s is not the child of an 'include'\n", XINCLUDE_FALLBACK); } } } return(0); } /** * xmlXIncludeDoProcess: * @ctxt: the XInclude processing context * @tree: the top of the tree to process * * Implement the XInclude substitution on the XML document @doc * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ static int xmlXIncludeDoProcess(xmlXIncludeCtxtPtr ctxt, xmlNodePtr tree) { xmlXIncludeRefPtr ref; xmlNodePtr cur; int ret = 0; int i, start; /* * First phase: lookup the elements in the document */ start = ctxt->incNr; cur = tree; do { /* TODO: need to work on entities -> stack */ if (xmlXIncludeTestNode(ctxt, cur) == 1) { ref = xmlXIncludeExpandNode(ctxt, cur); /* * Mark direct includes. */ if (ref != NULL) ref->replace = 1; } else if ((cur->children != NULL) && ((cur->type == XML_DOCUMENT_NODE) || (cur->type == XML_ELEMENT_NODE))) { cur = cur->children; continue; } do { if (cur == tree) break; if (cur->next != NULL) { cur = cur->next; break; } cur = cur->parent; } while (cur != NULL); } while ((cur != NULL) && (cur != tree)); /* * Second phase: extend the original document infoset. */ for (i = start; i < ctxt->incNr; i++) { if (ctxt->incTab[i]->replace != 0) { xmlXIncludeIncludeNode(ctxt, ctxt->incTab[i]); ctxt->incTab[i]->replace = 0; } else { /* * Ignore includes which were added indirectly, for example * inside xi:fallback elements. */ if (ctxt->incTab[i]->inc != NULL) { xmlFreeNodeList(ctxt->incTab[i]->inc); ctxt->incTab[i]->inc = NULL; } } ret++; } if (ctxt->isStream) { /* * incTab references nodes which will eventually be deleted in * streaming mode. The table is only required for XPointer * expressions which aren't allowed in streaming mode. */ for (i = 0;i < ctxt->incNr;i++) { xmlXIncludeFreeRef(ctxt->incTab[i]); } ctxt->incNr = 0; } return(ret); } /** * xmlXIncludeDoProcessRoot: * @ctxt: the XInclude processing context * @tree: the top of the tree to process * * Implement the XInclude substitution on the XML document @doc * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ static int xmlXIncludeDoProcessRoot(xmlXIncludeCtxtPtr ctxt, xmlNodePtr tree) { if ((tree == NULL) || (tree->type == XML_NAMESPACE_DECL)) return(-1); if (ctxt == NULL) return(-1); return(xmlXIncludeDoProcess(ctxt, tree)); } /** * xmlXIncludeGetLastError: * @ctxt: an XInclude processing context * * Available since 2.13.0. * * Returns the last error code. */ int xmlXIncludeGetLastError(xmlXIncludeCtxtPtr ctxt) { if (ctxt == NULL) return(XML_ERR_ARGUMENT); return(ctxt->errNo); } /** * xmlXIncludeSetErrorHandler: * @ctxt: an XInclude processing context * @handler: error handler * @data: user data which will be passed to the handler * * Register a callback function that will be called on errors and * warnings. If handler is NULL, the error handler will be deactivated. * * Available since 2.13.0. */ void xmlXIncludeSetErrorHandler(xmlXIncludeCtxtPtr ctxt, xmlStructuredErrorFunc handler, void *data) { if (ctxt == NULL) return; ctxt->errorHandler = handler; ctxt->errorCtxt = data; } /** * xmlXIncludeSetFlags: * @ctxt: an XInclude processing context * @flags: a set of xmlParserOption used for parsing XML includes * * Set the flags used for further processing of XML resources. * * Returns 0 in case of success and -1 in case of error. */ int xmlXIncludeSetFlags(xmlXIncludeCtxtPtr ctxt, int flags) { if (ctxt == NULL) return(-1); ctxt->parseFlags = flags; return(0); } /** * xmlXIncludeSetStreamingMode: * @ctxt: an XInclude processing context * @mode: whether streaming mode should be enabled * * In streaming mode, XPointer expressions aren't allowed. * * Returns 0 in case of success and -1 in case of error. */ int xmlXIncludeSetStreamingMode(xmlXIncludeCtxtPtr ctxt, int mode) { if (ctxt == NULL) return(-1); ctxt->isStream = !!mode; return(0); } /** * xmlXIncludeProcessTreeFlagsData: * @tree: an XML node * @flags: a set of xmlParserOption used for parsing XML includes * @data: application data that will be passed to the parser context * in the _private field of the parser context(s) * * Implement the XInclude substitution on the XML node @tree * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcessTreeFlagsData(xmlNodePtr tree, int flags, void *data) { xmlXIncludeCtxtPtr ctxt; int ret = 0; if ((tree == NULL) || (tree->type == XML_NAMESPACE_DECL) || (tree->doc == NULL)) return(-1); ctxt = xmlXIncludeNewContext(tree->doc); if (ctxt == NULL) return(-1); ctxt->_private = data; xmlXIncludeSetFlags(ctxt, flags); ret = xmlXIncludeDoProcessRoot(ctxt, tree); if ((ret >= 0) && (ctxt->nbErrors > 0)) ret = -1; xmlXIncludeFreeContext(ctxt); return(ret); } /** * xmlXIncludeProcessFlagsData: * @doc: an XML document * @flags: a set of xmlParserOption used for parsing XML includes * @data: application data that will be passed to the parser context * in the _private field of the parser context(s) * * Implement the XInclude substitution on the XML document @doc * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcessFlagsData(xmlDocPtr doc, int flags, void *data) { xmlNodePtr tree; if (doc == NULL) return(-1); tree = xmlDocGetRootElement(doc); if (tree == NULL) return(-1); return(xmlXIncludeProcessTreeFlagsData(tree, flags, data)); } /** * xmlXIncludeProcessFlags: * @doc: an XML document * @flags: a set of xmlParserOption used for parsing XML includes * * Implement the XInclude substitution on the XML document @doc * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcessFlags(xmlDocPtr doc, int flags) { return xmlXIncludeProcessFlagsData(doc, flags, NULL); } /** * xmlXIncludeProcess: * @doc: an XML document * * Implement the XInclude substitution on the XML document @doc * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcess(xmlDocPtr doc) { return(xmlXIncludeProcessFlags(doc, 0)); } /** * xmlXIncludeProcessTreeFlags: * @tree: a node in an XML document * @flags: a set of xmlParserOption used for parsing XML includes * * Implement the XInclude substitution for the given subtree * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcessTreeFlags(xmlNodePtr tree, int flags) { xmlXIncludeCtxtPtr ctxt; int ret = 0; if ((tree == NULL) || (tree->type == XML_NAMESPACE_DECL) || (tree->doc == NULL)) return(-1); ctxt = xmlXIncludeNewContext(tree->doc); if (ctxt == NULL) return(-1); xmlXIncludeSetFlags(ctxt, flags); ret = xmlXIncludeDoProcessRoot(ctxt, tree); if ((ret >= 0) && (ctxt->nbErrors > 0)) ret = -1; xmlXIncludeFreeContext(ctxt); return(ret); } /** * xmlXIncludeProcessTree: * @tree: a node in an XML document * * Implement the XInclude substitution for the given subtree * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcessTree(xmlNodePtr tree) { return(xmlXIncludeProcessTreeFlags(tree, 0)); } /** * xmlXIncludeProcessNode: * @ctxt: an existing XInclude context * @node: a node in an XML document * * Implement the XInclude substitution for the given subtree reusing * the information and data coming from the given context. * * Returns 0 if no substitution were done, -1 if some processing failed * or the number of substitutions done. */ int xmlXIncludeProcessNode(xmlXIncludeCtxtPtr ctxt, xmlNodePtr node) { int ret = 0; if ((node == NULL) || (node->type == XML_NAMESPACE_DECL) || (node->doc == NULL) || (ctxt == NULL)) return(-1); ret = xmlXIncludeDoProcessRoot(ctxt, node); if ((ret >= 0) && (ctxt->nbErrors > 0)) ret = -1; return(ret); } #else /* !LIBXML_XINCLUDE_ENABLED */ #endif