catalog.c revision 81418e38c80cf1ddac6fe1426d8037a3da39853f
1/**
2 * catalog.c: set of generic Catalog related routines
3 *
4 * Reference:  SGML Open Technical Resolution TR9401:1997.
5 *             http://www.jclark.com/sp/catalog.htm
6 *
7 * See Copyright for the status of this software.
8 *
9 * Daniel.Veillard@imag.fr
10 */
11
12#include "libxml.h"
13
14#ifdef LIBXML_CATALOG_ENABLED
15#ifdef HAVE_SYS_TYPES_H
16#include <sys/types.h>
17#endif
18#ifdef HAVE_SYS_STAT_H
19#include <sys/stat.h>
20#endif
21#ifdef HAVE_UNISTD_H
22#include <unistd.h>
23#endif
24#ifdef HAVE_FCNTL_H
25#include <fcntl.h>
26#endif
27#include <string.h>
28#include <libxml/xmlmemory.h>
29#include <libxml/hash.h>
30#include <libxml/uri.h>
31#include <libxml/parserInternals.h>
32#include <libxml/catalog.h>
33#include <libxml/xmlerror.h>
34
35/************************************************************************
36 *									*
37 *			Types, all private				*
38 *									*
39 ************************************************************************/
40
41typedef enum {
42    XML_CATA_NONE = 0,
43    XML_CATA_SYSTEM,
44    XML_CATA_PUBLIC,
45    XML_CATA_ENTITY,
46    XML_CATA_PENTITY,
47    XML_CATA_DOCTYPE,
48    XML_CATA_LINKTYPE,
49    XML_CATA_NOTATION,
50    XML_CATA_DELEGATE,
51    XML_CATA_BASE,
52    XML_CATA_CATALOG,
53    XML_CATA_DOCUMENT,
54    XML_CATA_SGMLDECL
55} xmlCatalogEntryType;
56
57typedef struct _xmlCatalogEntry xmlCatalogEntry;
58typedef xmlCatalogEntry *xmlCatalogEntryPtr;
59struct _xmlCatalogEntry {
60    xmlCatalogEntryType type;
61    xmlChar *name;
62    xmlChar *value;
63};
64
65static xmlHashTablePtr xmlDefaultCatalog;
66
67/* Catalog stack */
68static const char * catalTab[10];  /* stack of catals */
69static int          catalNr = 0;   /* Number of current catal streams */
70static int          catalMax = 10; /* Max number of catal streams */
71
72/************************************************************************
73 *									*
74 *			alloc or dealloc				*
75 *									*
76 ************************************************************************/
77
78static xmlCatalogEntryPtr
79xmlNewCatalogEntry(int type, xmlChar *name, xmlChar *value) {
80    xmlCatalogEntryPtr ret;
81
82    ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry));
83    if (ret == NULL) {
84	xmlGenericError(xmlGenericErrorContext,
85		"malloc of %d byte failed\n", sizeof(xmlCatalogEntry));
86	return(NULL);
87    }
88    ret->type = type;
89    ret->name = xmlStrdup(name);
90    ret->value = xmlStrdup(value);
91    return(ret);
92}
93
94static void
95xmlFreeCatalogEntry(xmlCatalogEntryPtr ret) {
96    if (ret == NULL)
97	return;
98    if (ret->name != NULL)
99	xmlFree(ret->name);
100    if (ret->value != NULL)
101	xmlFree(ret->value);
102    xmlFree(ret);
103}
104
105/**
106 * xmlCatalogDumpEntry:
107 * @entry:  the
108 * @out:  the file.
109 *
110 * Free up all the memory associated with catalogs
111 */
112static void
113xmlCatalogDumpEntry(xmlCatalogEntryPtr entry, FILE *out) {
114    if ((entry == NULL) || (out == NULL))
115	return;
116    switch (entry->type) {
117	case XML_CATA_ENTITY:
118	    fprintf(out, "ENTITY "); break;
119	case XML_CATA_PENTITY:
120	    fprintf(out, "ENTITY %%"); break;
121	case XML_CATA_DOCTYPE:
122	    fprintf(out, "DOCTYPE "); break;
123	case XML_CATA_LINKTYPE:
124	    fprintf(out, "LINKTYPE "); break;
125	case XML_CATA_NOTATION:
126	    fprintf(out, "NOTATION "); break;
127	case XML_CATA_PUBLIC:
128	    fprintf(out, "PUBLIC "); break;
129	case XML_CATA_SYSTEM:
130	    fprintf(out, "SYSTEM "); break;
131	case XML_CATA_DELEGATE:
132	    fprintf(out, "DELEGATE "); break;
133	case XML_CATA_BASE:
134	    fprintf(out, "BASE "); break;
135	case XML_CATA_CATALOG:
136	    fprintf(out, "CATALOG "); break;
137	case XML_CATA_DOCUMENT:
138	    fprintf(out, "DOCUMENT "); break;
139	case XML_CATA_SGMLDECL:
140	    fprintf(out, "SGMLDECL "); break;
141	default:
142	    return;
143    }
144    switch (entry->type) {
145	case XML_CATA_ENTITY:
146	case XML_CATA_PENTITY:
147	case XML_CATA_DOCTYPE:
148	case XML_CATA_LINKTYPE:
149	case XML_CATA_NOTATION:
150	    fprintf(out, "%s", entry->name); break;
151	case XML_CATA_PUBLIC:
152	case XML_CATA_SYSTEM:
153	case XML_CATA_SGMLDECL:
154	case XML_CATA_DOCUMENT:
155	case XML_CATA_CATALOG:
156	case XML_CATA_BASE:
157	case XML_CATA_DELEGATE:
158	    fprintf(out, "\"%s\"", entry->name); break;
159	default:
160	    break;
161    }
162    switch (entry->type) {
163	case XML_CATA_ENTITY:
164	case XML_CATA_PENTITY:
165	case XML_CATA_DOCTYPE:
166	case XML_CATA_LINKTYPE:
167	case XML_CATA_NOTATION:
168	case XML_CATA_PUBLIC:
169	case XML_CATA_SYSTEM:
170	case XML_CATA_DELEGATE:
171	    fprintf(out, " \"%s\"", entry->value); break;
172	default:
173	    break;
174    }
175    fprintf(out, "\n");
176}
177
178/************************************************************************
179 *									*
180 *			The parser					*
181 *									*
182 ************************************************************************/
183
184
185#define RAW *cur
186#define NEXT cur++;
187#define SKIP(x) cur += x;
188
189#define SKIP_BLANKS while (IS_BLANK(*cur)) NEXT;
190
191static const xmlChar *
192xmlParseCatalogComment(const xmlChar *cur) {
193    if ((cur[0] != '-') || (cur[1] != '-'))
194	return(cur);
195    SKIP(2);
196    while ((cur[0] != 0) && ((cur[0] != '-') || ((cur[1] != '-'))))
197	NEXT;
198    if (cur[0] == 0) {
199	return(NULL);
200    }
201    return(cur + 2);
202}
203
204static const xmlChar *
205xmlParseCatalogPubid(const xmlChar *cur, xmlChar **id) {
206    xmlChar *buf = NULL;
207    int len = 0;
208    int size = 50;
209    xmlChar stop;
210    int count = 0;
211
212    *id = NULL;
213
214    if (RAW == '"') {
215        NEXT;
216	stop = '"';
217    } else if (RAW == '\'') {
218        NEXT;
219	stop = '\'';
220    } else {
221	stop = ' ';
222    }
223    buf = (xmlChar *) xmlMalloc(size * sizeof(xmlChar));
224    if (buf == NULL) {
225	xmlGenericError(xmlGenericErrorContext,
226		"malloc of %d byte failed\n", size);
227	return(NULL);
228    }
229    while (xmlIsPubidChar(*cur)) {
230	if ((*cur == stop) && (stop != ' '))
231	    break;
232	if ((stop == ' ') && (IS_BLANK(*cur)))
233	    break;
234	if (len + 1 >= size) {
235	    size *= 2;
236	    buf = (xmlChar *) xmlRealloc(buf, size * sizeof(xmlChar));
237	    if (buf == NULL) {
238		xmlGenericError(xmlGenericErrorContext,
239			"realloc of %d byte failed\n", size);
240		return(NULL);
241	    }
242	}
243	buf[len++] = *cur;
244	count++;
245	NEXT;
246    }
247    buf[len] = 0;
248    if (stop == ' ') {
249	if (!IS_BLANK(*cur)) {
250	    xmlFree(buf);
251	    return(NULL);
252	}
253    } else {
254	if (*cur != stop) {
255	    xmlFree(buf);
256	    return(NULL);
257	}
258	NEXT;
259    }
260    *id = buf;
261    return(cur);
262}
263
264static const xmlChar *
265xmlParseCatalogName(const xmlChar *cur, xmlChar **name) {
266    xmlChar buf[XML_MAX_NAMELEN + 5];
267    int len = 0;
268    int c;
269
270    *name = NULL;
271
272    /*
273     * Handler for more complex cases
274     */
275    c = *cur;
276    if ((!IS_LETTER(c) && (c != '_') && (c != ':'))) {
277	return(NULL);
278    }
279
280    while (((IS_LETTER(c)) || (IS_DIGIT(c)) ||
281            (c == '.') || (c == '-') ||
282	    (c == '_') || (c == ':'))) {
283	buf[len++] = c;
284	cur++;
285	c = *cur;
286	if (len >= XML_MAX_NAMELEN)
287	    return(NULL);
288    }
289    *name = xmlStrndup(buf, len);
290    return(cur);
291}
292
293static int
294xmlParseCatalog(const xmlChar *value, const char *file) {
295    const xmlChar *cur = value;
296    xmlChar *base = NULL;
297    int res;
298
299    if ((cur == NULL) || (file == NULL))
300        return(-1);
301    base = xmlStrdup((const xmlChar *) file);
302
303    while ((cur != NULL) && (cur[0] != '0')) {
304	SKIP_BLANKS;
305	if ((cur[0] == '-') && (cur[1] == '-')) {
306	    cur = xmlParseCatalogComment(cur);
307	    if (cur == NULL) {
308		/* error */
309		break;
310	    }
311	} else {
312	    xmlChar *sysid = NULL;
313	    xmlChar *name = NULL;
314	    xmlCatalogEntryType type = XML_CATA_NONE;
315
316	    cur = xmlParseCatalogName(cur, &name);
317	    if (name == NULL) {
318		/* error */
319		break;
320	    }
321	    if (!IS_BLANK(*cur)) {
322		/* error */
323		break;
324	    }
325	    SKIP_BLANKS;
326	    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
327                type = XML_CATA_SYSTEM;
328	    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
329                type = XML_CATA_PUBLIC;
330	    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
331                type = XML_CATA_DELEGATE;
332	    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
333                type = XML_CATA_ENTITY;
334	    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
335                type = XML_CATA_DOCTYPE;
336	    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
337                type = XML_CATA_LINKTYPE;
338	    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
339                type = XML_CATA_NOTATION;
340	    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
341                type = XML_CATA_SGMLDECL;
342	    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
343                type = XML_CATA_DOCUMENT;
344	    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
345                type = XML_CATA_CATALOG;
346	    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
347                type = XML_CATA_BASE;
348	    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
349                type = XML_CATA_DELEGATE;
350	    else if (xmlStrEqual(name, (const xmlChar *) "OVERRIDE")) {
351		xmlFree(name);
352		cur = xmlParseCatalogName(cur, &name);
353		if (name == NULL) {
354		    /* error */
355		    break;
356		}
357		xmlFree(name);
358		continue;
359	    }
360	    xmlFree(name);
361	    name = NULL;
362
363	    switch(type) {
364		case XML_CATA_ENTITY:
365		    if (*cur == '%')
366			type = XML_CATA_PENTITY;
367		case XML_CATA_PENTITY:
368		case XML_CATA_DOCTYPE:
369		case XML_CATA_LINKTYPE:
370		case XML_CATA_NOTATION:
371		    cur = xmlParseCatalogName(cur, &name);
372		    if (cur == NULL) {
373			/* error */
374			break;
375		    }
376		    if (!IS_BLANK(*cur)) {
377			/* error */
378			break;
379		    }
380		    SKIP_BLANKS;
381		    cur = xmlParseCatalogPubid(cur, &sysid);
382		    if (cur == NULL) {
383			/* error */
384			break;
385		    }
386		    break;
387		case XML_CATA_PUBLIC:
388		case XML_CATA_SYSTEM:
389		case XML_CATA_DELEGATE:
390		    cur = xmlParseCatalogPubid(cur, &name);
391		    if (cur == NULL) {
392			/* error */
393			break;
394		    }
395		    if (!IS_BLANK(*cur)) {
396			/* error */
397			break;
398		    }
399		    SKIP_BLANKS;
400		    cur = xmlParseCatalogPubid(cur, &sysid);
401		    if (cur == NULL) {
402			/* error */
403			break;
404		    }
405		    break;
406		case XML_CATA_BASE:
407		case XML_CATA_CATALOG:
408		case XML_CATA_DOCUMENT:
409		case XML_CATA_SGMLDECL:
410		    cur = xmlParseCatalogPubid(cur, &sysid);
411		    if (cur == NULL) {
412			/* error */
413			break;
414		    }
415		    break;
416		default:
417		    break;
418	    }
419	    if (cur == NULL) {
420		if (name != NULL)
421		    xmlFree(name);
422		if (sysid != NULL)
423		    xmlFree(sysid);
424		break;
425	    } else if (type == XML_CATA_BASE) {
426		if (base != NULL)
427		    xmlFree(base);
428		base = xmlStrdup(sysid);
429	    } else if ((type == XML_CATA_PUBLIC) ||
430		       (type == XML_CATA_SYSTEM)) {
431		xmlChar *filename;
432
433		filename = xmlBuildURI(sysid, base);
434		if (filename != NULL) {
435		    xmlCatalogEntryPtr entry;
436
437		    entry = xmlNewCatalogEntry(type, name, filename);
438		    res = xmlHashAddEntry(xmlDefaultCatalog, name, entry);
439		    if (res < 0) {
440			xmlFreeCatalogEntry(entry);
441		    }
442		    xmlFree(filename);
443		}
444
445	    } else if (type == XML_CATA_CATALOG) {
446		xmlChar *filename;
447
448		filename = xmlBuildURI(sysid, base);
449		if (filename != NULL) {
450		    xmlLoadCatalog((const char *)filename);
451		    xmlFree(filename);
452		}
453	    }
454	    /*
455	     * drop anything else we won't handle it
456	     */
457	    if (name != NULL)
458		xmlFree(name);
459	    if (sysid != NULL)
460		xmlFree(sysid);
461	}
462    }
463    if (base != NULL)
464	xmlFree(base);
465    if (cur == NULL)
466	return(-1);
467    return(0);
468}
469
470/************************************************************************
471 *									*
472 *			Public interfaces				*
473 *									*
474 ************************************************************************/
475
476/*
477 * xmlLoadCatalog:
478 * @filename:  a file path
479 *
480 * Load the catalog and makes its definitions effective for the default
481 * external entity loader. It will recuse in CATALOG entries.
482 * TODO: this function is not thread safe, catalog initialization should
483 *       be done once at startup
484 *
485 * Returns 0 in case of success -1 in case of error
486 */
487int
488xmlLoadCatalog(const char *filename) {
489    int fd, len, ret, i;
490    struct stat info;
491    xmlChar *content;
492
493    if (filename == NULL)
494	return(-1);
495
496    if (xmlDefaultCatalog == NULL)
497	xmlDefaultCatalog = xmlHashCreate(20);
498    if (xmlDefaultCatalog == NULL)
499	return(-1);
500
501    if (stat(filename, &info) < 0)
502	return(-1);
503
504    /*
505     * Prevent loops
506     */
507    for (i = 0;i < catalNr;i++) {
508	if (xmlStrEqual((const xmlChar *)catalTab[i],
509		        (const xmlChar *)filename)) {
510	    xmlGenericError(xmlGenericErrorContext,
511		"xmlLoadCatalog: %s seems to induce a loop\n",
512		            filename);
513	    return(-1);
514	}
515    }
516    if (catalNr >= catalMax) {
517	xmlGenericError(xmlGenericErrorContext,
518	    "xmlLoadCatalog: %s catalog list too deep\n",
519			filename);
520	    return(-1);
521    }
522    catalTab[catalNr++] = filename;
523
524    if ((fd = open(filename, O_RDONLY)) < 0) {
525	catalNr--;
526	return(-1);
527    }
528
529    content = xmlMalloc(info.st_size + 10);
530    if (content == NULL) {
531	xmlGenericError(xmlGenericErrorContext,
532		"realloc of %d byte failed\n", info.st_size + 10);
533	catalNr--;
534	return(-1);
535    }
536    len = read(fd, content, info.st_size);
537    if (len < 0) {
538	xmlFree(content);
539	catalNr--;
540	return(-1);
541    }
542    content[len] = 0;
543    close(fd);
544
545    ret = xmlParseCatalog(content, filename);
546    xmlFree(content);
547    catalNr--;
548    return(ret);
549}
550
551/*
552 * xmlLoadCatalogs:
553 * @paths:  a list of file path separated by ':' or spaces
554 *
555 * Load the catalogs and makes their definitions effective for the default
556 * external entity loader.
557 * TODO: this function is not thread safe, catalog initialization should
558 *       be done once at startup
559 */
560void
561xmlLoadCatalogs(const char *pathss) {
562    const char *cur;
563    const char *paths;
564    xmlChar *path;
565
566    cur = pathss;
567    while ((cur != NULL) && (*cur != 0)) {
568	while (IS_BLANK(*cur)) cur++;
569	if (*cur != 0) {
570	    paths = cur;
571	    while ((*cur != 0) && (*cur != ':') && (!IS_BLANK(*cur)))
572		cur++;
573	    path = xmlStrndup((const xmlChar *)paths, cur - paths);
574	    if (path != NULL) {
575		xmlLoadCatalog((const char *) path);
576		xmlFree(path);
577	    }
578	}
579	while (*cur == ':')
580	    cur++;
581    }
582}
583
584/**
585 * xmlCatalogCleanup:
586 *
587 * Free up all the memory associated with catalogs
588 */
589void
590xmlCatalogCleanup(void) {
591    if (xmlDefaultCatalog != NULL)
592	xmlHashFree(xmlDefaultCatalog,
593		    (xmlHashDeallocator) xmlFreeCatalogEntry);
594    xmlDefaultCatalog = NULL;
595}
596
597/**
598 * xmlCatalogGetSystem:
599 * @sysId:  the system ID string
600 *
601 * Try to lookup the resource associated to a system ID
602 *
603 * Returns the resource name if found or NULL otherwise.
604 */
605const xmlChar *
606xmlCatalogGetSystem(const xmlChar *sysID) {
607    xmlCatalogEntryPtr entry;
608
609    if ((sysID == NULL) || (xmlDefaultCatalog == NULL))
610	return(NULL);
611    entry = (xmlCatalogEntryPtr) xmlHashLookup(xmlDefaultCatalog, sysID);
612    if (entry == NULL)
613	return(NULL);
614    if (entry->type == XML_CATA_SYSTEM)
615	return(entry->value);
616    return(NULL);
617}
618
619/**
620 * xmlCatalogGetPublic:
621 * @pubId:  the public ID string
622 *
623 * Try to lookup the system ID associated to a public ID
624 *
625 * Returns the system ID if found or NULL otherwise.
626 */
627const xmlChar *
628xmlCatalogGetPublic(const xmlChar *pubID) {
629    xmlCatalogEntryPtr entry;
630
631    if ((pubID == NULL) || (xmlDefaultCatalog == NULL))
632	return(NULL);
633    entry = (xmlCatalogEntryPtr) xmlHashLookup(xmlDefaultCatalog, pubID);
634    if (entry == NULL)
635	return(NULL);
636    if (entry->type == XML_CATA_PUBLIC)
637	return(entry->value);
638    return(NULL);
639}
640/**
641 * xmlCatalogDump:
642 * @out:  the file.
643 *
644 * Free up all the memory associated with catalogs
645 */
646void
647xmlCatalogDump(FILE *out) {
648    if (out == NULL)
649	return;
650    if (xmlDefaultCatalog != NULL) {
651	xmlHashScan(xmlDefaultCatalog,
652		    (xmlHashScanner) xmlCatalogDumpEntry, out);
653    }
654}
655#endif /* LIBXML_CATALOG_ENABLED */
656