1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2024-10-26 12:25:09 +03:00
libxml2/xinclude.c
Nick Wellnhofer a221cd7849 buf: Rework xmlBuf code
Always use what the old implementation called the "IO" allocation
scheme, allowing to move the content pointer past the initial
allocation. This is inexpensive and allows efficient shrinking.

Optimize xmlBufGrow, reusing shrunken memory as much as possible.

Simplify xmlBufAdd.

Make xmlBufBackToBuffer return an error on overflow.

Make "size" exclude the terminating NULL byte.

Always provide an initial size.

Reintroduce static buffers.

Remove xmlBufResize and several other functions.
2024-07-16 17:42:10 +02:00

2220 lines
59 KiB
C

/*
* 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 <string.h>
#include <libxml/xmlmemory.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/uri.h>
#include <libxml/xpath.h>
#include <libxml/xpointer.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlerror.h>
#include <libxml/encoding.h>
#ifdef LIBXML_XINCLUDE_ENABLED
#include <libxml/xinclude.h>
#include "private/buf.h"
#include "private/error.h"
#include "private/parser.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;
xmlResourceLoader resourceLoader;
void *resourceCtxt;
};
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);
if (ctxt->resourceLoader != NULL)
xmlCtxtSetResourceLoader(pctxt, ctxt->resourceLoader,
ctxt->resourceCtxt);
/*
* 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);
}
/*
* We set DTDLOAD to make sure that ID attributes declared in
* external DTDs are detected.
*/
xmlCtxtUseOptions(pctxt, ctxt->parseFlags | XML_PARSE_DTDLOAD);
inputStream = xmlLoadResource(pctxt, URL, NULL, XML_RESOURCE_XINCLUDE);
if (inputStream == NULL)
goto error;
if (inputPush(pctxt, inputStream) < 0) {
xmlFreeInputStream(inputStream);
goto error;
}
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;
}
} else if (xmlStrlen(href) > XML_MAX_URI_LENGTH) {
xmlXIncludeErr(ctxt, cur, XML_XINCLUDE_HREF_URI, "URI too long\n",
NULL);
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 ((xmlStrlen(base) > XML_MAX_URI_LENGTH) ||
(xmlStrlen(targetBase) > XML_MAX_URI_LENGTH)) {
relBase = xmlStrdup(base);
if (relBase == NULL) {
xmlXIncludeErrMemory(ctxt);
goto done;
}
} else 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_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;
}
default:
break;
}
return(list);
}
#endif
/************************************************************************
* *
* 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 = xmlXPathNewContext(doc);
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
} else {
ctxt->xpctxt->doc = doc;
}
xptr = xmlXPtrEval(fragment, ctxt->xpctxt);
if (ctxt->xpctxt->lastError.code != XML_ERR_OK) {
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;
}
if (xptr == NULL)
goto done;
switch (xptr->type) {
case XPATH_UNDEFINED:
case XPATH_BOOLEAN:
case XPATH_NUMBER:
case XPATH_STRING:
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;
}
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
done:
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;
}
if (ctxt->errorHandler != NULL)
xmlCtxtSetErrorHandler(pctxt, ctxt->errorHandler, ctxt->errorCtxt);
if (ctxt->resourceLoader != NULL)
xmlCtxtSetResourceLoader(pctxt, ctxt->resourceLoader,
ctxt->resourceCtxt);
inputStream = xmlLoadResource(pctxt, (const char*) url, NULL,
XML_RESOURCE_XINCLUDE_TEXT);
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 = xmlBufUse(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:
*
* <doc xmlns:xi="http://www.w3.org/2001/XInclude">
* <xi:include xpointer="xpointer(//e)"/>
* <e>
* <e>
* <e>
* <!-- more nested elements -->
* </e>
* </e>
* </e>
* </doc>
*/
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) {
if (nb_elem > 1)
xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_MULTIPLE_ROOT,
"XInclude error: would result in multiple root "
"nodes\n", NULL);
else
xmlXIncludeErr(ctxt, ref->elem, XML_XINCLUDE_MULTIPLE_ROOT,
"XInclude error: would result in no root "
"node\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) {
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;
}
/**
* xmlXIncludeSetResourceLoader:
* @ctxt: an XInclude processing context
* @loader: resource loader
* @data: user data which will be passed to the loader
*
* Register a callback function that will be called to load included
* documents.
*
* Available since 2.14.0.
*/
void
xmlXIncludeSetResourceLoader(xmlXIncludeCtxtPtr ctxt,
xmlResourceLoader loader, void *data) {
if (ctxt == NULL)
return;
ctxt->resourceLoader = loader;
ctxt->resourceCtxt = 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