/** * catalog.c: set of generic Catalog related routines * * Reference: SGML Open Technical Resolution TR9401:1997. * http://www.jclark.com/sp/catalog.htm * * XML Catalogs Working Draft 06 August 2001 * http://www.oasis-open.org/committees/entity/spec-2001-08-06.html * * See Copyright for the status of this software. * * Daniel.Veillard@imag.fr */ #include "libxml.h" #ifdef LIBXML_CATALOG_ENABLED #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #include #include #include #include #include #include #include #include #include #define MAX_DELEGATE 50 /** * TODO: * * macro to flag unimplemented blocks */ #define TODO \ xmlGenericError(xmlGenericErrorContext, \ "Unimplemented block at %s:%d\n", \ __FILE__, __LINE__); #define XML_URN_PUBID "urn:publicid:" #define XML_CATAL_BREAK ((xmlChar *) -1) #ifndef XML_XML_DEFAULT_CATALOG #define XML_XML_DEFAULT_CATALOG "file:///etc/xml/catalog" #endif #ifndef XML_SGML_DEFAULT_CATALOG #define XML_SGML_DEFAULT_CATALOG "file:///etc/sgml/catalog" #endif static int xmlExpandCatalog(xmlCatalogPtr catal, const char *filename); /************************************************************************ * * * Types, all private * * * ************************************************************************/ typedef enum { XML_CATA_REMOVED = -1, XML_CATA_NONE = 0, XML_CATA_CATALOG, XML_CATA_BROKEN_CATALOG, XML_CATA_NEXT_CATALOG, XML_CATA_PUBLIC, XML_CATA_SYSTEM, XML_CATA_REWRITE_SYSTEM, XML_CATA_DELEGATE_PUBLIC, XML_CATA_DELEGATE_SYSTEM, XML_CATA_URI, XML_CATA_REWRITE_URI, XML_CATA_DELEGATE_URI, SGML_CATA_SYSTEM, SGML_CATA_PUBLIC, SGML_CATA_ENTITY, SGML_CATA_PENTITY, SGML_CATA_DOCTYPE, SGML_CATA_LINKTYPE, SGML_CATA_NOTATION, SGML_CATA_DELEGATE, SGML_CATA_BASE, SGML_CATA_CATALOG, SGML_CATA_DOCUMENT, SGML_CATA_SGMLDECL } xmlCatalogEntryType; typedef struct _xmlCatalogEntry xmlCatalogEntry; typedef xmlCatalogEntry *xmlCatalogEntryPtr; struct _xmlCatalogEntry { struct _xmlCatalogEntry *next; struct _xmlCatalogEntry *parent; struct _xmlCatalogEntry *children; xmlCatalogEntryType type; xmlChar *name; xmlChar *value; xmlChar *URL; /* The expanded URL using the base */ xmlCatalogPrefer prefer; int dealloc; }; typedef enum { XML_XML_CATALOG_TYPE = 1, XML_SGML_CATALOG_TYPE } xmlCatalogType; #define XML_MAX_SGML_CATA_DEPTH 10 struct _xmlCatalog { xmlCatalogType type; /* either XML or SGML */ /* * SGML Catalogs are stored as a simple hash table of catalog entries * Catalog stack to check against overflows when building the * SGML catalog */ char *catalTab[XML_MAX_SGML_CATA_DEPTH]; /* stack of catals */ int catalNr; /* Number of current catal streams */ int catalMax; /* Max number of catal streams */ xmlHashTablePtr sgml; /* * XML Catalogs are stored as a tree of Catalog entries */ xmlCatalogPrefer prefer; xmlCatalogEntryPtr xml; }; /************************************************************************ * * * Global variables * * * ************************************************************************/ /* * Those are preferences */ static int xmlDebugCatalogs = 0; /* used for debugging */ static xmlCatalogAllow xmlCatalogDefaultAllow = XML_CATA_ALLOW_ALL; static xmlCatalogPrefer xmlCatalogDefaultPrefer = XML_CATA_PREFER_PUBLIC; /* * Hash table containing all the trees of XML catalogs parsed by * the application. */ static xmlHashTablePtr xmlCatalogXMLFiles = NULL; /* * The default catalog in use by the application */ static xmlCatalogPtr xmlDefaultCatalog = NULL; /* * A mutex for modifying the shared global catalog(s) * xmlDefaultCatalog tree. * It also protects xmlCatalogXMLFiles * The core of this readers/writer scheme is in xmlFetchXMLCatalogFile() */ static xmlRMutexPtr xmlCatalogMutex = NULL; /* * Whether the catalog support was initialized. */ static int xmlCatalogInitialized = 0; /************************************************************************ * * * Allocation and Freeing * * * ************************************************************************/ /** * xmlNewCatalogEntry: * @type: type of entry * @name: name of the entry * @value: value of the entry * @prefer: the PUBLIC vs. SYSTEM current preference value * * create a new Catalog entry, this type is shared both by XML and * SGML catalogs, but the acceptable types values differs. * * Returns the xmlCatalogEntryPtr or NULL in case of error */ static xmlCatalogEntryPtr xmlNewCatalogEntry(xmlCatalogEntryType type, const xmlChar *name, const xmlChar *value, const xmlChar *URL, xmlCatalogPrefer prefer) { xmlCatalogEntryPtr ret; ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry)); if (ret == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc of %d byte failed\n", sizeof(xmlCatalogEntry)); return(NULL); } ret->next = NULL; ret->parent = NULL; ret->children = NULL; ret->type = type; if (name != NULL) ret->name = xmlStrdup(name); else ret->name = NULL; if (value != NULL) ret->value = xmlStrdup(value); else ret->value = NULL; if (URL == NULL) URL = value; if (URL != NULL) ret->URL = xmlStrdup(URL); else ret->URL = NULL; ret->prefer = prefer; ret->dealloc = 0; return(ret); } static void xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret); /** * xmlFreeCatalogEntry: * @ret: a Catalog entry * * Free the memory allocated to a Catalog entry */ static void xmlFreeCatalogEntry(xmlCatalogEntryPtr ret) { if (ret == NULL) return; /* * Entries stored in the file hash must be dealloacted * only by the file hash cleaner ! */ if (ret->dealloc == 1) return; if (xmlDebugCatalogs) { if (ret->name != NULL) xmlGenericError(xmlGenericErrorContext, "Free catalog entry %s\n", ret->name); else if (ret->value != NULL) xmlGenericError(xmlGenericErrorContext, "Free catalog entry %s\n", ret->value); else xmlGenericError(xmlGenericErrorContext, "Free catalog entry\n"); } if (ret->name != NULL) xmlFree(ret->name); if (ret->value != NULL) xmlFree(ret->value); if (ret->URL != NULL) xmlFree(ret->URL); xmlFree(ret); } /** * xmlFreeCatalogEntryList: * @ret: a Catalog entry list * * Free the memory allocated to a full chained list of Catalog entries */ static void xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret) { xmlCatalogEntryPtr next; while (ret != NULL) { next = ret->next; xmlFreeCatalogEntry(ret); ret = next; } } /** * xmlFreeCatalogHashEntryList: * @ret: a Catalog entry list * * Free the memory allocated to list of Catalog entries from the * catalog file hash. */ static void xmlFreeCatalogHashEntryList(xmlCatalogEntryPtr catal) { xmlCatalogEntryPtr children, next; if (catal == NULL) return; children = catal->children; while (children != NULL) { next = children->next; children->dealloc = 0; children->children = NULL; xmlFreeCatalogEntry(children); children = next; } catal->dealloc = 0; xmlFreeCatalogEntry(catal); } /** * xmlCreateNewCatalog: * @type: type of catalog * @prefer: the PUBLIC vs. SYSTEM current preference value * * create a new Catalog, this type is shared both by XML and * SGML catalogs, but the acceptable types values differs. * * Returns the xmlCatalogPtr or NULL in case of error */ static xmlCatalogPtr xmlCreateNewCatalog(xmlCatalogType type, xmlCatalogPrefer prefer) { xmlCatalogPtr ret; ret = (xmlCatalogPtr) xmlMalloc(sizeof(xmlCatalog)); if (ret == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc of %d byte failed\n", sizeof(xmlCatalog)); return(NULL); } memset(ret, 0, sizeof(xmlCatalog)); ret->type = type; ret->catalNr = 0; ret->catalMax = XML_MAX_SGML_CATA_DEPTH; ret->prefer = prefer; if (ret->type == XML_SGML_CATALOG_TYPE) ret->sgml = xmlHashCreate(10); return(ret); } /** * xmlFreeCatalog: * @catal: a Catalog entry * * Free the memory allocated to a Catalog */ void xmlFreeCatalog(xmlCatalogPtr catal) { if (catal == NULL) return; if (catal->xml != NULL) xmlFreeCatalogEntryList(catal->xml); if (catal->sgml != NULL) xmlHashFree(catal->sgml, (xmlHashDeallocator) xmlFreeCatalogEntry); xmlFree(catal); } /************************************************************************ * * * Serializing Catalogs * * * ************************************************************************/ /** * xmlCatalogDumpEntry: * @entry: the * @out: the file. * * Serialize an SGML Catalog entry */ static void xmlCatalogDumpEntry(xmlCatalogEntryPtr entry, FILE *out) { if ((entry == NULL) || (out == NULL)) return; switch (entry->type) { case SGML_CATA_ENTITY: fprintf(out, "ENTITY "); break; case SGML_CATA_PENTITY: fprintf(out, "ENTITY %%"); break; case SGML_CATA_DOCTYPE: fprintf(out, "DOCTYPE "); break; case SGML_CATA_LINKTYPE: fprintf(out, "LINKTYPE "); break; case SGML_CATA_NOTATION: fprintf(out, "NOTATION "); break; case SGML_CATA_PUBLIC: fprintf(out, "PUBLIC "); break; case SGML_CATA_SYSTEM: fprintf(out, "SYSTEM "); break; case SGML_CATA_DELEGATE: fprintf(out, "DELEGATE "); break; case SGML_CATA_BASE: fprintf(out, "BASE "); break; case SGML_CATA_CATALOG: fprintf(out, "CATALOG "); break; case SGML_CATA_DOCUMENT: fprintf(out, "DOCUMENT "); break; case SGML_CATA_SGMLDECL: fprintf(out, "SGMLDECL "); break; default: return; } switch (entry->type) { case SGML_CATA_ENTITY: case SGML_CATA_PENTITY: case SGML_CATA_DOCTYPE: case SGML_CATA_LINKTYPE: case SGML_CATA_NOTATION: fprintf(out, "%s", entry->name); break; case SGML_CATA_PUBLIC: case SGML_CATA_SYSTEM: case SGML_CATA_SGMLDECL: case SGML_CATA_DOCUMENT: case SGML_CATA_CATALOG: case SGML_CATA_BASE: case SGML_CATA_DELEGATE: fprintf(out, "\"%s\"", entry->name); break; default: break; } switch (entry->type) { case SGML_CATA_ENTITY: case SGML_CATA_PENTITY: case SGML_CATA_DOCTYPE: case SGML_CATA_LINKTYPE: case SGML_CATA_NOTATION: case SGML_CATA_PUBLIC: case SGML_CATA_SYSTEM: case SGML_CATA_DELEGATE: fprintf(out, " \"%s\"", entry->value); break; default: break; } fprintf(out, "\n"); } static int xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) { int ret; xmlDocPtr doc; xmlNsPtr ns; xmlDtdPtr dtd; xmlNodePtr node, catalog; xmlOutputBufferPtr buf; xmlCatalogEntryPtr cur; /* * Rebuild a catalog */ doc = xmlNewDoc(NULL); if (doc == NULL) return(-1); dtd = xmlNewDtd(doc, BAD_CAST "catalog", BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN", BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"); xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd); ns = xmlNewNs(NULL, XML_CATALOGS_NAMESPACE, NULL); if (ns == NULL) { xmlFreeDoc(doc); return(-1); } catalog = xmlNewDocNode(doc, ns, BAD_CAST "catalog", NULL); if (catalog == NULL) { xmlFreeNs(ns); xmlFreeDoc(doc); return(-1); } catalog->nsDef = ns; xmlAddChild((xmlNodePtr) doc, catalog); /* * add all the catalog entries */ cur = catal; while (cur != NULL) { switch (cur->type) { case XML_CATA_REMOVED: break; case XML_CATA_BROKEN_CATALOG: case XML_CATA_CATALOG: if (cur == catal) { cur = cur->children; continue; } break; case XML_CATA_NEXT_CATALOG: node = xmlNewDocNode(doc, ns, BAD_CAST "nextCatalog", NULL); xmlSetProp(node, BAD_CAST "catalog", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_NONE: break; case XML_CATA_PUBLIC: node = xmlNewDocNode(doc, ns, BAD_CAST "public", NULL); xmlSetProp(node, BAD_CAST "publicId", cur->name); xmlSetProp(node, BAD_CAST "uri", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_SYSTEM: node = xmlNewDocNode(doc, ns, BAD_CAST "system", NULL); xmlSetProp(node, BAD_CAST "systemId", cur->name); xmlSetProp(node, BAD_CAST "uri", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_REWRITE_SYSTEM: node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteSystem", NULL); xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name); xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_DELEGATE_PUBLIC: node = xmlNewDocNode(doc, ns, BAD_CAST "delegatePublic", NULL); xmlSetProp(node, BAD_CAST "publicIdStartString", cur->name); xmlSetProp(node, BAD_CAST "catalog", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_DELEGATE_SYSTEM: node = xmlNewDocNode(doc, ns, BAD_CAST "delegateSystem", NULL); xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name); xmlSetProp(node, BAD_CAST "catalog", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_URI: node = xmlNewDocNode(doc, ns, BAD_CAST "uri", NULL); xmlSetProp(node, BAD_CAST "name", cur->name); xmlSetProp(node, BAD_CAST "uri", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_REWRITE_URI: node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteURI", NULL); xmlSetProp(node, BAD_CAST "uriStartString", cur->name); xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value); xmlAddChild(catalog, node); break; case XML_CATA_DELEGATE_URI: node = xmlNewDocNode(doc, ns, BAD_CAST "delegateURI", NULL); xmlSetProp(node, BAD_CAST "uriStartString", cur->name); xmlSetProp(node, BAD_CAST "catalog", cur->value); xmlAddChild(catalog, node); break; case SGML_CATA_SYSTEM: case SGML_CATA_PUBLIC: case SGML_CATA_ENTITY: case SGML_CATA_PENTITY: case SGML_CATA_DOCTYPE: case SGML_CATA_LINKTYPE: case SGML_CATA_NOTATION: case SGML_CATA_DELEGATE: case SGML_CATA_BASE: case SGML_CATA_CATALOG: case SGML_CATA_DOCUMENT: case SGML_CATA_SGMLDECL: break; } cur = cur->next; } /* * reserialize it */ buf = xmlOutputBufferCreateFile(out, NULL); if (buf == NULL) { xmlFreeDoc(doc); return(-1); } ret = xmlSaveFormatFileTo(buf, doc, NULL, 1); /* * Free it */ xmlFreeDoc(doc); return(ret); } /************************************************************************ * * * Converting SGML Catalogs to XML * * * ************************************************************************/ /** * xmlCatalogConvertEntry: * @entry: the entry * @catal: pointer to the catalog being converted * * Convert one entry from the catalog */ static void xmlCatalogConvertEntry(xmlCatalogEntryPtr entry, xmlCatalogPtr catal) { if ((entry == NULL) || (catal == NULL) || (catal->sgml == NULL) || (catal->xml == NULL)) return; switch (entry->type) { case SGML_CATA_ENTITY: entry->type = XML_CATA_PUBLIC; break; case SGML_CATA_PENTITY: entry->type = XML_CATA_PUBLIC; break; case SGML_CATA_DOCTYPE: entry->type = XML_CATA_PUBLIC; break; case SGML_CATA_LINKTYPE: entry->type = XML_CATA_PUBLIC; break; case SGML_CATA_NOTATION: entry->type = XML_CATA_PUBLIC; break; case SGML_CATA_PUBLIC: entry->type = XML_CATA_PUBLIC; break; case SGML_CATA_SYSTEM: entry->type = XML_CATA_SYSTEM; break; case SGML_CATA_DELEGATE: entry->type = XML_CATA_DELEGATE_PUBLIC; break; case SGML_CATA_CATALOG: entry->type = XML_CATA_CATALOG; break; default: xmlHashRemoveEntry(catal->sgml, entry->name, (xmlHashDeallocator) xmlFreeCatalogEntry); return; } /* * Conversion successful, remove from the SGML catalog * and add it to the default XML one */ xmlHashRemoveEntry(catal->sgml, entry->name, NULL); entry->parent = catal->xml; entry->next = NULL; if (catal->xml->children == NULL) catal->xml->children = entry; else { xmlCatalogEntryPtr prev; prev = catal->xml->children; while (prev->next != NULL) prev = prev->next; prev->next = entry; } } /** * xmlConvertSGMLCatalog: * @catal: the catalog * * Convert all the SGML catalog entries as XML ones * * Returns the number of entries converted if successful, -1 otherwise */ int xmlConvertSGMLCatalog(xmlCatalogPtr catal) { if ((catal == NULL) || (catal->type != XML_SGML_CATALOG_TYPE)) return(-1); if (xmlDebugCatalogs) { xmlGenericError(xmlGenericErrorContext, "Converting SGML catalog to XML\n"); } xmlHashScan(catal->sgml, (xmlHashScanner) xmlCatalogConvertEntry, &catal); return(0); } /************************************************************************ * * * Helper function * * * ************************************************************************/ /** * xmlCatalogUnWrapURN: * @urn: an "urn:publicid:" to unwrapp * * Expand the URN into the equivalent Public Identifier * * Returns the new identifier or NULL, the string must be deallocated * by the caller. */ static xmlChar * xmlCatalogUnWrapURN(const xmlChar *urn) { xmlChar result[2000]; unsigned int i = 0; if (xmlStrncmp(urn, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) return(NULL); urn += sizeof(XML_URN_PUBID) - 1; while (*urn != 0) { if (i > sizeof(result) - 3) break; if (*urn == '+') { result[i++] = ' '; urn++; } else if (*urn == ':') { result[i++] = '/'; result[i++] = '/'; urn++; } else if (*urn == ';') { result[i++] = ':'; result[i++] = ':'; urn++; } else if (*urn == '%') { if ((urn[1] == '2') && (urn[1] == 'B')) result[i++] = '+'; else if ((urn[1] == '3') && (urn[1] == 'A')) result[i++] = ':'; else if ((urn[1] == '2') && (urn[1] == 'F')) result[i++] = '/'; else if ((urn[1] == '3') && (urn[1] == 'B')) result[i++] = ';'; else if ((urn[1] == '2') && (urn[1] == '7')) result[i++] = '\''; else if ((urn[1] == '3') && (urn[1] == 'F')) result[i++] = '?'; else if ((urn[1] == '2') && (urn[1] == '3')) result[i++] = '#'; else if ((urn[1] == '2') && (urn[1] == '5')) result[i++] = '%'; else { result[i++] = *urn; urn++; continue; } urn += 3; } else { result[i++] = *urn; urn++; } } result[i] = 0; return(xmlStrdup(result)); } /** * xmlParseCatalogFile: * @filename: the filename * * parse an XML file and build a tree. It's like xmlParseFile() * except it bypass all catalog lookups. * * Returns the resulting document tree or NULL in case of error */ xmlDocPtr xmlParseCatalogFile(const char *filename) { xmlDocPtr ret; xmlParserCtxtPtr ctxt; char *directory = NULL; xmlParserInputPtr inputStream; xmlParserInputBufferPtr buf; ctxt = xmlNewParserCtxt(); if (ctxt == NULL) { if (xmlDefaultSAXHandler.error != NULL) { xmlDefaultSAXHandler.error(NULL, "out of memory\n"); } return(NULL); } buf = xmlParserInputBufferCreateFilename(filename, XML_CHAR_ENCODING_NONE); if (buf == NULL) { xmlFreeParserCtxt(ctxt); return(NULL); } inputStream = xmlNewInputStream(ctxt); if (inputStream == NULL) { xmlFreeParserCtxt(ctxt); return(NULL); } inputStream->filename = xmlMemStrdup(filename); inputStream->buf = buf; inputStream->base = inputStream->buf->buffer->content; inputStream->cur = inputStream->buf->buffer->content; inputStream->end = &inputStream->buf->buffer->content[inputStream->buf->buffer->use]; inputPush(ctxt, inputStream); if ((ctxt->directory == NULL) && (directory == NULL)) directory = xmlParserGetDirectory(filename); if ((ctxt->directory == NULL) && (directory != NULL)) ctxt->directory = directory; ctxt->valid = 0; ctxt->validate = 0; ctxt->loadsubset = 0; ctxt->pedantic = 0; xmlParseDocument(ctxt); if (ctxt->wellFormed) ret = ctxt->myDoc; else { ret = NULL; xmlFreeDoc(ctxt->myDoc); ctxt->myDoc = NULL; } xmlFreeParserCtxt(ctxt); return(ret); } /** * xmlLoadFileContent: * @filename: a file path * * Load a file content into memory. * * Returns a pointer to the 0 terminated string or NULL in case of error */ static xmlChar * xmlLoadFileContent(const char *filename) { #ifdef HAVE_STAT int fd; #else FILE *fd; #endif int len; long size; #ifdef HAVE_STAT struct stat info; #endif xmlChar *content; if (filename == NULL) return (NULL); #ifdef HAVE_STAT if (stat(filename, &info) < 0) return (NULL); #endif #ifdef HAVE_STAT if ((fd = open(filename, O_RDONLY)) < 0) { #else if ((fd = fopen(filename, "rb")) == NULL) { #endif return (NULL); } #ifdef HAVE_STAT size = info.st_size; #else if (fseek(fd, 0, SEEK_END) || (size = ftell(fd)) == EOF || fseek(fd, 0, SEEK_SET)) { /* File operations denied? ok, just close and return failure */ fclose(fd); return (NULL); } #endif content = xmlMalloc(size + 10); if (content == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc of %d byte failed\n", size + 10); return (NULL); } #ifdef HAVE_STAT len = read(fd, content, size); #else len = fread(content, 1, size, fd); #endif if (len < 0) { xmlFree(content); return (NULL); } #ifdef HAVE_STAT close(fd); #else fclose(fd); #endif content[len] = 0; return(content); } /************************************************************************ * * * The XML Catalog parser * * * ************************************************************************/ static xmlCatalogEntryPtr xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename); static void xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer, xmlCatalogEntryPtr parent); static xmlChar * xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID, const xmlChar *sysID); static xmlChar * xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI); /** * xmlGetXMLCatalogEntryType: * @name: the name * * lookup the internal type associated to an XML catalog entry name * * Returns the type associate with that name */ static xmlCatalogEntryType xmlGetXMLCatalogEntryType(const xmlChar *name) { xmlCatalogEntryType type = XML_CATA_NONE; if (xmlStrEqual(name, (const xmlChar *) "system")) type = XML_CATA_SYSTEM; else if (xmlStrEqual(name, (const xmlChar *) "public")) type = XML_CATA_PUBLIC; else if (xmlStrEqual(name, (const xmlChar *) "rewriteSystem")) type = XML_CATA_REWRITE_SYSTEM; else if (xmlStrEqual(name, (const xmlChar *) "delegatePublic")) type = XML_CATA_DELEGATE_PUBLIC; else if (xmlStrEqual(name, (const xmlChar *) "delegateSystem")) type = XML_CATA_DELEGATE_SYSTEM; else if (xmlStrEqual(name, (const xmlChar *) "uri")) type = XML_CATA_URI; else if (xmlStrEqual(name, (const xmlChar *) "rewriteURI")) type = XML_CATA_REWRITE_URI; else if (xmlStrEqual(name, (const xmlChar *) "delegateURI")) type = XML_CATA_DELEGATE_URI; else if (xmlStrEqual(name, (const xmlChar *) "nextCatalog")) type = XML_CATA_NEXT_CATALOG; else if (xmlStrEqual(name, (const xmlChar *) "catalog")) type = XML_CATA_CATALOG; return(type); } /** * xmlParseXMLCatalogOneNode: * @cur: the XML node * @type: the type of Catalog entry * @name: the name of the node * @attrName: the attribute holding the value * @uriAttrName: the attribute holding the URI-Reference * @prefer: the PUBLIC vs. SYSTEM current preference value * * Finishes the examination of an XML tree node of a catalog and build * a Catalog entry from it. * * Returns the new Catalog entry node or NULL in case of error. */ static xmlCatalogEntryPtr xmlParseXMLCatalogOneNode(xmlNodePtr cur, xmlCatalogEntryType type, const xmlChar *name, const xmlChar *attrName, const xmlChar *uriAttrName, xmlCatalogPrefer prefer) { int ok = 1; xmlChar *uriValue; xmlChar *nameValue = NULL; xmlChar *base = NULL; xmlChar *URL = NULL; xmlCatalogEntryPtr ret = NULL; if (attrName != NULL) { nameValue = xmlGetProp(cur, attrName); if (nameValue == NULL) { xmlGenericError(xmlGenericErrorContext, "%s entry lacks '%s'\n", name, attrName); ok = 0; } } uriValue = xmlGetProp(cur, uriAttrName); if (uriValue == NULL) { xmlGenericError(xmlGenericErrorContext, "%s entry lacks '%s'\n", name, uriAttrName); ok = 0; } if (!ok) { if (nameValue != NULL) xmlFree(nameValue); if (uriValue != NULL) xmlFree(uriValue); return(NULL); } base = xmlNodeGetBase(cur->doc, cur); URL = xmlBuildURI(uriValue, base); if (URL != NULL) { if (xmlDebugCatalogs > 1) { if (nameValue != NULL) xmlGenericError(xmlGenericErrorContext, "Found %s: '%s' '%s'\n", name, nameValue, URL); else xmlGenericError(xmlGenericErrorContext, "Found %s: '%s'\n", name, URL); } ret = xmlNewCatalogEntry(type, nameValue, uriValue, URL, prefer); } else { xmlGenericError(xmlGenericErrorContext, "%s entry '%s' broken ?: %s\n", name, uriAttrName, uriValue); } if (nameValue != NULL) xmlFree(nameValue); if (uriValue != NULL) xmlFree(uriValue); if (base != NULL) xmlFree(base); if (URL != NULL) xmlFree(URL); return(ret); } /** * xmlParseXMLCatalogNode: * @cur: the XML node * @prefer: the PUBLIC vs. SYSTEM current preference value * @parent: the parent Catalog entry * * Examines an XML tree node of a catalog and build * a Catalog entry from it adding it to its parent. The examination can * be recursive. */ static void xmlParseXMLCatalogNode(xmlNodePtr cur, xmlCatalogPrefer prefer, xmlCatalogEntryPtr parent) { xmlChar *uri = NULL; xmlChar *URL = NULL; xmlChar *base = NULL; xmlCatalogEntryPtr entry = NULL; if (cur == NULL) return; if (xmlStrEqual(cur->name, BAD_CAST "group")) { xmlChar *prop; prop = xmlGetProp(cur, BAD_CAST "prefer"); if (prop != NULL) { if (xmlStrEqual(prop, BAD_CAST "system")) { prefer = XML_CATA_PREFER_SYSTEM; } else if (xmlStrEqual(prop, BAD_CAST "public")) { prefer = XML_CATA_PREFER_PUBLIC; } else { xmlGenericError(xmlGenericErrorContext, "Invalid value for prefer: '%s'\n", prop); } xmlFree(prop); } /* * Recurse to propagate prefer to the subtree * (xml:base handling is automated) */ xmlParseXMLCatalogNodeList(cur->children, prefer, parent); } else if (xmlStrEqual(cur->name, BAD_CAST "public")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_PUBLIC, BAD_CAST "public", BAD_CAST "publicId", BAD_CAST "uri", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "system")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_SYSTEM, BAD_CAST "system", BAD_CAST "systemId", BAD_CAST "uri", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteSystem")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_SYSTEM, BAD_CAST "rewriteSystem", BAD_CAST "systemIdStartString", BAD_CAST "rewritePrefix", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "delegatePublic")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_PUBLIC, BAD_CAST "delegatePublic", BAD_CAST "publicIdStartString", BAD_CAST "catalog", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "delegateSystem")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_SYSTEM, BAD_CAST "delegateSystem", BAD_CAST "systemIdStartString", BAD_CAST "catalog", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "uri")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_URI, BAD_CAST "uri", BAD_CAST "name", BAD_CAST "uri", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteURI")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_URI, BAD_CAST "rewriteURI", BAD_CAST "uriStartString", BAD_CAST "rewritePrefix", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "delegateURI")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_URI, BAD_CAST "delegateURI", BAD_CAST "uriStartString", BAD_CAST "catalog", prefer); } else if (xmlStrEqual(cur->name, BAD_CAST "nextCatalog")) { entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_NEXT_CATALOG, BAD_CAST "nextCatalog", NULL, BAD_CAST "catalog", prefer); } if ((entry != NULL) && (parent != NULL)) { entry->parent = parent; if (parent->children == NULL) parent->children = entry; else { xmlCatalogEntryPtr prev; prev = parent->children; while (prev->next != NULL) prev = prev->next; prev->next = entry; } } if (base != NULL) xmlFree(base); if (uri != NULL) xmlFree(uri); if (URL != NULL) xmlFree(URL); } /** * xmlParseXMLCatalogNodeList: * @cur: the XML node list of siblings * @prefer: the PUBLIC vs. SYSTEM current preference value * @parent: the parent Catalog entry * * Examines a list of XML sibling nodes of a catalog and build * a list of Catalog entry from it adding it to the parent. * The examination will recurse to examine node subtrees. */ static void xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer, xmlCatalogEntryPtr parent) { while (cur != NULL) { if ((cur->ns != NULL) && (cur->ns->href != NULL) && (xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) { xmlParseXMLCatalogNode(cur, prefer, parent); } cur = cur->next; } /* TODO: sort the list according to REWRITE lengths and prefer value */ } /** * xmlParseXMLCatalogFile: * @prefer: the PUBLIC vs. SYSTEM current preference value * @filename: the filename for the catalog * * Parses the catalog file to extract the XML tree and then analyze the * tree to build a list of Catalog entries corresponding to this catalog * * Returns the resulting Catalog entries list */ static xmlCatalogEntryPtr xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename) { xmlDocPtr doc; xmlNodePtr cur; xmlChar *prop; xmlCatalogEntryPtr parent = NULL; if (filename == NULL) return(NULL); doc = xmlParseCatalogFile((const char *) filename); if (doc == NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Failed to parse catalog %s\n", filename); return(NULL); } if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "%d Parsing catalog %s\n", xmlGetThreadId(), filename); cur = xmlDocGetRootElement(doc); if ((cur != NULL) && (xmlStrEqual(cur->name, BAD_CAST "catalog")) && (cur->ns != NULL) && (cur->ns->href != NULL) && (xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) { parent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, (const xmlChar *)filename, NULL, prefer); if (parent == NULL) { xmlFreeDoc(doc); return(NULL); } prop = xmlGetProp(cur, BAD_CAST "prefer"); if (prop != NULL) { if (xmlStrEqual(prop, BAD_CAST "system")) { prefer = XML_CATA_PREFER_SYSTEM; } else if (xmlStrEqual(prop, BAD_CAST "public")) { prefer = XML_CATA_PREFER_PUBLIC; } else { xmlGenericError(xmlGenericErrorContext, "Invalid value for prefer: '%s'\n", prop); } xmlFree(prop); } cur = cur->children; xmlParseXMLCatalogNodeList(cur, prefer, parent); } else { xmlGenericError(xmlGenericErrorContext, "File %s is not an XML Catalog\n", filename); xmlFreeDoc(doc); return(NULL); } xmlFreeDoc(doc); return(parent); } /** * xmlFetchXMLCatalogFile: * @catal: an existing but incomplete catalog entry * * Fetch and parse the subcatalog referenced by an entry * * Returns 0 in case of success, -1 otherwise */ static int xmlFetchXMLCatalogFile(xmlCatalogEntryPtr catal) { xmlCatalogEntryPtr doc; if (catal == NULL) return(-1); if (catal->URL == NULL) return(-1); if (catal->children != NULL) return(-1); /* * lock the whole catalog for modification */ xmlRMutexLock(xmlCatalogMutex); if (catal->children != NULL) { /* Okay someone else did it in the meantime */ xmlRMutexUnlock(xmlCatalogMutex); return(0); } if (xmlCatalogXMLFiles != NULL) { doc = (xmlCatalogEntryPtr) xmlHashLookup(xmlCatalogXMLFiles, catal->URL); if (doc != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Found %s in file hash\n", catal->URL); if (catal->type == XML_CATA_CATALOG) catal->children = doc->children; else catal->children = doc; catal->dealloc = 0; xmlRMutexUnlock(xmlCatalogMutex); return(0); } if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "%s not found in file hash\n", catal->URL); } /* * Fetch and parse. Note that xmlParseXMLCatalogFile does not * use the existing catalog, there is no recusivity allowed at * that level. */ doc = xmlParseXMLCatalogFile(catal->prefer, catal->URL); if (doc == NULL) { catal->type = XML_CATA_BROKEN_CATALOG; xmlRMutexUnlock(xmlCatalogMutex); return(-1); } if (catal->type == XML_CATA_CATALOG) catal->children = doc->children; else catal->children = doc; doc->dealloc = 1; if (xmlCatalogXMLFiles == NULL) xmlCatalogXMLFiles = xmlHashCreate(10); if (xmlCatalogXMLFiles != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "%s added to file hash\n", catal->URL); xmlHashAddEntry(xmlCatalogXMLFiles, catal->URL, doc); } xmlRMutexUnlock(xmlCatalogMutex); return(0); } /************************************************************************ * * * XML Catalog handling * * * ************************************************************************/ /** * xmlAddXMLCatalog: * @catal: top of an XML catalog * @type: the type of record to add to the catalog * @orig: the system, public or prefix to match (or NULL) * @replace: the replacement value for the match * * Add an entry in the XML catalog, it may overwrite existing but * different entries. * * Returns 0 if successful, -1 otherwise */ static int xmlAddXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *type, const xmlChar *orig, const xmlChar *replace) { xmlCatalogEntryPtr cur; xmlCatalogEntryType typ; int doregister = 0; if ((catal == NULL) || ((catal->type != XML_CATA_CATALOG) && (catal->type != XML_CATA_BROKEN_CATALOG))) return(-1); if (catal->children == NULL) { xmlFetchXMLCatalogFile(catal); } if (catal->children == NULL) doregister = 1; typ = xmlGetXMLCatalogEntryType(type); if (typ == XML_CATA_NONE) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Failed to add unknown element %s to catalog\n", type); return(-1); } cur = catal->children; /* * Might be a simple "update in place" */ if (cur != NULL) { while (cur != NULL) { if ((orig != NULL) && (cur->type == typ) && (xmlStrEqual(orig, cur->name))) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Updating element %s to catalog\n", type); if (cur->value != NULL) xmlFree(cur->value); if (cur->URL != NULL) xmlFree(cur->URL); cur->value = xmlStrdup(replace); cur->URL = xmlStrdup(replace); return(0); } if (cur->next == NULL) break; cur = cur->next; } } if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Adding element %s to catalog\n", type); if (cur == NULL) catal->children = xmlNewCatalogEntry(typ, orig, replace, NULL, catal->prefer); else cur->next = xmlNewCatalogEntry(typ, orig, replace, NULL, catal->prefer); if (doregister) { cur = xmlHashLookup(xmlCatalogXMLFiles, catal->URL); if (cur != NULL) cur->children = catal->children; } return(0); } /** * xmlDelXMLCatalog: * @catal: top of an XML catalog * @value: the value to remove from the catalog * * Remove entries in the XML catalog where the value or the URI * is equal to @value * * Returns the number of entries removed if successful, -1 otherwise */ static int xmlDelXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *value) { xmlCatalogEntryPtr cur; int ret = 0; if ((catal == NULL) || ((catal->type != XML_CATA_CATALOG) && (catal->type != XML_CATA_BROKEN_CATALOG))) return(-1); if (value == NULL) return(-1); if (catal->children == NULL) { xmlFetchXMLCatalogFile(catal); } /* * Scan the children */ cur = catal->children; while (cur != NULL) { if (((cur->name != NULL) && (xmlStrEqual(value, cur->name))) || (xmlStrEqual(value, cur->value))) { if (xmlDebugCatalogs) { if (cur->name != NULL) xmlGenericError(xmlGenericErrorContext, "Removing element %s from catalog\n", cur->name); else xmlGenericError(xmlGenericErrorContext, "Removing element %s from catalog\n", cur->value); } cur->type = XML_CATA_REMOVED; } cur = cur->next; } return(ret); } /** * xmlCatalogXMLResolve: * @catal: a catalog list * @pubId: the public ID string * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier for a * list of catalog entries. * * Implements (or tries to) 7.1. External Identifier Resolution * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html * * Returns the URI of the resource or NULL if not found */ static xmlChar * xmlCatalogXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID, const xmlChar *sysID) { xmlChar *ret = NULL; xmlCatalogEntryPtr cur; int haveDelegate = 0; int haveNext = 0; /* * First tries steps 2/ 3/ 4/ if a system ID is provided. */ if (sysID != NULL) { xmlCatalogEntryPtr rewrite = NULL; int lenrewrite = 0, len; cur = catal; haveDelegate = 0; while (cur != NULL) { switch (cur->type) { case XML_CATA_SYSTEM: if (xmlStrEqual(sysID, cur->name)) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Found system match %s\n", cur->name); return(xmlStrdup(cur->URL)); } break; case XML_CATA_REWRITE_SYSTEM: len = xmlStrlen(cur->name); if ((len > lenrewrite) && (!xmlStrncmp(sysID, cur->name, len))) { lenrewrite = len; rewrite = cur; } break; case XML_CATA_DELEGATE_SYSTEM: if (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name))) haveDelegate++; break; case XML_CATA_NEXT_CATALOG: haveNext++; break; default: break; } cur = cur->next; } if (rewrite != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Using rewriting rule %s\n", rewrite->name); ret = xmlStrdup(rewrite->URL); if (ret != NULL) ret = xmlStrcat(ret, &sysID[lenrewrite]); return(ret); } if (haveDelegate) { const xmlChar *delegates[MAX_DELEGATE]; int nbList = 0, i; /* * Assume the entries have been sorted by decreasing substring * matches when the list was produced. */ cur = catal; while (cur != NULL) { if ((cur->type == XML_CATA_DELEGATE_SYSTEM) && (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))) { for (i = 0;i < nbList;i++) if (xmlStrEqual(cur->URL, delegates[i])) break; if (i < nbList) { cur = cur->next; continue; } if (nbList < MAX_DELEGATE) delegates[nbList++] = cur->URL; if (cur->children == NULL) { xmlFetchXMLCatalogFile(cur); } if (cur->children != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Trying system delegate %s\n", cur->URL); ret = xmlCatalogListXMLResolve( cur->children, NULL, sysID); if (ret != NULL) return(ret); } } cur = cur->next; } /* * Apply the cut algorithm explained in 4/ */ return(XML_CATAL_BREAK); } } /* * Then tries 5/ 6/ if a public ID is provided */ if (pubID != NULL) { cur = catal; haveDelegate = 0; while (cur != NULL) { switch (cur->type) { case XML_CATA_PUBLIC: if (xmlStrEqual(pubID, cur->name)) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Found public match %s\n", cur->name); return(xmlStrdup(cur->URL)); } break; case XML_CATA_DELEGATE_PUBLIC: if (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)) && (cur->prefer == XML_CATA_PREFER_PUBLIC)) haveDelegate++; break; case XML_CATA_NEXT_CATALOG: if (sysID == NULL) haveNext++; break; default: break; } cur = cur->next; } if (haveDelegate) { const xmlChar *delegates[MAX_DELEGATE]; int nbList = 0, i; /* * Assume the entries have been sorted by decreasing substring * matches when the list was produced. */ cur = catal; while (cur != NULL) { if ((cur->type == XML_CATA_DELEGATE_PUBLIC) && (cur->prefer == XML_CATA_PREFER_PUBLIC) && (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)))) { for (i = 0;i < nbList;i++) if (xmlStrEqual(cur->URL, delegates[i])) break; if (i < nbList) { cur = cur->next; continue; } if (nbList < MAX_DELEGATE) delegates[nbList++] = cur->URL; if (cur->children == NULL) { xmlFetchXMLCatalogFile(cur); } if (cur->children != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Trying public delegate %s\n", cur->URL); ret = xmlCatalogListXMLResolve( cur->children, pubID, NULL); if (ret != NULL) return(ret); } } cur = cur->next; } /* * Apply the cut algorithm explained in 4/ */ return(XML_CATAL_BREAK); } } if (haveNext) { cur = catal; while (cur != NULL) { if (cur->type == XML_CATA_NEXT_CATALOG) { if (cur->children == NULL) { xmlFetchXMLCatalogFile(cur); } if (cur->children != NULL) { ret = xmlCatalogListXMLResolve(cur->children, pubID, sysID); if (ret != NULL) return(ret); } } cur = cur->next; } } return(NULL); } /** * xmlCatalogXMLResolveURI: * @catal: a catalog list * @URI: the URI * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier for a * list of catalog entries. * * Implements (or tries to) 7.2.2. URI Resolution * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html * * Returns the URI of the resource or NULL if not found */ static xmlChar * xmlCatalogXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) { xmlChar *ret = NULL; xmlCatalogEntryPtr cur; int haveDelegate = 0; int haveNext = 0; xmlCatalogEntryPtr rewrite = NULL; int lenrewrite = 0, len; if (catal == NULL) return(NULL); if (URI == NULL) return(NULL); /* * First tries steps 2/ 3/ 4/ if a system ID is provided. */ cur = catal; haveDelegate = 0; while (cur != NULL) { switch (cur->type) { case XML_CATA_URI: if (xmlStrEqual(URI, cur->name)) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Found URI match %s\n", cur->name); return(xmlStrdup(cur->URL)); } break; case XML_CATA_REWRITE_URI: len = xmlStrlen(cur->name); if ((len > lenrewrite) && (!xmlStrncmp(URI, cur->name, len))) { lenrewrite = len; rewrite = cur; } break; case XML_CATA_DELEGATE_URI: if (!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name))) haveDelegate++; break; case XML_CATA_NEXT_CATALOG: haveNext++; break; default: break; } cur = cur->next; } if (rewrite != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Using rewriting rule %s\n", rewrite->name); ret = xmlStrdup(rewrite->URL); if (ret != NULL) ret = xmlStrcat(ret, &URI[lenrewrite]); return(ret); } if (haveDelegate) { const xmlChar *delegates[MAX_DELEGATE]; int nbList = 0, i; /* * Assume the entries have been sorted by decreasing substring * matches when the list was produced. */ cur = catal; while (cur != NULL) { if ((cur->type == XML_CATA_DELEGATE_SYSTEM) && (!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))) { for (i = 0;i < nbList;i++) if (xmlStrEqual(cur->URL, delegates[i])) break; if (i < nbList) { cur = cur->next; continue; } if (nbList < MAX_DELEGATE) delegates[nbList++] = cur->URL; if (cur->children == NULL) { xmlFetchXMLCatalogFile(cur); } if (cur->children != NULL) { if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Trying URI delegate %s\n", cur->URL); ret = xmlCatalogListXMLResolveURI( cur->children, URI); if (ret != NULL) return(ret); } } cur = cur->next; } /* * Apply the cut algorithm explained in 4/ */ return(XML_CATAL_BREAK); } if (haveNext) { cur = catal; while (cur != NULL) { if (cur->type == XML_CATA_NEXT_CATALOG) { if (cur->children == NULL) { xmlFetchXMLCatalogFile(cur); } if (cur->children != NULL) { ret = xmlCatalogListXMLResolveURI(cur->children, URI); if (ret != NULL) return(ret); } } cur = cur->next; } } return(NULL); } /** * xmlCatalogListXMLResolve: * @catal: a catalog list * @pubId: the public ID string * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier for a * list of catalogs * * Implements (or tries to) 7.1. External Identifier Resolution * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html * * Returns the URI of the resource or NULL if not found */ static xmlChar * xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID, const xmlChar *sysID) { xmlChar *ret = NULL; xmlChar *urnID = NULL; if (catal == NULL) return(NULL); if ((pubID == NULL) && (sysID == NULL)) return(NULL); if (!xmlStrncmp(pubID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) { urnID = xmlCatalogUnWrapURN(pubID); if (xmlDebugCatalogs) { if (urnID == NULL) xmlGenericError(xmlGenericErrorContext, "Public URN ID %s expanded to NULL\n", pubID); else xmlGenericError(xmlGenericErrorContext, "Public URN ID expanded to %s\n", urnID); } ret = xmlCatalogListXMLResolve(catal, urnID, sysID); if (urnID != NULL) xmlFree(urnID); return(ret); } if (!xmlStrncmp(sysID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) { urnID = xmlCatalogUnWrapURN(sysID); if (xmlDebugCatalogs) { if (urnID == NULL) xmlGenericError(xmlGenericErrorContext, "System URN ID %s expanded to NULL\n", sysID); else xmlGenericError(xmlGenericErrorContext, "System URN ID expanded to %s\n", urnID); } if (pubID == NULL) ret = xmlCatalogListXMLResolve(catal, urnID, NULL); else if (xmlStrEqual(pubID, urnID)) ret = xmlCatalogListXMLResolve(catal, pubID, NULL); else { ret = xmlCatalogListXMLResolve(catal, pubID, NULL); } if (urnID != NULL) xmlFree(urnID); return(ret); } while (catal != NULL) { if (catal->type == XML_CATA_CATALOG) { if (catal->children == NULL) { xmlFetchXMLCatalogFile(catal); } if (catal->children != NULL) { ret = xmlCatalogXMLResolve(catal->children, pubID, sysID); if (ret != NULL) return(ret); } } catal = catal->next; } return(ret); } /** * xmlCatalogListXMLResolveURI: * @catal: a catalog list * @URI: the URI * * Do a complete resolution lookup of an URI for a list of catalogs * * Implements (or tries to) 7.2. URI Resolution * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html * * Returns the URI of the resource or NULL if not found */ static xmlChar * xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) { xmlChar *ret = NULL; xmlChar *urnID = NULL; if (catal == NULL) return(NULL); if (URI == NULL) return(NULL); if (!xmlStrncmp(URI, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) { urnID = xmlCatalogUnWrapURN(URI); if (xmlDebugCatalogs) { if (urnID == NULL) xmlGenericError(xmlGenericErrorContext, "URN ID %s expanded to NULL\n", URI); else xmlGenericError(xmlGenericErrorContext, "URN ID expanded to %s\n", urnID); } ret = xmlCatalogListXMLResolve(catal, urnID, NULL); if (urnID != NULL) xmlFree(urnID); return(ret); } while (catal != NULL) { if (catal->type == XML_CATA_CATALOG) { if (catal->children == NULL) { xmlFetchXMLCatalogFile(catal); } if (catal->children != NULL) { ret = xmlCatalogXMLResolveURI(catal->children, URI); if (ret != NULL) return(ret); } } catal = catal->next; } return(ret); } /************************************************************************ * * * The SGML Catalog parser * * * ************************************************************************/ #define RAW *cur #define NEXT cur++; #define SKIP(x) cur += x; #define SKIP_BLANKS while (IS_BLANK(*cur)) NEXT; /** * xmlParseSGMLCatalogComment: * @cur: the current character * * Skip a comment in an SGML catalog * * Returns new current character */ static const xmlChar * xmlParseSGMLCatalogComment(const xmlChar *cur) { if ((cur[0] != '-') || (cur[1] != '-')) return(cur); SKIP(2); while ((cur[0] != 0) && ((cur[0] != '-') || ((cur[1] != '-')))) NEXT; if (cur[0] == 0) { return(NULL); } return(cur + 2); } /** * xmlParseSGMLCatalogPubid: * @cur: the current character * @id: the return location * * Parse an SGML catalog ID * * Returns new current character and store the value in @id */ static const xmlChar * xmlParseSGMLCatalogPubid(const xmlChar *cur, xmlChar **id) { xmlChar *buf = NULL; int len = 0; int size = 50; xmlChar stop; int count = 0; *id = NULL; if (RAW == '"') { NEXT; stop = '"'; } else if (RAW == '\'') { NEXT; stop = '\''; } else { stop = ' '; } buf = (xmlChar *) xmlMalloc(size * sizeof(xmlChar)); if (buf == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc of %d byte failed\n", size); return(NULL); } while (xmlIsPubidChar(*cur)) { if ((*cur == stop) && (stop != ' ')) break; if ((stop == ' ') && (IS_BLANK(*cur))) break; if (len + 1 >= size) { size *= 2; buf = (xmlChar *) xmlRealloc(buf, size * sizeof(xmlChar)); if (buf == NULL) { xmlGenericError(xmlGenericErrorContext, "realloc of %d byte failed\n", size); return(NULL); } } buf[len++] = *cur; count++; NEXT; } buf[len] = 0; if (stop == ' ') { if (!IS_BLANK(*cur)) { xmlFree(buf); return(NULL); } } else { if (*cur != stop) { xmlFree(buf); return(NULL); } NEXT; } *id = buf; return(cur); } /** * xmlParseSGMLCatalogName: * @cur: the current character * @name: the return location * * Parse an SGML catalog name * * Returns new current character and store the value in @name */ static const xmlChar * xmlParseSGMLCatalogName(const xmlChar *cur, xmlChar **name) { xmlChar buf[XML_MAX_NAMELEN + 5]; int len = 0; int c; *name = NULL; /* * Handler for more complex cases */ c = *cur; if ((!IS_LETTER(c) && (c != '_') && (c != ':'))) { return(NULL); } while (((IS_LETTER(c)) || (IS_DIGIT(c)) || (c == '.') || (c == '-') || (c == '_') || (c == ':'))) { buf[len++] = c; cur++; c = *cur; if (len >= XML_MAX_NAMELEN) return(NULL); } *name = xmlStrndup(buf, len); return(cur); } /** * xmlGetSGMLCatalogEntryType: * @name: the entry name * * Get the Catalog entry type for a given SGML Catalog name * * Returns Catalog entry type */ static xmlCatalogEntryType xmlGetSGMLCatalogEntryType(const xmlChar *name) { xmlCatalogEntryType type = XML_CATA_NONE; if (xmlStrEqual(name, (const xmlChar *) "SYSTEM")) type = SGML_CATA_SYSTEM; else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC")) type = SGML_CATA_PUBLIC; else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE")) type = SGML_CATA_DELEGATE; else if (xmlStrEqual(name, (const xmlChar *) "ENTITY")) type = SGML_CATA_ENTITY; else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE")) type = SGML_CATA_DOCTYPE; else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE")) type = SGML_CATA_LINKTYPE; else if (xmlStrEqual(name, (const xmlChar *) "NOTATION")) type = SGML_CATA_NOTATION; else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL")) type = SGML_CATA_SGMLDECL; else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT")) type = SGML_CATA_DOCUMENT; else if (xmlStrEqual(name, (const xmlChar *) "CATALOG")) type = SGML_CATA_CATALOG; else if (xmlStrEqual(name, (const xmlChar *) "BASE")) type = SGML_CATA_BASE; else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE")) type = SGML_CATA_DELEGATE; return(type); } /** * xmlParseSGMLCatalog: * @catal: the SGML Catalog * @value: the content of the SGML Catalog serialization * @file: the filepath for the catalog * @super: should this be handled as a Super Catalog in which case * parsing is not recursive * * Parse an SGML catalog content and fill up the @catal hash table with * the new entries found. * * Returns 0 in case of success, -1 in case of error. */ static int xmlParseSGMLCatalog(xmlCatalogPtr catal, const xmlChar *value, const char *file, int super) { const xmlChar *cur = value; xmlChar *base = NULL; int res; if ((cur == NULL) || (file == NULL)) return(-1); base = xmlStrdup((const xmlChar *) file); while ((cur != NULL) && (cur[0] != 0)) { SKIP_BLANKS; if (cur[0] == 0) break; if ((cur[0] == '-') && (cur[1] == '-')) { cur = xmlParseSGMLCatalogComment(cur); if (cur == NULL) { /* error */ break; } } else { xmlChar *sysid = NULL; xmlChar *name = NULL; xmlCatalogEntryType type = XML_CATA_NONE; cur = xmlParseSGMLCatalogName(cur, &name); if (name == NULL) { /* error */ break; } if (!IS_BLANK(*cur)) { /* error */ break; } SKIP_BLANKS; if (xmlStrEqual(name, (const xmlChar *) "SYSTEM")) type = SGML_CATA_SYSTEM; else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC")) type = SGML_CATA_PUBLIC; else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE")) type = SGML_CATA_DELEGATE; else if (xmlStrEqual(name, (const xmlChar *) "ENTITY")) type = SGML_CATA_ENTITY; else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE")) type = SGML_CATA_DOCTYPE; else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE")) type = SGML_CATA_LINKTYPE; else if (xmlStrEqual(name, (const xmlChar *) "NOTATION")) type = SGML_CATA_NOTATION; else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL")) type = SGML_CATA_SGMLDECL; else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT")) type = SGML_CATA_DOCUMENT; else if (xmlStrEqual(name, (const xmlChar *) "CATALOG")) type = SGML_CATA_CATALOG; else if (xmlStrEqual(name, (const xmlChar *) "BASE")) type = SGML_CATA_BASE; else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE")) type = SGML_CATA_DELEGATE; else if (xmlStrEqual(name, (const xmlChar *) "OVERRIDE")) { xmlFree(name); cur = xmlParseSGMLCatalogName(cur, &name); if (name == NULL) { /* error */ break; } xmlFree(name); continue; } xmlFree(name); name = NULL; switch(type) { case SGML_CATA_ENTITY: if (*cur == '%') type = SGML_CATA_PENTITY; case SGML_CATA_PENTITY: case SGML_CATA_DOCTYPE: case SGML_CATA_LINKTYPE: case SGML_CATA_NOTATION: cur = xmlParseSGMLCatalogName(cur, &name); if (cur == NULL) { /* error */ break; } if (!IS_BLANK(*cur)) { /* error */ break; } SKIP_BLANKS; cur = xmlParseSGMLCatalogPubid(cur, &sysid); if (cur == NULL) { /* error */ break; } break; case SGML_CATA_PUBLIC: case SGML_CATA_SYSTEM: case SGML_CATA_DELEGATE: cur = xmlParseSGMLCatalogPubid(cur, &name); if (cur == NULL) { /* error */ break; } if (!IS_BLANK(*cur)) { /* error */ break; } SKIP_BLANKS; cur = xmlParseSGMLCatalogPubid(cur, &sysid); if (cur == NULL) { /* error */ break; } break; case SGML_CATA_BASE: case SGML_CATA_CATALOG: case SGML_CATA_DOCUMENT: case SGML_CATA_SGMLDECL: cur = xmlParseSGMLCatalogPubid(cur, &sysid); if (cur == NULL) { /* error */ break; } break; default: break; } if (cur == NULL) { if (name != NULL) xmlFree(name); if (sysid != NULL) xmlFree(sysid); break; } else if (type == SGML_CATA_BASE) { if (base != NULL) xmlFree(base); base = xmlStrdup(sysid); } else if ((type == SGML_CATA_PUBLIC) || (type == SGML_CATA_SYSTEM)) { xmlChar *filename; filename = xmlBuildURI(sysid, base); if (filename != NULL) { xmlCatalogEntryPtr entry; entry = xmlNewCatalogEntry(type, name, filename, NULL, XML_CATA_PREFER_NONE); res = xmlHashAddEntry(catal->sgml, name, entry); if (res < 0) { xmlFreeCatalogEntry(entry); } xmlFree(filename); } } else if (type == SGML_CATA_CATALOG) { if (super) { xmlCatalogEntryPtr entry; entry = xmlNewCatalogEntry(type, sysid, NULL, NULL, XML_CATA_PREFER_NONE); res = xmlHashAddEntry(catal->sgml, sysid, entry); if (res < 0) { xmlFreeCatalogEntry(entry); } } else { xmlChar *filename; filename = xmlBuildURI(sysid, base); if (filename != NULL) { xmlExpandCatalog(catal, (const char *)filename); xmlFree(filename); } } } /* * drop anything else we won't handle it */ if (name != NULL) xmlFree(name); if (sysid != NULL) xmlFree(sysid); } } if (base != NULL) xmlFree(base); if (cur == NULL) return(-1); return(0); } /************************************************************************ * * * SGML Catalog handling * * * ************************************************************************/ /** * xmlCatalogGetSGMLPublic: * @catal: an SGML catalog hash * @pubId: the public ID string * * Try to lookup the system ID associated to a public ID * * Returns the system ID if found or NULL otherwise. */ static const xmlChar * xmlCatalogGetSGMLPublic(xmlHashTablePtr catal, const xmlChar *pubID) { xmlCatalogEntryPtr entry; if (catal == NULL) return(NULL); entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, pubID); if (entry == NULL) return(NULL); if (entry->type == SGML_CATA_PUBLIC) return(entry->URL); return(NULL); } /** * xmlCatalogGetSGMLSystem: * @catal: an SGML catalog hash * @sysId: the public ID string * * Try to lookup the catalog local reference for a system ID * * Returns the system ID if found or NULL otherwise. */ static const xmlChar * xmlCatalogGetSGMLSystem(xmlHashTablePtr catal, const xmlChar *sysID) { xmlCatalogEntryPtr entry; if (catal == NULL) return(NULL); entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, sysID); if (entry == NULL) return(NULL); if (entry->type == SGML_CATA_SYSTEM) return(entry->URL); return(NULL); } /** * xmlCatalogSGMLResolve: * @catal: the SGML catalog * @pubId: the public ID string * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier * * Returns the URI of the resource or NULL if not found */ static const xmlChar * xmlCatalogSGMLResolve(xmlCatalogPtr catal, const xmlChar *pubID, const xmlChar *sysID) { const xmlChar *ret = NULL; if (catal->sgml == NULL) return(NULL); if (pubID != NULL) ret = xmlCatalogGetSGMLPublic(catal->sgml, pubID); if (ret != NULL) return(ret); if (sysID != NULL) ret = xmlCatalogGetSGMLSystem(catal->sgml, sysID); return(NULL); } /************************************************************************ * * * Specific Public interfaces * * * ************************************************************************/ /** * xmlLoadSGMLSuperCatalog: * @filename: a file path * * Load an SGML super catalog. It won't expand CATALOG or DELEGATE * references. This is only needed for manipulating SGML Super Catalogs * like adding and removing CATALOG or DELEGATE entries. * * Returns the catalog parsed or NULL in case of error */ xmlCatalogPtr xmlLoadSGMLSuperCatalog(const char *filename) { xmlChar *content; xmlCatalogPtr catal; int ret; content = xmlLoadFileContent(filename); if (content == NULL) return(NULL); catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer); if (catal == NULL) { xmlFree(content); return(NULL); } ret = xmlParseSGMLCatalog(catal, content, filename, 1); xmlFree(content); if (ret < 0) { xmlFreeCatalog(catal); return(NULL); } return (catal); } /** * xmlLoadACatalog: * @filename: a file path * * Load the catalog and build the associated data structures. * This can be either an XML Catalog or an SGML Catalog * It will recurse in SGML CATALOG entries. On the other hand XML * Catalogs are not handled recursively. * * Returns the catalog parsed or NULL in case of error */ xmlCatalogPtr xmlLoadACatalog(const char *filename) { xmlChar *content; xmlChar *first; xmlCatalogPtr catal; int ret; content = xmlLoadFileContent(filename); if (content == NULL) return(NULL); first = content; while ((*first != 0) && (*first != '-') && (*first != '<') && (!(((*first >= 'A') && (*first <= 'Z')) || ((*first >= 'a') && (*first <= 'z'))))) first++; if (*first != '<') { catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer); if (catal == NULL) { xmlFree(content); return(NULL); } ret = xmlParseSGMLCatalog(catal, content, filename, 0); if (ret < 0) { xmlFreeCatalog(catal); xmlFree(content); return(NULL); } } else { catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE, xmlCatalogDefaultPrefer); if (catal == NULL) { xmlFree(content); return(NULL); } catal->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, NULL, BAD_CAST filename, xmlCatalogDefaultPrefer); } xmlFree(content); return (catal); } /** * xmlExpandCatalog: * @catal: a catalog * @filename: a file path * * Load the catalog and expand the existing catal structure. * This can be either an XML Catalog or an SGML Catalog * * Returns 0 in case of success, -1 in case of error */ static int xmlExpandCatalog(xmlCatalogPtr catal, const char *filename) { int ret; if ((catal == NULL) || (filename == NULL)) return(-1); if (catal->type == XML_SGML_CATALOG_TYPE) { xmlChar *content; content = xmlLoadFileContent(filename); if (content == NULL) return(-1); ret = xmlParseSGMLCatalog(catal, content, filename, 0); if (ret < 0) { xmlFree(content); return(-1); } xmlFree(content); } else { xmlCatalogEntryPtr tmp, cur; tmp = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, NULL, BAD_CAST filename, xmlCatalogDefaultPrefer); cur = catal->xml; if (cur == NULL) { catal->xml = tmp; } else { while (cur->next != NULL) cur = cur->next; cur->next = tmp; } } return (0); } /** * xmlACatalogResolveSystem: * @catal: a Catalog * @sysId: the public ID string * * Try to lookup the catalog resource for a system ID * * Returns the system ID if found or NULL otherwise, the value returned * must be freed by the caller. */ xmlChar * xmlACatalogResolveSystem(xmlCatalogPtr catal, const xmlChar *sysID) { xmlChar *ret = NULL; if ((sysID == NULL) || (catal == NULL)) return(NULL); if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Resolve sysID %s\n", sysID); if (catal->type == XML_XML_CATALOG_TYPE) { ret = xmlCatalogListXMLResolve(catal->xml, NULL, sysID); if (ret == XML_CATAL_BREAK) ret = NULL; } else { const xmlChar *sgml; sgml = xmlCatalogGetSGMLSystem(catal->sgml, sysID); if (sgml != NULL) ret = xmlStrdup(sgml); } return(ret); } /** * xmlACatalogResolvePublic: * @catal: a Catalog * @pubId: the public ID string * * Try to lookup the system ID associated to a public ID in that catalog * * Returns the system ID if found or NULL otherwise, the value returned * must be freed by the caller. */ xmlChar * xmlACatalogResolvePublic(xmlCatalogPtr catal, const xmlChar *pubID) { xmlChar *ret = NULL; if ((pubID == NULL) || (catal == NULL)) return(NULL); if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Resolve pubID %s\n", pubID); if (catal->type == XML_XML_CATALOG_TYPE) { ret = xmlCatalogListXMLResolve(catal->xml, pubID, NULL); if (ret == XML_CATAL_BREAK) ret = NULL; } else { const xmlChar *sgml; sgml = xmlCatalogGetSGMLPublic(catal->sgml, pubID); if (sgml != NULL) ret = xmlStrdup(sgml); } return(ret); } /** * xmlACatalogResolve: * @catal: a Catalog * @pubId: the public ID string * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier * * Returns the URI of the resource or NULL if not found, it must be freed * by the caller. */ xmlChar * xmlACatalogResolve(xmlCatalogPtr catal, const xmlChar * pubID, const xmlChar * sysID) { xmlChar *ret = NULL; if (((pubID == NULL) && (sysID == NULL)) || (catal == NULL)) return (NULL); if (xmlDebugCatalogs) { if (pubID != NULL) { xmlGenericError(xmlGenericErrorContext, "Resolve: pubID %s\n", pubID); } else { xmlGenericError(xmlGenericErrorContext, "Resolve: sysID %s\n", sysID); } } if (catal->type == XML_XML_CATALOG_TYPE) { ret = xmlCatalogListXMLResolve(catal->xml, pubID, sysID); if (ret == XML_CATAL_BREAK) ret = NULL; } else { const xmlChar *sgml; sgml = xmlCatalogSGMLResolve(catal, pubID, sysID); if (sgml != NULL) ret = xmlStrdup(sgml); } return (ret); } /** * xmlACatalogResolveURI: * @catal: a Catalog * @pubId: the URI * * Do a complete resolution lookup of an URI * * Returns the URI of the resource or NULL if not found, it must be freed * by the caller. */ xmlChar * xmlACatalogResolveURI(xmlCatalogPtr catal, const xmlChar *URI) { xmlChar *ret = NULL; if ((URI == NULL) || (catal == NULL)) return(NULL); if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Resolve URI %s\n", URI); if (catal->type == XML_XML_CATALOG_TYPE) { ret = xmlCatalogListXMLResolveURI(catal->xml, URI); if (ret == XML_CATAL_BREAK) ret = NULL; } else { const xmlChar *sgml; sgml = xmlCatalogSGMLResolve(catal, NULL, URI); if (sgml != NULL) sgml = xmlStrdup(sgml); } return(ret); } /** * xmlACatalogDump: * @catal: a Catalog * @out: the file. * * Free up all the memory associated with catalogs */ void xmlACatalogDump(xmlCatalogPtr catal, FILE *out) { if ((out == NULL) || (catal == NULL)) return; if (catal->type == XML_XML_CATALOG_TYPE) { xmlDumpXMLCatalog(out, catal->xml); } else { xmlHashScan(catal->sgml, (xmlHashScanner) xmlCatalogDumpEntry, out); } } /** * xmlACatalogAdd: * @catal: a Catalog * @type: the type of record to add to the catalog * @orig: the system, public or prefix to match * @replace: the replacement value for the match * * Add an entry in the catalog, it may overwrite existing but * different entries. * * Returns 0 if successful, -1 otherwise */ int xmlACatalogAdd(xmlCatalogPtr catal, const xmlChar * type, const xmlChar * orig, const xmlChar * replace) { int res = -1; if (catal == NULL) return(-1); if (catal->type == XML_XML_CATALOG_TYPE) { res = xmlAddXMLCatalog(catal->xml, type, orig, replace); } else { xmlCatalogEntryType cattype; cattype = xmlGetSGMLCatalogEntryType(type); if (cattype != XML_CATA_NONE) { xmlCatalogEntryPtr entry; entry = xmlNewCatalogEntry(cattype, orig, replace, NULL, XML_CATA_PREFER_NONE); if (catal->sgml == NULL) catal->sgml = xmlHashCreate(10); res = xmlHashAddEntry(catal->sgml, orig, entry); } } return (res); } /** * xmlACatalogRemove: * @catal: a Catalog * @value: the value to remove * * Remove an entry from the catalog * * Returns the number of entries removed if successful, -1 otherwise */ int xmlACatalogRemove(xmlCatalogPtr catal, const xmlChar *value) { int res = -1; if ((catal == NULL) || (value == NULL)) return(-1); if (catal->type == XML_XML_CATALOG_TYPE) { res = xmlDelXMLCatalog(catal->xml, value); } else { res = xmlHashRemoveEntry(catal->sgml, value, (xmlHashDeallocator) xmlFreeCatalogEntry); if (res == 0) res = 1; } return(res); } /** * xmlNewCatalog: * @sgml: should this create an SGML catalog * * create a new Catalog. * * Returns the xmlCatalogPtr or NULL in case of error */ xmlCatalogPtr xmlNewCatalog(int sgml) { xmlCatalogPtr catal = NULL; if (sgml) { catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer); if ((catal != NULL) && (catal->sgml == NULL)) catal->sgml = xmlHashCreate(10); } else catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE, xmlCatalogDefaultPrefer); return(catal); } /** * xmlCatalogIsEmpty: * @catal: should this create an SGML catalog * * Check is a catalog is empty * * Returns 1 if the catalog is empty, 0 if not, amd -1 in case of error. */ int xmlCatalogIsEmpty(xmlCatalogPtr catal) { if (catal == NULL) return(-1); if (catal->type == XML_XML_CATALOG_TYPE) { if (catal->xml == NULL) return(1); if ((catal->xml->type != XML_CATA_CATALOG) && (catal->xml->type != XML_CATA_BROKEN_CATALOG)) return(-1); if (catal->xml->children == NULL) return(1); return(0); } else { int res; if (catal->sgml == NULL) return(1); res = xmlHashSize(catal->sgml); if (res == 0) return(1); if (res < 0) return(-1); } return(0); } /************************************************************************ * * * Public interfaces manipulating the global shared default catalog * * * ************************************************************************/ /** * xmlInitializeCatalogData: * * Do the catalog initialization only of global data, doesn't try to load * any catalog actually. * this function is not thread safe, catalog initialization should * preferably be done once at startup */ static void xmlInitializeCatalogData(void) { if (xmlCatalogInitialized != 0) return; if (getenv("XML_DEBUG_CATALOG")) xmlDebugCatalogs = 1; xmlCatalogMutex = xmlNewRMutex(); xmlCatalogInitialized = 1; } /** * xmlInitializeCatalog: * * Do the catalog initialization. * this function is not thread safe, catalog initialization should * preferably be done once at startup */ void xmlInitializeCatalog(void) { if (xmlCatalogInitialized != 0) return; xmlInitializeCatalogData(); xmlRMutexLock(xmlCatalogMutex); if (getenv("XML_DEBUG_CATALOG")) xmlDebugCatalogs = 1; if (xmlDefaultCatalog == NULL) { const char *catalogs; xmlCatalogPtr catal; catalogs = (const char *) getenv("XML_CATALOG_FILES"); if (catalogs == NULL) catalogs = XML_XML_DEFAULT_CATALOG; catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE, xmlCatalogDefaultPrefer); if (catal != NULL) { catal->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, NULL, BAD_CAST catalogs, xmlCatalogDefaultPrefer); xmlDefaultCatalog = catal; } } xmlRMutexUnlock(xmlCatalogMutex); } /** * xmlLoadCatalog: * @filename: a file path * * Load the catalog and makes its definitions effective for the default * external entity loader. It will recurse in SGML CATALOG entries. * this function is not thread safe, catalog initialization should * preferably be done once at startup * * Returns 0 in case of success -1 in case of error */ int xmlLoadCatalog(const char *filename) { int ret; xmlCatalogPtr catal; if (!xmlCatalogInitialized) xmlInitializeCatalogData(); xmlRMutexLock(xmlCatalogMutex); if (xmlDefaultCatalog == NULL) { catal = xmlLoadACatalog(filename); if (catal == NULL) return(-1); xmlDefaultCatalog = catal; xmlRMutexUnlock(xmlCatalogMutex); return(0); } ret = xmlExpandCatalog(xmlDefaultCatalog, filename); xmlRMutexUnlock(xmlCatalogMutex); return(ret); } /** * xmlLoadCatalogs: * @paths: a list of file path separated by ':' or spaces * * Load the catalogs and makes their definitions effective for the default * external entity loader. * this function is not thread safe, catalog initialization should * preferably be done once at startup */ void xmlLoadCatalogs(const char *pathss) { const char *cur; const char *paths; xmlChar *path; if (pathss == NULL) return; cur = pathss; while ((cur != NULL) && (*cur != 0)) { while (IS_BLANK(*cur)) cur++; if (*cur != 0) { paths = cur; while ((*cur != 0) && (*cur != ':') && (!IS_BLANK(*cur))) cur++; path = xmlStrndup((const xmlChar *)paths, cur - paths); if (path != NULL) { xmlLoadCatalog((const char *) path); xmlFree(path); } } while (*cur == ':') cur++; } } /** * xmlCatalogCleanup: * * Free up all the memory associated with catalogs */ void xmlCatalogCleanup(void) { if (xmlCatalogInitialized == 0) return; xmlRMutexLock(xmlCatalogMutex); if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Catalogs cleanup\n"); if (xmlCatalogXMLFiles != NULL) xmlHashFree(xmlCatalogXMLFiles, (xmlHashDeallocator)xmlFreeCatalogHashEntryList); xmlCatalogXMLFiles = NULL; if (xmlDefaultCatalog != NULL) xmlFreeCatalog(xmlDefaultCatalog); xmlDefaultCatalog = NULL; xmlDebugCatalogs = 0; xmlCatalogInitialized = 0; xmlRMutexUnlock(xmlCatalogMutex); xmlFreeRMutex(xmlCatalogMutex); } /** * xmlCatalogResolveSystem: * @sysId: the public ID string * * Try to lookup the catalog resource for a system ID * * Returns the system ID if found or NULL otherwise, the value returned * must be freed by the caller. */ xmlChar * xmlCatalogResolveSystem(const xmlChar *sysID) { xmlChar *ret; if (!xmlCatalogInitialized) xmlInitializeCatalog(); ret = xmlACatalogResolveSystem(xmlDefaultCatalog, sysID); return(ret); } /** * xmlCatalogResolvePublic: * @pubId: the public ID string * * Try to lookup the system ID associated to a public ID * * Returns the system ID if found or NULL otherwise, the value returned * must be freed by the caller. */ xmlChar * xmlCatalogResolvePublic(const xmlChar *pubID) { xmlChar *ret; if (!xmlCatalogInitialized) xmlInitializeCatalog(); ret = xmlACatalogResolvePublic(xmlDefaultCatalog, pubID); return(ret); } /** * xmlCatalogResolve: * @pubId: the public ID string * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier * * Returns the URI of the resource or NULL if not found, it must be freed * by the caller. */ xmlChar * xmlCatalogResolve(const xmlChar *pubID, const xmlChar *sysID) { xmlChar *ret; if (!xmlCatalogInitialized) xmlInitializeCatalog(); ret = xmlACatalogResolve(xmlDefaultCatalog, pubID, sysID); return(ret); } /** * xmlCatalogResolveURI: * @pubId: the URI * * Do a complete resolution lookup of an URI * * Returns the URI of the resource or NULL if not found, it must be freed * by the caller. */ xmlChar * xmlCatalogResolveURI(const xmlChar *URI) { xmlChar *ret; if (!xmlCatalogInitialized) xmlInitializeCatalog(); ret = xmlACatalogResolveURI(xmlDefaultCatalog, URI); return(ret); } /** * xmlCatalogDump: * @out: the file. * * Free up all the memory associated with catalogs */ void xmlCatalogDump(FILE *out) { if (out == NULL) return; if (!xmlCatalogInitialized) xmlInitializeCatalog(); xmlACatalogDump(xmlDefaultCatalog, out); } /** * xmlCatalogAdd: * @type: the type of record to add to the catalog * @orig: the system, public or prefix to match * @replace: the replacement value for the match * * Add an entry in the catalog, it may overwrite existing but * different entries. * If called before any other catalo routine, allows to override the * default shared catalog put in place by xmlInitializeCatalog(); * * Returns 0 if successful, -1 otherwise */ int xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace) { int res = -1; if (!xmlCatalogInitialized) xmlInitializeCatalogData(); xmlRMutexLock(xmlCatalogMutex); /* * Specific case where one want to override the default catalog * put in place by xmlInitializeCatalog(); */ if ((xmlDefaultCatalog == NULL) && (xmlStrEqual(type, BAD_CAST "catalog"))) { xmlDefaultCatalog = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE, xmlCatalogDefaultPrefer); xmlDefaultCatalog->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, orig, NULL, xmlCatalogDefaultPrefer); xmlRMutexUnlock(xmlCatalogMutex); return(0); } res = xmlACatalogAdd(xmlDefaultCatalog, type, orig, replace); xmlRMutexUnlock(xmlCatalogMutex); return(res); } /** * xmlCatalogRemove: * @value: the value to remove * * Remove an entry from the catalog * * Returns the number of entries removed if successful, -1 otherwise */ int xmlCatalogRemove(const xmlChar *value) { int res; if (!xmlCatalogInitialized) xmlInitializeCatalog(); xmlRMutexLock(xmlCatalogMutex); res = xmlACatalogRemove(xmlDefaultCatalog, value); xmlRMutexUnlock(xmlCatalogMutex); return(res); } /** * xmlCatalogConvert: * * Convert all the SGML catalog entries as XML ones * * Returns the number of entries converted if successful, -1 otherwise */ int xmlCatalogConvert(void) { int res = -1; if (!xmlCatalogInitialized) xmlInitializeCatalog(); xmlRMutexLock(xmlCatalogMutex); res = xmlConvertSGMLCatalog(xmlDefaultCatalog); xmlRMutexUnlock(xmlCatalogMutex); return(res); } /************************************************************************ * * * Public interface manipulating the common preferences * * * ************************************************************************/ /** * xmlCatalogGetDefaults: * * Used to get the user preference w.r.t. to what catalogs should * be accepted * * Returns the current xmlCatalogAllow value */ xmlCatalogAllow xmlCatalogGetDefaults(void) { return(xmlCatalogDefaultAllow); } /** * xmlCatalogSetDefaults: * * Used to set the user preference w.r.t. to what catalogs should * be accepted */ void xmlCatalogSetDefaults(xmlCatalogAllow allow) { if (xmlDebugCatalogs) { switch (allow) { case XML_CATA_ALLOW_NONE: xmlGenericError(xmlGenericErrorContext, "Disabling catalog usage\n"); break; case XML_CATA_ALLOW_GLOBAL: xmlGenericError(xmlGenericErrorContext, "Allowing only global catalogs\n"); break; case XML_CATA_ALLOW_DOCUMENT: xmlGenericError(xmlGenericErrorContext, "Allowing only catalogs from the document\n"); break; case XML_CATA_ALLOW_ALL: xmlGenericError(xmlGenericErrorContext, "Allowing all catalogs\n"); break; } } xmlCatalogDefaultAllow = allow; } /** * xmlCatalogSetDefaultPrefer: * @prefer: the default preference for delegation * * Allows to set the preference between public and system for deletion * in XML Catalog resolution. C.f. section 4.1.1 of the spec * Values accepted are XML_CATA_PREFER_PUBLIC or XML_CATA_PREFER_SYSTEM * * Returns the previous value of the default preference for delegation */ xmlCatalogPrefer xmlCatalogSetDefaultPrefer(xmlCatalogPrefer prefer) { xmlCatalogPrefer ret = xmlCatalogDefaultPrefer; if (prefer == XML_CATA_PREFER_NONE) return(ret); if (xmlDebugCatalogs) { switch (prefer) { case XML_CATA_PREFER_PUBLIC: xmlGenericError(xmlGenericErrorContext, "Setting catalog preference to PUBLIC\n"); break; case XML_CATA_PREFER_SYSTEM: xmlGenericError(xmlGenericErrorContext, "Setting catalog preference to SYSTEM\n"); break; case XML_CATA_PREFER_NONE: break; } } xmlCatalogDefaultPrefer = prefer; return(ret); } /** * xmlCatalogSetDebug: * @level: the debug level of catalogs required * * Used to set the debug level for catalog operation, 0 disable * debugging, 1 enable it * * Returns the previous value of the catalog debugging level */ int xmlCatalogSetDebug(int level) { int ret = xmlDebugCatalogs; if (level <= 0) xmlDebugCatalogs = 0; else xmlDebugCatalogs = level; return(ret); } /************************************************************************ * * * Minimal interfaces used for per-document catalogs by the parser * * * ************************************************************************/ /** * xmlCatalogFreeLocal: * @catalogs: a document's list of catalogs * * Free up the memory associated to the catalog list */ void xmlCatalogFreeLocal(void *catalogs) { xmlCatalogEntryPtr catal; if (!xmlCatalogInitialized) xmlInitializeCatalog(); catal = (xmlCatalogEntryPtr) catalogs; if (catal != NULL) xmlFreeCatalogEntryList(catal); } /** * xmlCatalogAddLocal: * @catalogs: a document's list of catalogs * @URL: the URL to a new local catalog * * Add the new entry to the catalog list * * Returns the updated list */ void * xmlCatalogAddLocal(void *catalogs, const xmlChar *URL) { xmlCatalogEntryPtr catal, add; if (!xmlCatalogInitialized) xmlInitializeCatalog(); if (URL == NULL) return(catalogs); if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Adding document catalog %s\n", URL); add = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, URL, NULL, xmlCatalogDefaultPrefer); if (add == NULL) return(catalogs); catal = (xmlCatalogEntryPtr) catalogs; if (catal == NULL) return((void *) add); while (catal->next != NULL) catal = catal->next; catal->next = add; return(catalogs); } /** * xmlCatalogLocalResolve: * @catalogs: a document's list of catalogs * @pubId: the public ID string * @sysId: the system ID string * * Do a complete resolution lookup of an External Identifier using a * document's private catalog list * * Returns the URI of the resource or NULL if not found, it must be freed * by the caller. */ xmlChar * xmlCatalogLocalResolve(void *catalogs, const xmlChar *pubID, const xmlChar *sysID) { xmlCatalogEntryPtr catal; xmlChar *ret; if (!xmlCatalogInitialized) xmlInitializeCatalog(); if ((pubID == NULL) && (sysID == NULL)) return(NULL); if (xmlDebugCatalogs) { if (pubID != NULL) { xmlGenericError(xmlGenericErrorContext, "Local resolve: pubID %s\n", pubID); } else { xmlGenericError(xmlGenericErrorContext, "Local resolve: sysID %s\n", sysID); } } catal = (xmlCatalogEntryPtr) catalogs; if (catal == NULL) return(NULL); ret = xmlCatalogListXMLResolve(catal, pubID, sysID); if ((ret != NULL) && (ret != XML_CATAL_BREAK)) return(ret); return(NULL); } /** * xmlCatalogLocalResolveURI: * @catalogs: a document's list of catalogs * @pubId: the URI * * Do a complete resolution lookup of an URI using a * document's private catalog list * * Returns the URI of the resource or NULL if not found, it must be freed * by the caller. */ xmlChar * xmlCatalogLocalResolveURI(void *catalogs, const xmlChar *URI) { xmlCatalogEntryPtr catal; xmlChar *ret; if (!xmlCatalogInitialized) xmlInitializeCatalog(); if (URI == NULL) return(NULL); if (xmlDebugCatalogs) xmlGenericError(xmlGenericErrorContext, "Resolve URI %s\n", URI); catal = (xmlCatalogEntryPtr) catalogs; if (catal == NULL) return(NULL); ret = xmlCatalogListXMLResolveURI(catal, URI); if ((ret != NULL) && (ret != XML_CATAL_BREAK)) return(ret); return(NULL); } /************************************************************************ * * * Deprecated interfaces * * * ************************************************************************/ /** * xmlCatalogGetSystem: * @pubId: the public ID string * * Try to lookup the system ID associated to a public ID * DEPRECATED, use xmlCatalogResolveSystem() * * Returns the system ID if found or NULL otherwise. */ const xmlChar * xmlCatalogGetSystem(const xmlChar *sysID) { xmlChar *ret; static xmlChar result[1000]; static int msg = 0; if (!xmlCatalogInitialized) xmlInitializeCatalog(); if (msg == 0) { xmlGenericError(xmlGenericErrorContext, "Use of deprecated xmlCatalogGetSystem() call\n"); msg++; } if (sysID == NULL) return(NULL); /* * Check first the XML catalogs */ if (xmlDefaultCatalog != NULL) { ret = xmlCatalogListXMLResolve(xmlDefaultCatalog->xml, NULL, sysID); if ((ret != NULL) && (ret != XML_CATAL_BREAK)) { snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret); result[sizeof(result) - 1] = 0; return(result); } } if (xmlDefaultCatalog != NULL) return(xmlCatalogGetSGMLSystem(xmlDefaultCatalog->sgml, sysID)); return(NULL); } /** * xmlCatalogGetPublic: * @pubId: the public ID string * * Try to lookup the system ID associated to a public ID * DEPRECATED, use xmlCatalogResolvePublic() * * Returns the system ID if found or NULL otherwise. */ const xmlChar * xmlCatalogGetPublic(const xmlChar *pubID) { xmlChar *ret; static xmlChar result[1000]; static int msg = 0; if (!xmlCatalogInitialized) xmlInitializeCatalog(); if (msg == 0) { xmlGenericError(xmlGenericErrorContext, "Use of deprecated xmlCatalogGetPublic() call\n"); msg++; } if (pubID == NULL) return(NULL); /* * Check first the XML catalogs */ if (xmlDefaultCatalog != NULL) { ret = xmlCatalogListXMLResolve(xmlDefaultCatalog->xml, pubID, NULL); if ((ret != NULL) && (ret != XML_CATAL_BREAK)) { snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret); result[sizeof(result) - 1] = 0; return(result); } } if (xmlDefaultCatalog != NULL) return(xmlCatalogGetSGMLPublic(xmlDefaultCatalog->sgml, pubID)); return(NULL); } #endif /* LIBXML_CATALOG_ENABLED */