/** * catalog.c: set of generic Catalog related routines * * Reference: SGML Open Technical Resolution TR9401:1997. * http://www.jclark.com/sp/catalog.htm * * 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 #include #include #include #include #include #include #include /************************************************************************ * * * Types, all private * * * ************************************************************************/ typedef enum { XML_CATA_NONE = 0, XML_CATA_SYSTEM, XML_CATA_PUBLIC, XML_CATA_ENTITY, XML_CATA_PENTITY, XML_CATA_DOCTYPE, XML_CATA_LINKTYPE, XML_CATA_NOTATION, XML_CATA_DELEGATE, XML_CATA_BASE, XML_CATA_CATALOG, XML_CATA_DOCUMENT, XML_CATA_SGMLDECL } xmlCatalogEntryType; typedef struct _xmlCatalogEntry xmlCatalogEntry; typedef xmlCatalogEntry *xmlCatalogEntryPtr; struct _xmlCatalogEntry { xmlCatalogEntryType type; xmlChar *name; xmlChar *value; }; static xmlHashTablePtr xmlDefaultCatalog; /* Catalog stack */ static const char * catalTab[10]; /* stack of catals */ static int catalNr = 0; /* Number of current catal streams */ static int catalMax = 10; /* Max number of catal streams */ /************************************************************************ * * * alloc or dealloc * * * ************************************************************************/ static xmlCatalogEntryPtr xmlNewCatalogEntry(int type, xmlChar *name, xmlChar *value) { xmlCatalogEntryPtr ret; ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry)); if (ret == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc of %d byte failed\n", sizeof(xmlCatalogEntry)); return(NULL); } ret->type = type; ret->name = xmlStrdup(name); ret->value = xmlStrdup(value); return(ret); } static void xmlFreeCatalogEntry(xmlCatalogEntryPtr ret) { if (ret == NULL) return; if (ret->name != NULL) xmlFree(ret->name); if (ret->value != NULL) xmlFree(ret->value); xmlFree(ret); } /** * xmlCatalogDumpEntry: * @entry: the * @out: the file. * * Free up all the memory associated with catalogs */ static void xmlCatalogDumpEntry(xmlCatalogEntryPtr entry, FILE *out) { if ((entry == NULL) || (out == NULL)) return; switch (entry->type) { case XML_CATA_ENTITY: fprintf(out, "ENTITY "); break; case XML_CATA_PENTITY: fprintf(out, "ENTITY %%"); break; case XML_CATA_DOCTYPE: fprintf(out, "DOCTYPE "); break; case XML_CATA_LINKTYPE: fprintf(out, "LINKTYPE "); break; case XML_CATA_NOTATION: fprintf(out, "NOTATION "); break; case XML_CATA_PUBLIC: fprintf(out, "PUBLIC "); break; case XML_CATA_SYSTEM: fprintf(out, "SYSTEM "); break; case XML_CATA_DELEGATE: fprintf(out, "DELEGATE "); break; case XML_CATA_BASE: fprintf(out, "BASE "); break; case XML_CATA_CATALOG: fprintf(out, "CATALOG "); break; case XML_CATA_DOCUMENT: fprintf(out, "DOCUMENT "); break; case XML_CATA_SGMLDECL: fprintf(out, "SGMLDECL "); break; default: return; } switch (entry->type) { case XML_CATA_ENTITY: case XML_CATA_PENTITY: case XML_CATA_DOCTYPE: case XML_CATA_LINKTYPE: case XML_CATA_NOTATION: fprintf(out, "%s", entry->name); break; case XML_CATA_PUBLIC: case XML_CATA_SYSTEM: case XML_CATA_SGMLDECL: case XML_CATA_DOCUMENT: case XML_CATA_CATALOG: case XML_CATA_BASE: case XML_CATA_DELEGATE: fprintf(out, "\"%s\"", entry->name); break; default: break; } switch (entry->type) { case XML_CATA_ENTITY: case XML_CATA_PENTITY: case XML_CATA_DOCTYPE: case XML_CATA_LINKTYPE: case XML_CATA_NOTATION: case XML_CATA_PUBLIC: case XML_CATA_SYSTEM: case XML_CATA_DELEGATE: fprintf(out, " \"%s\"", entry->value); break; default: break; } fprintf(out, "\n"); } /************************************************************************ * * * The parser * * * ************************************************************************/ #define RAW *cur #define NEXT cur++; #define SKIP(x) cur += x; #define SKIP_BLANKS while (IS_BLANK(*cur)) NEXT; static const xmlChar * xmlParseCatalogComment(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); } static const xmlChar * xmlParseCatalogPubid(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); } static const xmlChar * xmlParseCatalogName(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); } static int xmlParseCatalog(const xmlChar *value, const char *file) { 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] == '-') && (cur[1] == '-')) { cur = xmlParseCatalogComment(cur); if (cur == NULL) { /* error */ break; } } else { xmlChar *sysid = NULL; xmlChar *name = NULL; xmlCatalogEntryType type = XML_CATA_NONE; cur = xmlParseCatalogName(cur, &name); if (name == NULL) { /* error */ break; } if (!IS_BLANK(*cur)) { /* error */ break; } SKIP_BLANKS; 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 *) "DELEGATE")) type = XML_CATA_DELEGATE; else if (xmlStrEqual(name, (const xmlChar *) "ENTITY")) type = XML_CATA_ENTITY; else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE")) type = XML_CATA_DOCTYPE; else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE")) type = XML_CATA_LINKTYPE; else if (xmlStrEqual(name, (const xmlChar *) "NOTATION")) type = XML_CATA_NOTATION; else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL")) type = XML_CATA_SGMLDECL; else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT")) type = XML_CATA_DOCUMENT; else if (xmlStrEqual(name, (const xmlChar *) "CATALOG")) type = XML_CATA_CATALOG; else if (xmlStrEqual(name, (const xmlChar *) "BASE")) type = XML_CATA_BASE; else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE")) type = XML_CATA_DELEGATE; else if (xmlStrEqual(name, (const xmlChar *) "OVERRIDE")) { xmlFree(name); cur = xmlParseCatalogName(cur, &name); if (name == NULL) { /* error */ break; } xmlFree(name); continue; } xmlFree(name); name = NULL; switch(type) { case XML_CATA_ENTITY: if (*cur == '%') type = XML_CATA_PENTITY; case XML_CATA_PENTITY: case XML_CATA_DOCTYPE: case XML_CATA_LINKTYPE: case XML_CATA_NOTATION: cur = xmlParseCatalogName(cur, &name); if (cur == NULL) { /* error */ break; } if (!IS_BLANK(*cur)) { /* error */ break; } SKIP_BLANKS; cur = xmlParseCatalogPubid(cur, &sysid); if (cur == NULL) { /* error */ break; } break; case XML_CATA_PUBLIC: case XML_CATA_SYSTEM: case XML_CATA_DELEGATE: cur = xmlParseCatalogPubid(cur, &name); if (cur == NULL) { /* error */ break; } if (!IS_BLANK(*cur)) { /* error */ break; } SKIP_BLANKS; cur = xmlParseCatalogPubid(cur, &sysid); if (cur == NULL) { /* error */ break; } break; case XML_CATA_BASE: case XML_CATA_CATALOG: case XML_CATA_DOCUMENT: case XML_CATA_SGMLDECL: cur = xmlParseCatalogPubid(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 == XML_CATA_BASE) { if (base != NULL) xmlFree(base); base = xmlStrdup(sysid); } else if ((type == XML_CATA_PUBLIC) || (type == XML_CATA_SYSTEM)) { xmlChar *filename; filename = xmlBuildURI(sysid, base); if (filename != NULL) { xmlCatalogEntryPtr entry; entry = xmlNewCatalogEntry(type, name, filename); res = xmlHashAddEntry(xmlDefaultCatalog, name, entry); if (res < 0) { xmlFreeCatalogEntry(entry); } xmlFree(filename); } } else if (type == XML_CATA_CATALOG) { xmlChar *filename; filename = xmlBuildURI(sysid, base); if (filename != NULL) { xmlLoadCatalog((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); } /************************************************************************ * * * Public interfaces * * * ************************************************************************/ /* * xmlLoadCatalog: * @filename: a file path * * Load the catalog and makes its definitions effective for the default * external entity loader. It will recuse in CATALOG entries. * TODO: this function is not thread safe, catalog initialization should * be done once at startup * * Returns 0 in case of success -1 in case of error */ int xmlLoadCatalog(const char *filename) { int fd, len, ret, i; struct stat info; xmlChar *content; if (filename == NULL) return(-1); if (xmlDefaultCatalog == NULL) xmlDefaultCatalog = xmlHashCreate(20); if (xmlDefaultCatalog == NULL) return(-1); if (stat(filename, &info) < 0) return(-1); /* * Prevent loops */ for (i = 0;i < catalNr;i++) { if (xmlStrEqual((const xmlChar *)catalTab[i], (const xmlChar *)filename)) { xmlGenericError(xmlGenericErrorContext, "xmlLoadCatalog: %s seems to induce a loop\n", filename); return(-1); } } if (catalNr >= catalMax) { xmlGenericError(xmlGenericErrorContext, "xmlLoadCatalog: %s catalog list too deep\n", filename); return(-1); } catalTab[catalNr++] = filename; if ((fd = open(filename, O_RDONLY)) < 0) { catalNr--; return(-1); } content = xmlMalloc(info.st_size + 10); if (content == NULL) { xmlGenericError(xmlGenericErrorContext, "realloc of %d byte failed\n", info.st_size + 10); catalNr--; return(-1); } len = read(fd, content, info.st_size); if (len < 0) { xmlFree(content); catalNr--; return(-1); } content[len] = 0; close(fd); ret = xmlParseCatalog(content, filename); xmlFree(content); catalNr--; 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. * TODO: this function is not thread safe, catalog initialization should * be done once at startup */ void xmlLoadCatalogs(const char *pathss) { const char *cur; const char *paths; xmlChar *path; 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 (xmlDefaultCatalog != NULL) xmlHashFree(xmlDefaultCatalog, (xmlHashDeallocator) xmlFreeCatalogEntry); xmlDefaultCatalog = NULL; } /** * xmlCatalogGetSystem: * @sysId: the system ID string * * Try to lookup the resource associated to a system ID * * Returns the resource name if found or NULL otherwise. */ const xmlChar * xmlCatalogGetSystem(const xmlChar *sysID) { xmlCatalogEntryPtr entry; if ((sysID == NULL) || (xmlDefaultCatalog == NULL)) return(NULL); entry = (xmlCatalogEntryPtr) xmlHashLookup(xmlDefaultCatalog, sysID); if (entry == NULL) return(NULL); if (entry->type == XML_CATA_SYSTEM) return(entry->value); return(NULL); } /** * xmlCatalogGetPublic: * @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. */ const xmlChar * xmlCatalogGetPublic(const xmlChar *pubID) { xmlCatalogEntryPtr entry; if ((pubID == NULL) || (xmlDefaultCatalog == NULL)) return(NULL); entry = (xmlCatalogEntryPtr) xmlHashLookup(xmlDefaultCatalog, pubID); if (entry == NULL) return(NULL); if (entry->type == XML_CATA_PUBLIC) return(entry->value); return(NULL); } /** * xmlCatalogDump: * @out: the file. * * Free up all the memory associated with catalogs */ void xmlCatalogDump(FILE *out) { if (out == NULL) return; if (xmlDefaultCatalog != NULL) { xmlHashScan(xmlDefaultCatalog, (xmlHashScanner) xmlCatalogDumpEntry, out); } } #endif /* LIBXML_CATALOG_ENABLED */