blob: 9664c4fb361ff607a98247b5a5ab581ae5557b02 [file] [log] [blame]
/**
* 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 <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <string.h>
#include <libxml/xmlmemory.h>
#include <libxml/hash.h>
#include <libxml/uri.h>
#include <libxml/parserInternals.h>
#include <libxml/catalog.h>
#include <libxml/xmlerror.h>
/************************************************************************
* *
* 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 */