catalog.c revision 99784ff899ed1e87a232cb9016e6ba3ee7642a4b
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 *             XML Catalogs Working Draft 06 August 2001
8 *             http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
9 *
10 * See Copyright for the status of this software.
11 *
12 * Daniel.Veillard@imag.fr
13 */
14
15#include "libxml.h"
16
17#ifdef LIBXML_CATALOG_ENABLED
18#ifdef HAVE_SYS_TYPES_H
19#include <sys/types.h>
20#endif
21#ifdef HAVE_SYS_STAT_H
22#include <sys/stat.h>
23#endif
24#ifdef HAVE_UNISTD_H
25#include <unistd.h>
26#endif
27#ifdef HAVE_FCNTL_H
28#include <fcntl.h>
29#endif
30#include <string.h>
31#include <libxml/xmlmemory.h>
32#include <libxml/hash.h>
33#include <libxml/uri.h>
34#include <libxml/parserInternals.h>
35#include <libxml/catalog.h>
36#include <libxml/xmlerror.h>
37
38#define MAX_DELEGATE	50
39
40/**
41 * TODO:
42 *
43 * macro to flag unimplemented blocks
44 */
45#define TODO 								\
46    xmlGenericError(xmlGenericErrorContext,				\
47	    "Unimplemented block at %s:%d\n",				\
48            __FILE__, __LINE__);
49
50#define XML_URN_PUBID "urn:publicid:"
51#define XML_CATAL_BREAK ((xmlChar *) -1)
52#ifndef XML_DEFAULT_CATALOG
53#define XML_DEFAULT_CATALOG "/etc/xml/catalog"
54#endif
55
56/************************************************************************
57 *									*
58 *			Types, all private				*
59 *									*
60 ************************************************************************/
61
62typedef enum {
63    XML_CATA_NONE = 0,
64    XML_CATA_CATALOG,
65    XML_CATA_BROKEN_CATALOG,
66    XML_CATA_NEXT_CATALOG,
67    XML_CATA_PUBLIC,
68    XML_CATA_SYSTEM,
69    XML_CATA_REWRITE_SYSTEM,
70    XML_CATA_DELEGATE_PUBLIC,
71    XML_CATA_DELEGATE_SYSTEM,
72    XML_CATA_URI,
73    XML_CATA_REWRITE_URI,
74    XML_CATA_DELEGATE_URI,
75    SGML_CATA_SYSTEM,
76    SGML_CATA_PUBLIC,
77    SGML_CATA_ENTITY,
78    SGML_CATA_PENTITY,
79    SGML_CATA_DOCTYPE,
80    SGML_CATA_LINKTYPE,
81    SGML_CATA_NOTATION,
82    SGML_CATA_DELEGATE,
83    SGML_CATA_BASE,
84    SGML_CATA_CATALOG,
85    SGML_CATA_DOCUMENT,
86    SGML_CATA_SGMLDECL
87} xmlCatalogEntryType;
88
89typedef struct _xmlCatalogEntry xmlCatalogEntry;
90typedef xmlCatalogEntry *xmlCatalogEntryPtr;
91struct _xmlCatalogEntry {
92    struct _xmlCatalogEntry *next;
93    struct _xmlCatalogEntry *parent;
94    struct _xmlCatalogEntry *children;
95    xmlCatalogEntryType type;
96    xmlChar *name;
97    xmlChar *value;
98    xmlCatalogPrefer prefer;
99    int dealloc;
100};
101
102static xmlCatalogAllow xmlCatalogDefaultAllow = XML_CATA_ALLOW_ALL;
103static xmlCatalogPrefer xmlCatalogDefaultPrefer = XML_CATA_PREFER_PUBLIC;
104static xmlHashTablePtr xmlDefaultCatalog;
105static xmlHashTablePtr xmlCatalogXMLFiles = NULL;
106static xmlCatalogEntryPtr xmlDefaultXMLCatalogList = NULL;
107static int xmlCatalogInitialized = 0;
108
109
110/* Catalog stack */
111static const char * catalTab[10];  /* stack of catals */
112static int          catalNr = 0;   /* Number of current catal streams */
113static int          catalMax = 10; /* Max number of catal streams */
114
115static int xmlDebugCatalogs = 0;   /* used for debugging */
116
117/************************************************************************
118 *									*
119 *			alloc or dealloc				*
120 *									*
121 ************************************************************************/
122
123static xmlCatalogEntryPtr
124xmlNewCatalogEntry(xmlCatalogEntryType type, const xmlChar *name,
125	           const xmlChar *value, xmlCatalogPrefer prefer) {
126    xmlCatalogEntryPtr ret;
127
128    ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry));
129    if (ret == NULL) {
130	xmlGenericError(xmlGenericErrorContext,
131		"malloc of %d byte failed\n", sizeof(xmlCatalogEntry));
132	return(NULL);
133    }
134    ret->next = NULL;
135    ret->parent = NULL;
136    ret->children = NULL;
137    ret->type = type;
138    if (name != NULL)
139	ret->name = xmlStrdup(name);
140    else
141	ret->name = NULL;
142    if (value != NULL)
143	ret->value = xmlStrdup(value);
144    else
145	ret->value = NULL;
146    ret->prefer = prefer;
147    ret->dealloc = 1;
148    return(ret);
149}
150
151static void
152xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret);
153
154static void
155xmlFreeCatalogEntry(xmlCatalogEntryPtr ret) {
156    if (ret == NULL)
157	return;
158    if ((ret->children != NULL) && (ret->dealloc == 1))
159	xmlFreeCatalogEntryList(ret->children);
160    if (ret->name != NULL)
161	xmlFree(ret->name);
162    if (ret->value != NULL)
163	xmlFree(ret->value);
164    xmlFree(ret);
165}
166
167static void
168xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret) {
169    xmlCatalogEntryPtr next;
170
171    while (ret != NULL) {
172	next = ret->next;
173	xmlFreeCatalogEntry(ret);
174	ret = next;
175    }
176}
177
178/**
179 * xmlCatalogDumpEntry:
180 * @entry:  the
181 * @out:  the file.
182 *
183 * Free up all the memory associated with catalogs
184 */
185static void
186xmlCatalogDumpEntry(xmlCatalogEntryPtr entry, FILE *out) {
187    if ((entry == NULL) || (out == NULL))
188	return;
189    switch (entry->type) {
190	case SGML_CATA_ENTITY:
191	    fprintf(out, "ENTITY "); break;
192	case SGML_CATA_PENTITY:
193	    fprintf(out, "ENTITY %%"); break;
194	case SGML_CATA_DOCTYPE:
195	    fprintf(out, "DOCTYPE "); break;
196	case SGML_CATA_LINKTYPE:
197	    fprintf(out, "LINKTYPE "); break;
198	case SGML_CATA_NOTATION:
199	    fprintf(out, "NOTATION "); break;
200	case SGML_CATA_PUBLIC:
201	    fprintf(out, "PUBLIC "); break;
202	case SGML_CATA_SYSTEM:
203	    fprintf(out, "SYSTEM "); break;
204	case SGML_CATA_DELEGATE:
205	    fprintf(out, "DELEGATE "); break;
206	case SGML_CATA_BASE:
207	    fprintf(out, "BASE "); break;
208	case SGML_CATA_CATALOG:
209	    fprintf(out, "CATALOG "); break;
210	case SGML_CATA_DOCUMENT:
211	    fprintf(out, "DOCUMENT "); break;
212	case SGML_CATA_SGMLDECL:
213	    fprintf(out, "SGMLDECL "); break;
214	default:
215	    return;
216    }
217    switch (entry->type) {
218	case SGML_CATA_ENTITY:
219	case SGML_CATA_PENTITY:
220	case SGML_CATA_DOCTYPE:
221	case SGML_CATA_LINKTYPE:
222	case SGML_CATA_NOTATION:
223	    fprintf(out, "%s", entry->name); break;
224	case SGML_CATA_PUBLIC:
225	case SGML_CATA_SYSTEM:
226	case SGML_CATA_SGMLDECL:
227	case SGML_CATA_DOCUMENT:
228	case SGML_CATA_CATALOG:
229	case SGML_CATA_BASE:
230	case SGML_CATA_DELEGATE:
231	    fprintf(out, "\"%s\"", entry->name); break;
232	default:
233	    break;
234    }
235    switch (entry->type) {
236	case SGML_CATA_ENTITY:
237	case SGML_CATA_PENTITY:
238	case SGML_CATA_DOCTYPE:
239	case SGML_CATA_LINKTYPE:
240	case SGML_CATA_NOTATION:
241	case SGML_CATA_PUBLIC:
242	case SGML_CATA_SYSTEM:
243	case SGML_CATA_DELEGATE:
244	    fprintf(out, " \"%s\"", entry->value); break;
245	default:
246	    break;
247    }
248    fprintf(out, "\n");
249}
250
251/**
252 * xmlCatalogConvertEntry:
253 * @entry:  the entry
254 * @res:  pointer to te number converted
255 *
256 * Free up all the memory associated with catalogs
257 */
258static void
259xmlCatalogConvertEntry(xmlCatalogEntryPtr entry, int *res) {
260    if ((entry == NULL) || (xmlDefaultXMLCatalogList == NULL))
261	return;
262    switch (entry->type) {
263	case SGML_CATA_ENTITY:
264	    entry->type = XML_CATA_PUBLIC;
265	    break;
266	case SGML_CATA_PENTITY:
267	    entry->type = XML_CATA_PUBLIC;
268	    break;
269	case SGML_CATA_DOCTYPE:
270	    entry->type = XML_CATA_PUBLIC;
271	    break;
272	case SGML_CATA_LINKTYPE:
273	    entry->type = XML_CATA_PUBLIC;
274	    break;
275	case SGML_CATA_NOTATION:
276	    entry->type = XML_CATA_PUBLIC;
277	    break;
278	case SGML_CATA_PUBLIC:
279	    entry->type = XML_CATA_PUBLIC;
280	    break;
281	case SGML_CATA_SYSTEM:
282	    entry->type = XML_CATA_SYSTEM;
283	    break;
284	case SGML_CATA_DELEGATE:
285	    entry->type = XML_CATA_DELEGATE_PUBLIC;
286	    break;
287	case SGML_CATA_CATALOG:
288	    entry->type = XML_CATA_CATALOG;
289	    break;
290	default:
291	    xmlHashRemoveEntry(xmlDefaultCatalog, entry->name,
292		               (xmlHashDeallocator) xmlFreeCatalogEntry);
293	    return;
294    }
295    /*
296     * Conversion successful, remove from the SGML catalog
297     * and add it to the default XML one
298     */
299    xmlHashRemoveEntry(xmlDefaultCatalog, entry->name, NULL);
300    entry->parent = xmlDefaultXMLCatalogList;
301    entry->next = NULL;
302    if (xmlDefaultXMLCatalogList->children == NULL)
303	xmlDefaultXMLCatalogList->children = entry;
304    else {
305	xmlCatalogEntryPtr prev;
306
307	prev = xmlDefaultXMLCatalogList->children;
308	while (prev->next != NULL)
309	    prev = prev->next;
310	prev->next = entry;
311    }
312    if (res != NULL)
313	(*res)++;
314}
315
316/************************************************************************
317 *									*
318 *			Helper function					*
319 *									*
320 ************************************************************************/
321
322/**
323 * xmlCatalogUnWrapURN:
324 * @urn:  an "urn:publicid:" to unwrapp
325 *
326 * Expand the URN into the equivalent Public Identifier
327 *
328 * Returns the new identifier or NULL, the string must be deallocated
329 *         by the caller.
330 */
331static xmlChar *
332xmlCatalogUnWrapURN(const xmlChar *urn) {
333    xmlChar result[2000];
334    unsigned int i = 0;
335
336    if (xmlStrncmp(urn, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1))
337	return(NULL);
338    urn += sizeof(XML_URN_PUBID) - 1;
339
340    while (*urn != 0) {
341	if (i > sizeof(result) - 3)
342	    break;
343	if (*urn == '+') {
344	    result[i++] = ' ';
345	    urn++;
346	} else if (*urn == ':') {
347	    result[i++] = '/';
348	    result[i++] = '/';
349	    urn++;
350	} else if (*urn == ';') {
351	    result[i++] = ':';
352	    result[i++] = ':';
353	    urn++;
354	} else if (*urn == '%') {
355	    if ((urn[1] == '2') && (urn[1] == 'B'))
356		result[i++] = '+';
357	    else if ((urn[1] == '3') && (urn[1] == 'A'))
358		result[i++] = ':';
359	    else if ((urn[1] == '2') && (urn[1] == 'F'))
360		result[i++] = '/';
361	    else if ((urn[1] == '3') && (urn[1] == 'B'))
362		result[i++] = ';';
363	    else if ((urn[1] == '2') && (urn[1] == '7'))
364		result[i++] = '\'';
365	    else if ((urn[1] == '3') && (urn[1] == 'F'))
366		result[i++] = '?';
367	    else if ((urn[1] == '2') && (urn[1] == '3'))
368		result[i++] = '#';
369	    else if ((urn[1] == '2') && (urn[1] == '5'))
370		result[i++] = '%';
371	    else {
372		result[i++] = *urn;
373		urn++;
374		continue;
375	    }
376	    urn += 3;
377	} else {
378	    result[i++] = *urn;
379	    urn++;
380	}
381    }
382    result[i] = 0;
383
384    return(xmlStrdup(result));
385}
386
387/**
388 * xmlParseCatalogFile:
389 * @filename:  the filename
390 *
391 * parse an XML file and build a tree. It's like xmlParseFile()
392 * except it bypass all catalog lookups.
393 *
394 * Returns the resulting document tree or NULL in case of error
395 */
396
397xmlDocPtr
398xmlParseCatalogFile(const char *filename) {
399    xmlDocPtr ret;
400    xmlParserCtxtPtr ctxt;
401    char *directory = NULL;
402    xmlParserInputPtr inputStream;
403    xmlParserInputBufferPtr buf;
404
405    ctxt = xmlNewParserCtxt();
406    if (ctxt == NULL) {
407	if (xmlDefaultSAXHandler.error != NULL) {
408	    xmlDefaultSAXHandler.error(NULL, "out of memory\n");
409	}
410	return(NULL);
411    }
412
413    buf = xmlParserInputBufferCreateFilename(filename, XML_CHAR_ENCODING_NONE);
414    if (buf == NULL) {
415	xmlFreeParserCtxt(ctxt);
416	return(NULL);
417    }
418
419    inputStream = xmlNewInputStream(ctxt);
420    if (inputStream == NULL) {
421	xmlFreeParserCtxt(ctxt);
422	return(NULL);
423    }
424
425    inputStream->filename = xmlMemStrdup(filename);
426    inputStream->buf = buf;
427    inputStream->base = inputStream->buf->buffer->content;
428    inputStream->cur = inputStream->buf->buffer->content;
429    inputStream->end =
430	&inputStream->buf->buffer->content[inputStream->buf->buffer->use];
431
432    inputPush(ctxt, inputStream);
433    if ((ctxt->directory == NULL) && (directory == NULL))
434        directory = xmlParserGetDirectory(filename);
435    if ((ctxt->directory == NULL) && (directory != NULL))
436        ctxt->directory = directory;
437    ctxt->valid = 0;
438    ctxt->validate = 0;
439    ctxt->loadsubset = 0;
440    ctxt->pedantic = 0;
441
442    xmlParseDocument(ctxt);
443
444    if (ctxt->wellFormed)
445	ret = ctxt->myDoc;
446    else {
447        ret = NULL;
448        xmlFreeDoc(ctxt->myDoc);
449        ctxt->myDoc = NULL;
450    }
451    xmlFreeParserCtxt(ctxt);
452
453    return(ret);
454}
455
456/************************************************************************
457 *									*
458 *			The XML Catalog parser				*
459 *									*
460 ************************************************************************/
461
462static xmlCatalogEntryPtr
463xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename);
464static xmlCatalogEntryPtr
465xmlParseXMLCatalog(const xmlChar *value, xmlCatalogPrefer prefer,
466	           const char *file);
467static void
468xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
469	                   xmlCatalogEntryPtr parent);
470static xmlChar *
471xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
472	              const xmlChar *sysID);
473static xmlChar *
474xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI);
475
476
477static xmlCatalogEntryType
478xmlGetXMLCatalogEntryType(const xmlChar *name) {
479    xmlCatalogEntryType type = XML_CATA_NONE;
480    if (xmlStrEqual(name, (const xmlChar *) "system"))
481	type = XML_CATA_SYSTEM;
482    else if (xmlStrEqual(name, (const xmlChar *) "public"))
483	type = XML_CATA_PUBLIC;
484    else if (xmlStrEqual(name, (const xmlChar *) "rewriteSystem"))
485	type = XML_CATA_REWRITE_SYSTEM;
486    else if (xmlStrEqual(name, (const xmlChar *) "delegatePublic"))
487	type = XML_CATA_DELEGATE_PUBLIC;
488    else if (xmlStrEqual(name, (const xmlChar *) "delegateSystem"))
489	type = XML_CATA_DELEGATE_SYSTEM;
490    else if (xmlStrEqual(name, (const xmlChar *) "uri"))
491	type = XML_CATA_URI;
492    else if (xmlStrEqual(name, (const xmlChar *) "rewriteURI"))
493	type = XML_CATA_REWRITE_URI;
494    else if (xmlStrEqual(name, (const xmlChar *) "delegateURI"))
495	type = XML_CATA_DELEGATE_URI;
496    else if (xmlStrEqual(name, (const xmlChar *) "nextCatalog"))
497	type = XML_CATA_NEXT_CATALOG;
498    else if (xmlStrEqual(name, (const xmlChar *) "catalog"))
499	type = XML_CATA_CATALOG;
500    return(type);
501}
502
503static xmlCatalogEntryPtr
504xmlParseXMLCatalogOneNode(xmlNodePtr cur, xmlCatalogEntryType type,
505			  const xmlChar *name, const xmlChar *attrName,
506			  const xmlChar *uriAttrName, xmlCatalogPrefer prefer) {
507    int ok = 1;
508    xmlChar *uriValue;
509    xmlChar *nameValue = NULL;
510    xmlChar *base = NULL;
511    xmlChar *URL = NULL;
512    xmlCatalogEntryPtr ret = NULL;
513
514    if (attrName != NULL) {
515	nameValue = xmlGetProp(cur, attrName);
516	if (nameValue == NULL) {
517	    xmlGenericError(xmlGenericErrorContext,
518		    "%s entry lacks '%s'\n", name, attrName);
519	    ok = 0;
520	}
521    }
522    uriValue = xmlGetProp(cur, uriAttrName);
523    if (uriValue == NULL) {
524	xmlGenericError(xmlGenericErrorContext,
525		"%s entry lacks '%s'\n", name, uriAttrName);
526	ok = 0;
527    }
528    if (!ok) {
529	if (nameValue != NULL)
530	    xmlFree(nameValue);
531	if (uriValue != NULL)
532	    xmlFree(uriValue);
533	return(NULL);
534    }
535
536    base = xmlNodeGetBase(cur->doc, cur);
537    URL = xmlBuildURI(uriValue, base);
538    if (URL != NULL) {
539	if (xmlDebugCatalogs > 1) {
540	    if (nameValue != NULL)
541		xmlGenericError(xmlGenericErrorContext,
542			"Found %s: '%s' '%s'\n", name, nameValue, URL);
543	    else
544		xmlGenericError(xmlGenericErrorContext,
545			"Found %s: '%s'\n", name, URL);
546	}
547	ret = xmlNewCatalogEntry(type, nameValue, URL, prefer);
548    } else {
549	xmlGenericError(xmlGenericErrorContext,
550		"%s entry '%s' broken ?: %s\n", name, uriAttrName, uriValue);
551    }
552    if (nameValue != NULL)
553	xmlFree(nameValue);
554    if (uriValue != NULL)
555	xmlFree(uriValue);
556    if (base != NULL)
557	xmlFree(base);
558    if (URL != NULL)
559	xmlFree(URL);
560    return(ret);
561}
562
563static void
564xmlParseXMLCatalogNode(xmlNodePtr cur, xmlCatalogPrefer prefer,
565	               xmlCatalogEntryPtr parent)
566{
567    xmlChar *uri = NULL;
568    xmlChar *URL = NULL;
569    xmlChar *base = NULL;
570    xmlCatalogEntryPtr entry = NULL;
571
572    if (cur == NULL)
573        return;
574    if (xmlStrEqual(cur->name, BAD_CAST "group")) {
575        xmlChar *prop;
576
577        prop = xmlGetProp(cur, BAD_CAST "prefer");
578        if (prop != NULL) {
579            if (xmlStrEqual(prop, BAD_CAST "system")) {
580                prefer = XML_CATA_PREFER_SYSTEM;
581            } else if (xmlStrEqual(prop, BAD_CAST "public")) {
582                prefer = XML_CATA_PREFER_PUBLIC;
583            } else {
584                xmlGenericError(xmlGenericErrorContext,
585                                "Invalid value for prefer: '%s'\n", prop);
586            }
587            xmlFree(prop);
588        }
589	/*
590	 * Recurse to propagate prefer to the subtree
591	 * (xml:base handling is automated)
592	 */
593        xmlParseXMLCatalogNodeList(cur->children, prefer, parent);
594    } else if (xmlStrEqual(cur->name, BAD_CAST "public")) {
595	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_PUBLIC,
596		BAD_CAST "public", BAD_CAST "publicId", BAD_CAST "uri", prefer);
597    } else if (xmlStrEqual(cur->name, BAD_CAST "system")) {
598	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_SYSTEM,
599		BAD_CAST "system", BAD_CAST "systemId", BAD_CAST "uri", prefer);
600    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteSystem")) {
601	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_SYSTEM,
602		BAD_CAST "rewriteSystem", BAD_CAST "systemIdStartString",
603		BAD_CAST "rewritePrefix", prefer);
604    } else if (xmlStrEqual(cur->name, BAD_CAST "delegatePublic")) {
605	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_PUBLIC,
606		BAD_CAST "delegatePublic", BAD_CAST "publicIdStartString",
607		BAD_CAST "catalog", prefer);
608    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateSystem")) {
609	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_SYSTEM,
610		BAD_CAST "delegateSystem", BAD_CAST "systemIdStartString",
611		BAD_CAST "catalog", prefer);
612    } else if (xmlStrEqual(cur->name, BAD_CAST "uri")) {
613	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_URI,
614		BAD_CAST "uri", BAD_CAST "name",
615		BAD_CAST "uri", prefer);
616    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteURI")) {
617	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_URI,
618		BAD_CAST "rewriteURI", BAD_CAST "uriStartString",
619		BAD_CAST "rewritePrefix", prefer);
620    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateURI")) {
621	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_URI,
622		BAD_CAST "delegateURI", BAD_CAST "uriStartString",
623		BAD_CAST "catalog", prefer);
624    } else if (xmlStrEqual(cur->name, BAD_CAST "nextCatalog")) {
625	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_NEXT_CATALOG,
626		BAD_CAST "nextCatalog", NULL,
627		BAD_CAST "catalog", prefer);
628    }
629    if ((entry != NULL) && (parent != NULL)) {
630	entry->parent = parent;
631	if (parent->children == NULL)
632	    parent->children = entry;
633	else {
634	    xmlCatalogEntryPtr prev;
635
636	    prev = parent->children;
637	    while (prev->next != NULL)
638		prev = prev->next;
639	    prev->next = entry;
640	}
641    }
642    if (base != NULL)
643	xmlFree(base);
644    if (uri != NULL)
645	xmlFree(uri);
646    if (URL != NULL)
647	xmlFree(URL);
648}
649
650static void
651xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
652	                   xmlCatalogEntryPtr parent) {
653    while (cur != NULL) {
654	if ((cur->ns != NULL) && (cur->ns->href != NULL) &&
655	    (xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
656	    xmlParseXMLCatalogNode(cur, prefer, parent);
657	}
658	cur = cur->next;
659    }
660    /* TODO: sort the list according to REWRITE lengths and prefer value */
661}
662
663static xmlCatalogEntryPtr
664xmlParseXMLCatalog(const xmlChar *value, xmlCatalogPrefer prefer,
665	           const char *file) {
666    xmlDocPtr doc;
667    xmlNodePtr cur;
668    xmlChar *prop;
669    xmlCatalogEntryPtr parent = NULL;
670
671    if ((value == NULL) || (file == NULL))
672        return(NULL);
673
674    if (xmlDebugCatalogs)
675	xmlGenericError(xmlGenericErrorContext,
676		"Parsing catalog %s's content\n", file);
677
678    doc = xmlParseDoc((xmlChar *) value);
679    if (doc == NULL)
680	return(NULL);
681    doc->URL = xmlStrdup((const xmlChar *) file);
682
683    cur = xmlDocGetRootElement(doc);
684    if ((cur != NULL) && (xmlStrEqual(cur->name, BAD_CAST "catalog")) &&
685	(cur->ns != NULL) && (cur->ns->href != NULL) &&
686	(xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
687
688	prop = xmlGetProp(cur, BAD_CAST "prefer");
689	if (prop != NULL) {
690	    if (xmlStrEqual(prop, BAD_CAST "system")) {
691		prefer = XML_CATA_PREFER_SYSTEM;
692	    } else if (xmlStrEqual(prop, BAD_CAST "public")) {
693		prefer = XML_CATA_PREFER_PUBLIC;
694	    } else {
695		xmlGenericError(xmlGenericErrorContext,
696			"Invalid value for prefer: '%s'\n",
697			        prop);
698	    }
699	    xmlFree(prop);
700	}
701	parent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
702		                    (const xmlChar *)file, prefer);
703        if (parent == NULL) {
704	    xmlFreeDoc(doc);
705	    return(NULL);
706	}
707
708	cur = cur->children;
709	xmlParseXMLCatalogNodeList(cur, prefer, parent);
710    } else {
711	xmlGenericError(xmlGenericErrorContext,
712			"File %s is not an XML Catalog\n", file);
713	xmlFreeDoc(doc);
714	return(NULL);
715    }
716    xmlFreeDoc(doc);
717    return(parent);
718}
719
720static xmlCatalogEntryPtr
721xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename) {
722    xmlDocPtr doc;
723    xmlNodePtr cur;
724    xmlChar *prop;
725    xmlCatalogEntryPtr parent = NULL;
726
727    if (filename == NULL)
728        return(NULL);
729
730    doc = xmlParseCatalogFile((const char *) filename);
731    if (doc == NULL) {
732	if (xmlDebugCatalogs)
733	    xmlGenericError(xmlGenericErrorContext,
734		    "Failed to parse catalog %s\n", filename);
735	return(NULL);
736    }
737
738    if (xmlDebugCatalogs)
739	xmlGenericError(xmlGenericErrorContext,
740		"Parsing catalog %s\n", filename);
741
742    cur = xmlDocGetRootElement(doc);
743    if ((cur != NULL) && (xmlStrEqual(cur->name, BAD_CAST "catalog")) &&
744	(cur->ns != NULL) && (cur->ns->href != NULL) &&
745	(xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
746
747	parent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
748		                    (const xmlChar *)filename, prefer);
749        if (parent == NULL) {
750	    xmlFreeDoc(doc);
751	    return(NULL);
752	}
753
754	prop = xmlGetProp(cur, BAD_CAST "prefer");
755	if (prop != NULL) {
756	    if (xmlStrEqual(prop, BAD_CAST "system")) {
757		prefer = XML_CATA_PREFER_SYSTEM;
758	    } else if (xmlStrEqual(prop, BAD_CAST "public")) {
759		prefer = XML_CATA_PREFER_PUBLIC;
760	    } else {
761		xmlGenericError(xmlGenericErrorContext,
762			"Invalid value for prefer: '%s'\n",
763			        prop);
764	    }
765	    xmlFree(prop);
766	}
767	cur = cur->children;
768	xmlParseXMLCatalogNodeList(cur, prefer, parent);
769    } else {
770	xmlGenericError(xmlGenericErrorContext,
771			"File %s is not an XML Catalog\n", filename);
772	xmlFreeDoc(doc);
773	return(NULL);
774    }
775    xmlFreeDoc(doc);
776    return(parent);
777}
778
779/**
780 * xmlFetchXMLCatalogFile:
781 * @catal:  an existing but incomplete catalog entry
782 *
783 * Fetch and parse the subcatalog referenced by an entry
784 * It tries to be thread safe but by lack of an atomic test and
785 * set there is a risk of loosing memory.
786 *
787 * Returns 0 in case of success, -1 otherwise
788 */
789static int
790xmlFetchXMLCatalogFile(xmlCatalogEntryPtr catal) {
791    xmlCatalogEntryPtr children = NULL, doc;
792
793    if (catal == NULL)
794	return(-1);
795    if (catal->value == NULL)
796	return(-1);
797    if (catal->children != NULL)
798	return(-1);
799
800    if (xmlCatalogXMLFiles != NULL)
801	children = (xmlCatalogEntryPtr)
802	    xmlHashLookup(xmlCatalogXMLFiles, catal->value);
803    if (children != NULL) {
804	catal->children = children;
805	catal->dealloc = 0;
806	return(0);
807    }
808
809    /*
810     * Fetch and parse
811     */
812    doc = xmlParseXMLCatalogFile(catal->prefer, catal->value);
813    if (doc == NULL) {
814	catal->type = XML_CATA_BROKEN_CATALOG;
815	return(-1);
816    }
817    if ((catal->type == XML_CATA_CATALOG) &&
818	(doc->type == XML_CATA_CATALOG)) {
819	children = doc->children;
820	doc->children = NULL;
821        xmlFreeCatalogEntryList(doc);
822    } else {
823	children = doc;
824    }
825
826    /*
827     * Where a real test and set would be needed !
828     */
829    if (catal->children == NULL) {
830	catal->children = children;
831	catal->dealloc = 1;
832	if (xmlCatalogXMLFiles == NULL)
833	    xmlCatalogXMLFiles = xmlHashCreate(10);
834	if (xmlCatalogXMLFiles != NULL) {
835	    if (children != NULL)
836		xmlHashAddEntry(xmlCatalogXMLFiles, catal->value, children);
837	}
838    } else {
839	/*
840	 * Another thread filled it before us
841	 */
842	xmlFreeCatalogEntryList(children);
843    }
844    return(0);
845}
846
847static int
848xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) {
849    int ret;
850    xmlDocPtr doc;
851    xmlNsPtr ns;
852    xmlDtdPtr dtd;
853    xmlNodePtr node, catalog;
854    xmlOutputBufferPtr buf;
855    xmlCatalogEntryPtr cur;
856
857    /*
858     * Rebuild a catalog
859     */
860    doc = xmlNewDoc(NULL);
861    if (doc == NULL)
862	return(-1);
863    dtd = xmlNewDtd(doc, BAD_CAST "catalog",
864	       BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN",
865BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd");
866
867    xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
868
869    ns = xmlNewNs(NULL, XML_CATALOGS_NAMESPACE, NULL);
870    if (ns == NULL) {
871	xmlFreeDoc(doc);
872	return(-1);
873    }
874    catalog = xmlNewDocNode(doc, ns, BAD_CAST "catalog", NULL);
875    if (catalog == NULL) {
876	xmlFreeNs(ns);
877	xmlFreeDoc(doc);
878	return(-1);
879    }
880    catalog->nsDef = ns;
881    xmlAddChild((xmlNodePtr) doc, catalog);
882
883    /*
884     * add all the catalog entries
885     */
886    cur = catal;
887    while (cur != NULL) {
888	switch (cur->type) {
889	    case XML_CATA_BROKEN_CATALOG:
890	    case XML_CATA_CATALOG:
891		if (cur == catal) {
892		    cur = cur->children;
893		    continue;
894		}
895		break;
896	    case XML_CATA_NEXT_CATALOG:
897		node = xmlNewDocNode(doc, ns, BAD_CAST "nextCatalog", NULL);
898		xmlSetProp(node, BAD_CAST "catalog", cur->value);
899		xmlAddChild(catalog, node);
900                break;
901	    case XML_CATA_NONE:
902		break;
903	    case XML_CATA_PUBLIC:
904		node = xmlNewDocNode(doc, ns, BAD_CAST "public", NULL);
905		xmlSetProp(node, BAD_CAST "publicId", cur->name);
906		xmlSetProp(node, BAD_CAST "uri", cur->value);
907		xmlAddChild(catalog, node);
908		break;
909	    case XML_CATA_SYSTEM:
910		node = xmlNewDocNode(doc, ns, BAD_CAST "system", NULL);
911		xmlSetProp(node, BAD_CAST "systemId", cur->name);
912		xmlSetProp(node, BAD_CAST "uri", cur->value);
913		xmlAddChild(catalog, node);
914		break;
915	    case XML_CATA_REWRITE_SYSTEM:
916		node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteSystem", NULL);
917		xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
918		xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
919		xmlAddChild(catalog, node);
920		break;
921	    case XML_CATA_DELEGATE_PUBLIC:
922		node = xmlNewDocNode(doc, ns, BAD_CAST "delegatePublic", NULL);
923		xmlSetProp(node, BAD_CAST "publicIdStartString", cur->name);
924		xmlSetProp(node, BAD_CAST "catalog", cur->value);
925		xmlAddChild(catalog, node);
926		break;
927	    case XML_CATA_DELEGATE_SYSTEM:
928		node = xmlNewDocNode(doc, ns, BAD_CAST "delegateSystem", NULL);
929		xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
930		xmlSetProp(node, BAD_CAST "catalog", cur->value);
931		xmlAddChild(catalog, node);
932		break;
933	    case XML_CATA_URI:
934		node = xmlNewDocNode(doc, ns, BAD_CAST "uri", NULL);
935		xmlSetProp(node, BAD_CAST "name", cur->name);
936		xmlSetProp(node, BAD_CAST "uri", cur->value);
937		xmlAddChild(catalog, node);
938		break;
939	    case XML_CATA_REWRITE_URI:
940		node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteURI", NULL);
941		xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
942		xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
943		xmlAddChild(catalog, node);
944		break;
945	    case XML_CATA_DELEGATE_URI:
946		node = xmlNewDocNode(doc, ns, BAD_CAST "delegateURI", NULL);
947		xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
948		xmlSetProp(node, BAD_CAST "catalog", cur->value);
949		xmlAddChild(catalog, node);
950		break;
951	    case SGML_CATA_SYSTEM:
952	    case SGML_CATA_PUBLIC:
953	    case SGML_CATA_ENTITY:
954	    case SGML_CATA_PENTITY:
955	    case SGML_CATA_DOCTYPE:
956	    case SGML_CATA_LINKTYPE:
957	    case SGML_CATA_NOTATION:
958	    case SGML_CATA_DELEGATE:
959	    case SGML_CATA_BASE:
960	    case SGML_CATA_CATALOG:
961	    case SGML_CATA_DOCUMENT:
962	    case SGML_CATA_SGMLDECL:
963		break;
964	}
965	cur = cur->next;
966    }
967
968    /*
969     * reserialize it
970     */
971    buf = xmlOutputBufferCreateFile(out, NULL);
972    if (buf == NULL) {
973	xmlFreeDoc(doc);
974	return(-1);
975    }
976    ret = xmlSaveFormatFileTo(buf, doc, NULL, 1);
977
978    /*
979     * Free it
980     */
981    xmlFreeDoc(doc);
982
983    return(ret);
984}
985
986/**
987 * xmlAddXMLCatalog:
988 * @catal:  top of an XML catalog
989 * @type:  the type of record to add to the catalog
990 * @orig:  the system, public or prefix to match (or NULL)
991 * @replace:  the replacement value for the match
992 *
993 * Add an entry in the XML catalog, it may overwrite existing but
994 * different entries.
995 *
996 * Returns 0 if successful, -1 otherwise
997 */
998static int
999xmlAddXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *type,
1000	      const xmlChar *orig, const xmlChar *replace) {
1001    xmlCatalogEntryPtr cur;
1002    xmlCatalogEntryType typ;
1003
1004    if ((catal == NULL) ||
1005	((catal->type != XML_CATA_CATALOG) &&
1006	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1007	return(-1);
1008    typ = xmlGetXMLCatalogEntryType(type);
1009    if (typ == XML_CATA_NONE) {
1010	if (xmlDebugCatalogs)
1011	    xmlGenericError(xmlGenericErrorContext,
1012		    "Failed to add unknown element %s to catalog\n", type);
1013	return(-1);
1014    }
1015
1016    cur = catal->children;
1017    /*
1018     * Might be a simple "update in place"
1019     */
1020    if (cur != NULL) {
1021	while (cur != NULL) {
1022	    if ((orig != NULL) && (cur->type == typ) &&
1023		(xmlStrEqual(orig, cur->name))) {
1024		if (xmlDebugCatalogs)
1025		    xmlGenericError(xmlGenericErrorContext,
1026			    "Updating element %s to catalog\n", type);
1027		if (cur->value != NULL)
1028		    xmlFree(cur->value);
1029		cur->value = xmlStrdup(replace);
1030		return(0);
1031	    }
1032	    if (cur->next == NULL)
1033		break;
1034	    cur = cur->next;
1035	}
1036    }
1037    if (xmlDebugCatalogs)
1038	xmlGenericError(xmlGenericErrorContext,
1039		"Adding element %s to catalog\n", type);
1040    if (cur == NULL)
1041	catal->children = xmlNewCatalogEntry(typ, orig, replace, catal->prefer);
1042    else
1043	cur->next = xmlNewCatalogEntry(typ, orig, replace, catal->prefer);
1044    return(0);
1045}
1046
1047/**
1048 * xmlDelXMLCatalog:
1049 * @catal:  top of an XML catalog
1050 * @value:  the value to remove from teh catalog
1051 *
1052 * Remove entries in the XML catalog where the value or the URI
1053 * is equal to @value
1054 *
1055 * Returns the number of entries removed if successful, -1 otherwise
1056 */
1057static int
1058xmlDelXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *value) {
1059    xmlCatalogEntryPtr cur, prev, tmp;
1060    int ret = 0;
1061
1062    if ((catal == NULL) ||
1063	((catal->type != XML_CATA_CATALOG) &&
1064	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1065	return(-1);
1066    if (value == NULL)
1067	return(-1);
1068
1069    /*
1070     * Scan the children
1071     */
1072    cur = catal->children;
1073    prev = NULL;
1074    while (cur != NULL) {
1075	if (((cur->name != NULL) && (xmlStrEqual(value, cur->name))) ||
1076	    (xmlStrEqual(value, cur->value))) {
1077	    if (xmlDebugCatalogs) {
1078		if (cur->name != NULL)
1079		    xmlGenericError(xmlGenericErrorContext,
1080			    "Removing element %s from catalog\n", cur->name);
1081		else
1082		    xmlGenericError(xmlGenericErrorContext,
1083			    "Removing element %s from catalog\n", cur->value);
1084	    }
1085	    ret++;
1086	    tmp = cur;
1087	    cur = tmp->next;
1088	    if (prev == NULL) {
1089		catal->children = cur;
1090	    } else {
1091		prev->next = cur;
1092	    }
1093            xmlFreeCatalogEntry(tmp);
1094	    continue;
1095	}
1096	prev = cur;
1097	cur = cur->next;
1098    }
1099    return(ret);
1100}
1101
1102/**
1103 * xmlCatalogXMLResolve:
1104 * @catal:  a catalog list
1105 * @pubId:  the public ID string
1106 * @sysId:  the system ID string
1107 *
1108 * Do a complete resolution lookup of an External Identifier for a
1109 * list of catalog entries.
1110 *
1111 * Implements (or tries to) 7.1. External Identifier Resolution
1112 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1113 *
1114 * Returns the URI of the resource or NULL if not found
1115 */
1116static xmlChar *
1117xmlCatalogXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1118	              const xmlChar *sysID) {
1119    xmlChar *ret = NULL;
1120    xmlCatalogEntryPtr cur;
1121    int haveDelegate = 0;
1122    int haveNext = 0;
1123
1124    /*
1125     * First tries steps 2/ 3/ 4/ if a system ID is provided.
1126     */
1127    if (sysID != NULL) {
1128	xmlCatalogEntryPtr rewrite = NULL;
1129	int lenrewrite = 0, len;
1130	cur = catal;
1131	haveDelegate = 0;
1132	while (cur != NULL) {
1133	    switch (cur->type) {
1134		case XML_CATA_SYSTEM:
1135		    if (xmlStrEqual(sysID, cur->name)) {
1136			if (xmlDebugCatalogs)
1137			    xmlGenericError(xmlGenericErrorContext,
1138				    "Found system match %s\n", cur->name);
1139			return(xmlStrdup(cur->value));
1140		    }
1141		    break;
1142		case XML_CATA_REWRITE_SYSTEM:
1143		    len = xmlStrlen(cur->name);
1144		    if ((len > lenrewrite) &&
1145			(!xmlStrncmp(sysID, cur->name, len))) {
1146			lenrewrite = len;
1147			rewrite = cur;
1148		    }
1149		    break;
1150		case XML_CATA_DELEGATE_SYSTEM:
1151		    if (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))
1152			haveDelegate++;
1153		    break;
1154		case XML_CATA_NEXT_CATALOG:
1155		    haveNext++;
1156		    break;
1157		default:
1158		    break;
1159	    }
1160	    cur = cur->next;
1161	}
1162	if (rewrite != NULL) {
1163	    if (xmlDebugCatalogs)
1164		xmlGenericError(xmlGenericErrorContext,
1165			"Using rewriting rule %s\n", rewrite->name);
1166	    ret = xmlStrdup(rewrite->value);
1167	    if (ret != NULL)
1168		ret = xmlStrcat(ret, &sysID[lenrewrite]);
1169	    return(ret);
1170	}
1171	if (haveDelegate) {
1172	    const xmlChar *delegates[MAX_DELEGATE];
1173	    int nbList = 0, i;
1174
1175	    /*
1176	     * Assume the entries have been sorted by decreasing substring
1177	     * matches when the list was produced.
1178	     */
1179	    cur = catal;
1180	    while (cur != NULL) {
1181		if ((cur->type == XML_CATA_DELEGATE_SYSTEM) &&
1182		    (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))) {
1183		    for (i = 0;i < nbList;i++)
1184			if (xmlStrEqual(cur->value, delegates[i]))
1185			    break;
1186		    if (i < nbList) {
1187			cur = cur->next;
1188			continue;
1189		    }
1190		    if (nbList < MAX_DELEGATE)
1191			delegates[nbList++] = cur->value;
1192
1193		    if (cur->children == NULL) {
1194			xmlFetchXMLCatalogFile(cur);
1195		    }
1196		    if (cur->children != NULL) {
1197			if (xmlDebugCatalogs)
1198			    xmlGenericError(xmlGenericErrorContext,
1199				    "Trying system delegate %s\n", cur->value);
1200			ret = xmlCatalogListXMLResolve(cur->children, NULL,
1201				                       sysID);
1202			if (ret != NULL)
1203			    return(ret);
1204		    }
1205		}
1206		cur = cur->next;
1207	    }
1208	    /*
1209	     * Apply the cut algorithm explained in 4/
1210	     */
1211	    return(XML_CATAL_BREAK);
1212	}
1213    }
1214    /*
1215     * Then tries 5/ 6/ if a public ID is provided
1216     */
1217    if (pubID != NULL) {
1218	cur = catal;
1219	haveDelegate = 0;
1220	while (cur != NULL) {
1221	    switch (cur->type) {
1222		case XML_CATA_PUBLIC:
1223		    if (xmlStrEqual(pubID, cur->name)) {
1224			if (xmlDebugCatalogs)
1225			    xmlGenericError(xmlGenericErrorContext,
1226				    "Found public match %s\n", cur->name);
1227			return(xmlStrdup(cur->value));
1228		    }
1229		    break;
1230		case XML_CATA_DELEGATE_PUBLIC:
1231		    if (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)) &&
1232			(cur->prefer == XML_CATA_PREFER_PUBLIC))
1233			haveDelegate++;
1234		    break;
1235		case XML_CATA_NEXT_CATALOG:
1236		    if (sysID == NULL)
1237			haveNext++;
1238		    break;
1239		default:
1240		    break;
1241	    }
1242	    cur = cur->next;
1243	}
1244	if (haveDelegate) {
1245	    const xmlChar *delegates[MAX_DELEGATE];
1246	    int nbList = 0, i;
1247
1248	    /*
1249	     * Assume the entries have been sorted by decreasing substring
1250	     * matches when the list was produced.
1251	     */
1252	    cur = catal;
1253	    while (cur != NULL) {
1254		if ((cur->type == XML_CATA_DELEGATE_PUBLIC) &&
1255		    (cur->prefer == XML_CATA_PREFER_PUBLIC) &&
1256		    (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)))) {
1257
1258		    for (i = 0;i < nbList;i++)
1259			if (xmlStrEqual(cur->value, delegates[i]))
1260			    break;
1261		    if (i < nbList) {
1262			cur = cur->next;
1263			continue;
1264		    }
1265		    if (nbList < MAX_DELEGATE)
1266			delegates[nbList++] = cur->value;
1267
1268		    if (cur->children == NULL) {
1269			xmlFetchXMLCatalogFile(cur);
1270		    }
1271		    if (cur->children != NULL) {
1272			if (xmlDebugCatalogs)
1273			    xmlGenericError(xmlGenericErrorContext,
1274				    "Trying public delegate %s\n", cur->value);
1275			ret = xmlCatalogListXMLResolve(cur->children, pubID,
1276				                       NULL);
1277			if (ret != NULL)
1278			    return(ret);
1279		    }
1280		}
1281		cur = cur->next;
1282	    }
1283	    /*
1284	     * Apply the cut algorithm explained in 4/
1285	     */
1286	    return(XML_CATAL_BREAK);
1287	}
1288    }
1289    if (haveNext) {
1290	cur = catal;
1291	while (cur != NULL) {
1292	    if (cur->type == XML_CATA_NEXT_CATALOG) {
1293		if (cur->children == NULL) {
1294		    xmlFetchXMLCatalogFile(cur);
1295		}
1296		if (cur->children != NULL) {
1297		    ret = xmlCatalogListXMLResolve(cur->children, pubID, sysID);
1298		    if (ret != NULL)
1299			return(ret);
1300		}
1301	    }
1302	    cur = cur->next;
1303	}
1304    }
1305
1306    return(NULL);
1307}
1308
1309/**
1310 * xmlCatalogXMLResolveURI:
1311 * @catal:  a catalog list
1312 * @URI:  the URI
1313 * @sysId:  the system ID string
1314 *
1315 * Do a complete resolution lookup of an External Identifier for a
1316 * list of catalog entries.
1317 *
1318 * Implements (or tries to) 7.2.2. URI Resolution
1319 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1320 *
1321 * Returns the URI of the resource or NULL if not found
1322 */
1323static xmlChar *
1324xmlCatalogXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) {
1325    xmlChar *ret = NULL;
1326    xmlCatalogEntryPtr cur;
1327    int haveDelegate = 0;
1328    int haveNext = 0;
1329    xmlCatalogEntryPtr rewrite = NULL;
1330    int lenrewrite = 0, len;
1331
1332    if (catal == NULL)
1333	return(NULL);
1334
1335    if (URI == NULL)
1336	return(NULL);
1337
1338    /*
1339     * First tries steps 2/ 3/ 4/ if a system ID is provided.
1340     */
1341    cur = catal;
1342    haveDelegate = 0;
1343    while (cur != NULL) {
1344	switch (cur->type) {
1345	    case XML_CATA_URI:
1346		if (xmlStrEqual(URI, cur->name)) {
1347		    if (xmlDebugCatalogs)
1348			xmlGenericError(xmlGenericErrorContext,
1349				"Found URI match %s\n", cur->name);
1350		    return(xmlStrdup(cur->value));
1351		}
1352		break;
1353	    case XML_CATA_REWRITE_URI:
1354		len = xmlStrlen(cur->name);
1355		if ((len > lenrewrite) &&
1356		    (!xmlStrncmp(URI, cur->name, len))) {
1357		    lenrewrite = len;
1358		    rewrite = cur;
1359		}
1360		break;
1361	    case XML_CATA_DELEGATE_URI:
1362		if (!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))
1363		    haveDelegate++;
1364		break;
1365	    case XML_CATA_NEXT_CATALOG:
1366		haveNext++;
1367		break;
1368	    default:
1369		break;
1370	}
1371	cur = cur->next;
1372    }
1373    if (rewrite != NULL) {
1374	if (xmlDebugCatalogs)
1375	    xmlGenericError(xmlGenericErrorContext,
1376		    "Using rewriting rule %s\n", rewrite->name);
1377	ret = xmlStrdup(rewrite->value);
1378	if (ret != NULL)
1379	    ret = xmlStrcat(ret, &URI[lenrewrite]);
1380	return(ret);
1381    }
1382    if (haveDelegate) {
1383	const xmlChar *delegates[MAX_DELEGATE];
1384	int nbList = 0, i;
1385
1386	/*
1387	 * Assume the entries have been sorted by decreasing substring
1388	 * matches when the list was produced.
1389	 */
1390	cur = catal;
1391	while (cur != NULL) {
1392	    if ((cur->type == XML_CATA_DELEGATE_SYSTEM) &&
1393		(!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))) {
1394		for (i = 0;i < nbList;i++)
1395		    if (xmlStrEqual(cur->value, delegates[i]))
1396			break;
1397		if (i < nbList) {
1398		    cur = cur->next;
1399		    continue;
1400		}
1401		if (nbList < MAX_DELEGATE)
1402		    delegates[nbList++] = cur->value;
1403
1404		if (cur->children == NULL) {
1405		    xmlFetchXMLCatalogFile(cur);
1406		}
1407		if (cur->children != NULL) {
1408		    if (xmlDebugCatalogs)
1409			xmlGenericError(xmlGenericErrorContext,
1410				"Trying URI delegate %s\n", cur->value);
1411		    ret = xmlCatalogListXMLResolveURI(cur->children, URI);
1412		    if (ret != NULL)
1413			return(ret);
1414		}
1415	    }
1416	    cur = cur->next;
1417	}
1418	/*
1419	 * Apply the cut algorithm explained in 4/
1420	 */
1421	return(XML_CATAL_BREAK);
1422    }
1423    if (haveNext) {
1424	cur = catal;
1425	while (cur != NULL) {
1426	    if (cur->type == XML_CATA_NEXT_CATALOG) {
1427		if (cur->children == NULL) {
1428		    xmlFetchXMLCatalogFile(cur);
1429		}
1430		if (cur->children != NULL) {
1431		    ret = xmlCatalogListXMLResolveURI(cur->children, URI);
1432		    if (ret != NULL)
1433			return(ret);
1434		}
1435	    }
1436	    cur = cur->next;
1437	}
1438    }
1439
1440    return(NULL);
1441}
1442
1443/**
1444 * xmlCatalogListXMLResolve:
1445 * @catal:  a catalog list
1446 * @pubId:  the public ID string
1447 * @sysId:  the system ID string
1448 *
1449 * Do a complete resolution lookup of an External Identifier for a
1450 * list of catalogs
1451 *
1452 * Implements (or tries to) 7.1. External Identifier Resolution
1453 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1454 *
1455 * Returns the URI of the resource or NULL if not found
1456 */
1457static xmlChar *
1458xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1459	              const xmlChar *sysID) {
1460    xmlChar *ret = NULL;
1461    xmlChar *urnID = NULL;
1462
1463    if (catal == NULL)
1464        return(NULL);
1465    if ((pubID == NULL) && (sysID == NULL))
1466	return(NULL);
1467
1468    if (!xmlStrncmp(pubID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
1469	urnID = xmlCatalogUnWrapURN(pubID);
1470	if (xmlDebugCatalogs) {
1471	    if (urnID == NULL)
1472		xmlGenericError(xmlGenericErrorContext,
1473			"Public URN ID %s expanded to NULL\n", pubID);
1474	    else
1475		xmlGenericError(xmlGenericErrorContext,
1476			"Public URN ID expanded to %s\n", urnID);
1477	}
1478	ret = xmlCatalogListXMLResolve(catal, urnID, sysID);
1479	if (urnID != NULL)
1480	    xmlFree(urnID);
1481	return(ret);
1482    }
1483    if (!xmlStrncmp(sysID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
1484	urnID = xmlCatalogUnWrapURN(sysID);
1485	if (xmlDebugCatalogs) {
1486	    if (urnID == NULL)
1487		xmlGenericError(xmlGenericErrorContext,
1488			"System URN ID %s expanded to NULL\n", sysID);
1489	    else
1490		xmlGenericError(xmlGenericErrorContext,
1491			"System URN ID expanded to %s\n", urnID);
1492	}
1493	if (pubID == NULL)
1494	    ret = xmlCatalogListXMLResolve(catal, urnID, NULL);
1495	else if (xmlStrEqual(pubID, urnID))
1496	    ret = xmlCatalogListXMLResolve(catal, pubID, NULL);
1497	else {
1498	    ret = xmlCatalogListXMLResolve(catal, pubID, NULL);
1499	}
1500	if (urnID != NULL)
1501	    xmlFree(urnID);
1502	return(ret);
1503    }
1504    while (catal != NULL) {
1505	if (catal->type == XML_CATA_CATALOG) {
1506	    if (catal->children == NULL) {
1507		xmlFetchXMLCatalogFile(catal);
1508	    }
1509	    if (catal->children != NULL) {
1510		ret = xmlCatalogXMLResolve(catal->children, pubID, sysID);
1511		if (ret != NULL)
1512		    return(ret);
1513	    }
1514	}
1515	catal = catal->next;
1516    }
1517    return(ret);
1518}
1519
1520/**
1521 * xmlCatalogListXMLResolveURI:
1522 * @catal:  a catalog list
1523 * @URI:  the URI
1524 *
1525 * Do a complete resolution lookup of an URI for a list of catalogs
1526 *
1527 * Implements (or tries to) 7.2. URI Resolution
1528 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1529 *
1530 * Returns the URI of the resource or NULL if not found
1531 */
1532static xmlChar *
1533xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) {
1534    xmlChar *ret = NULL;
1535    xmlChar *urnID = NULL;
1536
1537    if (catal == NULL)
1538        return(NULL);
1539    if (URI == NULL)
1540	return(NULL);
1541
1542    if (!xmlStrncmp(URI, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
1543	urnID = xmlCatalogUnWrapURN(URI);
1544	if (xmlDebugCatalogs) {
1545	    if (urnID == NULL)
1546		xmlGenericError(xmlGenericErrorContext,
1547			"URN ID %s expanded to NULL\n", URI);
1548	    else
1549		xmlGenericError(xmlGenericErrorContext,
1550			"URN ID expanded to %s\n", urnID);
1551	}
1552	ret = xmlCatalogListXMLResolve(catal, urnID, NULL);
1553	if (urnID != NULL)
1554	    xmlFree(urnID);
1555	return(ret);
1556    }
1557    while (catal != NULL) {
1558	if (catal->type == XML_CATA_CATALOG) {
1559	    if (catal->children == NULL) {
1560		xmlFetchXMLCatalogFile(catal);
1561	    }
1562	    if (catal->children != NULL) {
1563		ret = xmlCatalogXMLResolveURI(catal->children, URI);
1564		if (ret != NULL)
1565		    return(ret);
1566	    }
1567	}
1568	catal = catal->next;
1569    }
1570    return(ret);
1571}
1572
1573/************************************************************************
1574 *									*
1575 *			The SGML Catalog parser				*
1576 *									*
1577 ************************************************************************/
1578
1579
1580#define RAW *cur
1581#define NEXT cur++;
1582#define SKIP(x) cur += x;
1583
1584#define SKIP_BLANKS while (IS_BLANK(*cur)) NEXT;
1585
1586static const xmlChar *
1587xmlParseSGMLCatalogComment(const xmlChar *cur) {
1588    if ((cur[0] != '-') || (cur[1] != '-'))
1589	return(cur);
1590    SKIP(2);
1591    while ((cur[0] != 0) && ((cur[0] != '-') || ((cur[1] != '-'))))
1592	NEXT;
1593    if (cur[0] == 0) {
1594	return(NULL);
1595    }
1596    return(cur + 2);
1597}
1598
1599static const xmlChar *
1600xmlParseSGMLCatalogPubid(const xmlChar *cur, xmlChar **id) {
1601    xmlChar *buf = NULL;
1602    int len = 0;
1603    int size = 50;
1604    xmlChar stop;
1605    int count = 0;
1606
1607    *id = NULL;
1608
1609    if (RAW == '"') {
1610        NEXT;
1611	stop = '"';
1612    } else if (RAW == '\'') {
1613        NEXT;
1614	stop = '\'';
1615    } else {
1616	stop = ' ';
1617    }
1618    buf = (xmlChar *) xmlMalloc(size * sizeof(xmlChar));
1619    if (buf == NULL) {
1620	xmlGenericError(xmlGenericErrorContext,
1621		"malloc of %d byte failed\n", size);
1622	return(NULL);
1623    }
1624    while (xmlIsPubidChar(*cur)) {
1625	if ((*cur == stop) && (stop != ' '))
1626	    break;
1627	if ((stop == ' ') && (IS_BLANK(*cur)))
1628	    break;
1629	if (len + 1 >= size) {
1630	    size *= 2;
1631	    buf = (xmlChar *) xmlRealloc(buf, size * sizeof(xmlChar));
1632	    if (buf == NULL) {
1633		xmlGenericError(xmlGenericErrorContext,
1634			"realloc of %d byte failed\n", size);
1635		return(NULL);
1636	    }
1637	}
1638	buf[len++] = *cur;
1639	count++;
1640	NEXT;
1641    }
1642    buf[len] = 0;
1643    if (stop == ' ') {
1644	if (!IS_BLANK(*cur)) {
1645	    xmlFree(buf);
1646	    return(NULL);
1647	}
1648    } else {
1649	if (*cur != stop) {
1650	    xmlFree(buf);
1651	    return(NULL);
1652	}
1653	NEXT;
1654    }
1655    *id = buf;
1656    return(cur);
1657}
1658
1659static const xmlChar *
1660xmlParseSGMLCatalogName(const xmlChar *cur, xmlChar **name) {
1661    xmlChar buf[XML_MAX_NAMELEN + 5];
1662    int len = 0;
1663    int c;
1664
1665    *name = NULL;
1666
1667    /*
1668     * Handler for more complex cases
1669     */
1670    c = *cur;
1671    if ((!IS_LETTER(c) && (c != '_') && (c != ':'))) {
1672	return(NULL);
1673    }
1674
1675    while (((IS_LETTER(c)) || (IS_DIGIT(c)) ||
1676            (c == '.') || (c == '-') ||
1677	    (c == '_') || (c == ':'))) {
1678	buf[len++] = c;
1679	cur++;
1680	c = *cur;
1681	if (len >= XML_MAX_NAMELEN)
1682	    return(NULL);
1683    }
1684    *name = xmlStrndup(buf, len);
1685    return(cur);
1686}
1687
1688static xmlCatalogEntryType
1689xmlGetSGMLCatalogEntryType(const xmlChar *name) {
1690    xmlCatalogEntryType type = XML_CATA_NONE;
1691    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
1692	type = SGML_CATA_SYSTEM;
1693    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
1694	type = SGML_CATA_PUBLIC;
1695    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
1696	type = SGML_CATA_DELEGATE;
1697    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
1698	type = SGML_CATA_ENTITY;
1699    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
1700	type = SGML_CATA_DOCTYPE;
1701    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
1702	type = SGML_CATA_LINKTYPE;
1703    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
1704	type = SGML_CATA_NOTATION;
1705    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
1706	type = SGML_CATA_SGMLDECL;
1707    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
1708	type = SGML_CATA_DOCUMENT;
1709    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
1710	type = SGML_CATA_CATALOG;
1711    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
1712	type = SGML_CATA_BASE;
1713    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
1714	type = SGML_CATA_DELEGATE;
1715    return(type);
1716}
1717
1718static int
1719xmlParseSGMLCatalog(const xmlChar *value, const char *file) {
1720    const xmlChar *cur = value;
1721    xmlChar *base = NULL;
1722    int res;
1723
1724    if ((cur == NULL) || (file == NULL))
1725        return(-1);
1726    base = xmlStrdup((const xmlChar *) file);
1727
1728    while ((cur != NULL) && (cur[0] != 0)) {
1729	SKIP_BLANKS;
1730	if (cur[0] == 0)
1731	    break;
1732	if ((cur[0] == '-') && (cur[1] == '-')) {
1733	    cur = xmlParseSGMLCatalogComment(cur);
1734	    if (cur == NULL) {
1735		/* error */
1736		break;
1737	    }
1738	} else {
1739	    xmlChar *sysid = NULL;
1740	    xmlChar *name = NULL;
1741	    xmlCatalogEntryType type = XML_CATA_NONE;
1742
1743	    cur = xmlParseSGMLCatalogName(cur, &name);
1744	    if (name == NULL) {
1745		/* error */
1746		break;
1747	    }
1748	    if (!IS_BLANK(*cur)) {
1749		/* error */
1750		break;
1751	    }
1752	    SKIP_BLANKS;
1753	    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
1754                type = SGML_CATA_SYSTEM;
1755	    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
1756                type = SGML_CATA_PUBLIC;
1757	    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
1758                type = SGML_CATA_DELEGATE;
1759	    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
1760                type = SGML_CATA_ENTITY;
1761	    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
1762                type = SGML_CATA_DOCTYPE;
1763	    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
1764                type = SGML_CATA_LINKTYPE;
1765	    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
1766                type = SGML_CATA_NOTATION;
1767	    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
1768                type = SGML_CATA_SGMLDECL;
1769	    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
1770                type = SGML_CATA_DOCUMENT;
1771	    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
1772                type = SGML_CATA_CATALOG;
1773	    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
1774                type = SGML_CATA_BASE;
1775	    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
1776                type = SGML_CATA_DELEGATE;
1777	    else if (xmlStrEqual(name, (const xmlChar *) "OVERRIDE")) {
1778		xmlFree(name);
1779		cur = xmlParseSGMLCatalogName(cur, &name);
1780		if (name == NULL) {
1781		    /* error */
1782		    break;
1783		}
1784		xmlFree(name);
1785		continue;
1786	    }
1787	    xmlFree(name);
1788	    name = NULL;
1789
1790	    switch(type) {
1791		case SGML_CATA_ENTITY:
1792		    if (*cur == '%')
1793			type = SGML_CATA_PENTITY;
1794		case SGML_CATA_PENTITY:
1795		case SGML_CATA_DOCTYPE:
1796		case SGML_CATA_LINKTYPE:
1797		case SGML_CATA_NOTATION:
1798		    cur = xmlParseSGMLCatalogName(cur, &name);
1799		    if (cur == NULL) {
1800			/* error */
1801			break;
1802		    }
1803		    if (!IS_BLANK(*cur)) {
1804			/* error */
1805			break;
1806		    }
1807		    SKIP_BLANKS;
1808		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
1809		    if (cur == NULL) {
1810			/* error */
1811			break;
1812		    }
1813		    break;
1814		case SGML_CATA_PUBLIC:
1815		case SGML_CATA_SYSTEM:
1816		case SGML_CATA_DELEGATE:
1817		    cur = xmlParseSGMLCatalogPubid(cur, &name);
1818		    if (cur == NULL) {
1819			/* error */
1820			break;
1821		    }
1822		    if (!IS_BLANK(*cur)) {
1823			/* error */
1824			break;
1825		    }
1826		    SKIP_BLANKS;
1827		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
1828		    if (cur == NULL) {
1829			/* error */
1830			break;
1831		    }
1832		    break;
1833		case SGML_CATA_BASE:
1834		case SGML_CATA_CATALOG:
1835		case SGML_CATA_DOCUMENT:
1836		case SGML_CATA_SGMLDECL:
1837		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
1838		    if (cur == NULL) {
1839			/* error */
1840			break;
1841		    }
1842		    break;
1843		default:
1844		    break;
1845	    }
1846	    if (cur == NULL) {
1847		if (name != NULL)
1848		    xmlFree(name);
1849		if (sysid != NULL)
1850		    xmlFree(sysid);
1851		break;
1852	    } else if (type == SGML_CATA_BASE) {
1853		if (base != NULL)
1854		    xmlFree(base);
1855		base = xmlStrdup(sysid);
1856	    } else if ((type == SGML_CATA_PUBLIC) ||
1857		       (type == SGML_CATA_SYSTEM)) {
1858		xmlChar *filename;
1859
1860		filename = xmlBuildURI(sysid, base);
1861		if (filename != NULL) {
1862		    xmlCatalogEntryPtr entry;
1863
1864		    entry = xmlNewCatalogEntry(type, name, filename,
1865			                       XML_CATA_PREFER_NONE);
1866		    res = xmlHashAddEntry(xmlDefaultCatalog, name, entry);
1867		    if (res < 0) {
1868			xmlFreeCatalogEntry(entry);
1869		    }
1870		    xmlFree(filename);
1871		}
1872
1873	    } else if (type == SGML_CATA_CATALOG) {
1874		xmlChar *filename;
1875
1876		filename = xmlBuildURI(sysid, base);
1877		if (filename != NULL) {
1878		    xmlLoadCatalog((const char *)filename);
1879		    xmlFree(filename);
1880		}
1881	    }
1882	    /*
1883	     * drop anything else we won't handle it
1884	     */
1885	    if (name != NULL)
1886		xmlFree(name);
1887	    if (sysid != NULL)
1888		xmlFree(sysid);
1889	}
1890    }
1891    if (base != NULL)
1892	xmlFree(base);
1893    if (cur == NULL)
1894	return(-1);
1895    return(0);
1896}
1897
1898/**
1899 * xmlCatalogGetSGMLPublic:
1900 * @catal:  an SGML catalog hash
1901 * @pubId:  the public ID string
1902 *
1903 * Try to lookup the system ID associated to a public ID
1904 *
1905 * Returns the system ID if found or NULL otherwise.
1906 */
1907static const xmlChar *
1908xmlCatalogGetSGMLPublic(xmlHashTablePtr catal, const xmlChar *pubID) {
1909    xmlCatalogEntryPtr entry;
1910
1911    if (catal == NULL)
1912	return(NULL);
1913
1914    entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, pubID);
1915    if (entry == NULL)
1916	return(NULL);
1917    if (entry->type == SGML_CATA_PUBLIC)
1918	return(entry->value);
1919    return(NULL);
1920}
1921
1922/**
1923 * xmlCatalogGetSGMLSystem:
1924 * @catal:  an SGML catalog hash
1925 * @sysId:  the public ID string
1926 *
1927 * Try to lookup the catalog local reference for a system ID
1928 *
1929 * Returns the system ID if found or NULL otherwise.
1930 */
1931static const xmlChar *
1932xmlCatalogGetSGMLSystem(xmlHashTablePtr catal, const xmlChar *sysID) {
1933    xmlCatalogEntryPtr entry;
1934
1935    if (catal == NULL)
1936	return(NULL);
1937
1938    entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, sysID);
1939    if (entry == NULL)
1940	return(NULL);
1941    if (entry->type == SGML_CATA_SYSTEM)
1942	return(entry->value);
1943    return(NULL);
1944}
1945
1946/**
1947 * xmlCatalogSGMLResolve:
1948 * @pubId:  the public ID string
1949 * @sysId:  the system ID string
1950 *
1951 * Do a complete resolution lookup of an External Identifier
1952 *
1953 * Returns the URI of the resource or NULL if not found
1954 */
1955static const xmlChar *
1956xmlCatalogSGMLResolve(const xmlChar *pubID, const xmlChar *sysID) {
1957    const xmlChar *ret = NULL;
1958
1959    if (xmlDefaultCatalog == NULL)
1960	return(NULL);
1961
1962    if (pubID != NULL)
1963	ret = xmlCatalogGetSGMLPublic(xmlDefaultCatalog, pubID);
1964    if (ret != NULL)
1965	return(ret);
1966    if (sysID != NULL)
1967	ret = xmlCatalogGetSGMLSystem(xmlDefaultCatalog, sysID);
1968    return(NULL);
1969}
1970
1971/************************************************************************
1972 *									*
1973 *			Public interfaces				*
1974 *									*
1975 ************************************************************************/
1976
1977/**
1978 * xmlInitializeCatalog:
1979 *
1980 * Do the catalog initialization.
1981 * TODO: this function is not thread safe, catalog initialization should
1982 *       preferably be done once at startup
1983 */
1984void
1985xmlInitializeCatalog(void) {
1986    const char *catalogs;
1987
1988    if (xmlCatalogInitialized != 0)
1989	return;
1990
1991    if (getenv("XML_DEBUG_CATALOG"))
1992	xmlDebugCatalogs = 1;
1993    if ((xmlDefaultXMLCatalogList == NULL) && (xmlDefaultCatalog == NULL)) {
1994	catalogs = getenv("XML_CATALOG_FILES");
1995	if (catalogs == NULL)
1996	    catalogs = XML_DEFAULT_CATALOG;
1997	xmlDefaultXMLCatalogList = xmlNewCatalogEntry(XML_CATA_CATALOG,
1998			   NULL, BAD_CAST catalogs, xmlCatalogDefaultPrefer);
1999    }
2000
2001    xmlCatalogInitialized = 1;
2002}
2003
2004/**
2005 * xmlLoadCatalog:
2006 * @filename:  a file path
2007 *
2008 * Load the catalog and makes its definitions effective for the default
2009 * external entity loader. It will recurse in SGML CATALOG entries.
2010 * TODO: this function is not thread safe, catalog initialization should
2011 *       preferably be done once at startup
2012 *
2013 * Returns 0 in case of success -1 in case of error
2014 */
2015int
2016xmlLoadCatalog(const char *filename) {
2017    int fd, len, ret, i;
2018    struct stat info;
2019    xmlChar *content;
2020
2021    if (filename == NULL)
2022	return(-1);
2023
2024    if (xmlDefaultCatalog == NULL)
2025	xmlDefaultCatalog = xmlHashCreate(20);
2026    if (xmlDefaultCatalog == NULL)
2027	return(-1);
2028
2029    /*
2030     * Need to be done after ...
2031     */
2032    if (!xmlCatalogInitialized)
2033	xmlInitializeCatalog();
2034
2035#ifdef HAVE_STAT
2036    if (stat(filename, &info) < 0)
2037	return(-1);
2038#endif
2039
2040    /*
2041     * Prevent loops
2042     */
2043    for (i = 0;i < catalNr;i++) {
2044	if (xmlStrEqual((const xmlChar *)catalTab[i],
2045		        (const xmlChar *)filename)) {
2046	    xmlGenericError(xmlGenericErrorContext,
2047		"xmlLoadCatalog: %s seems to induce a loop\n",
2048		            filename);
2049	    return(-1);
2050	}
2051    }
2052    if (catalNr >= catalMax) {
2053	xmlGenericError(xmlGenericErrorContext,
2054	    "xmlLoadCatalog: %s catalog list too deep\n",
2055			filename);
2056	    return(-1);
2057    }
2058    catalTab[catalNr++] = filename;
2059
2060    if ((fd = open(filename, O_RDONLY)) < 0) {
2061	catalNr--;
2062	return(-1);
2063    }
2064
2065    content = xmlMalloc(info.st_size + 10);
2066    if (content == NULL) {
2067	xmlGenericError(xmlGenericErrorContext,
2068		"realloc of %d byte failed\n", info.st_size + 10);
2069	catalNr--;
2070	return(-1);
2071    }
2072    len = read(fd, content, info.st_size);
2073    if (len < 0) {
2074	xmlFree(content);
2075	catalNr--;
2076	return(-1);
2077    }
2078    content[len] = 0;
2079    close(fd);
2080
2081    if ((content[0] == ' ') || (content[0] == '-') ||
2082	((content[0] >= 'A') && (content[0] <= 'Z')) ||
2083	((content[0] >= 'a') && (content[0] <= 'z')))
2084	ret = xmlParseSGMLCatalog(content, filename);
2085    else {
2086	xmlCatalogEntryPtr catal, tmp;
2087	/* TODO: allow to switch the default preference */
2088	catal = xmlParseXMLCatalog(content, XML_CATA_PREFER_PUBLIC, filename);
2089	if (catal != NULL) {
2090	    if (xmlDefaultXMLCatalogList == NULL)
2091		xmlDefaultXMLCatalogList = catal;
2092	    else {
2093		tmp = xmlDefaultXMLCatalogList;
2094		while (tmp->next != NULL)
2095		    tmp = tmp->next;
2096		tmp->next = catal;
2097	    }
2098	    ret = 0;
2099	} else
2100	    ret = -1;
2101    }
2102    xmlFree(content);
2103    catalNr--;
2104    return(ret);
2105}
2106
2107/**
2108 * xmlLoadCatalogs:
2109 * @paths:  a list of file path separated by ':' or spaces
2110 *
2111 * Load the catalogs and makes their definitions effective for the default
2112 * external entity loader.
2113 * TODO: this function is not thread safe, catalog initialization should
2114 *       preferably be done once at startup
2115 */
2116void
2117xmlLoadCatalogs(const char *pathss) {
2118    const char *cur;
2119    const char *paths;
2120    xmlChar *path;
2121
2122    if (pathss == NULL)
2123	return;
2124
2125    cur = pathss;
2126    while ((cur != NULL) && (*cur != 0)) {
2127	while (IS_BLANK(*cur)) cur++;
2128	if (*cur != 0) {
2129	    paths = cur;
2130	    while ((*cur != 0) && (*cur != ':') && (!IS_BLANK(*cur)))
2131		cur++;
2132	    path = xmlStrndup((const xmlChar *)paths, cur - paths);
2133	    if (path != NULL) {
2134		xmlLoadCatalog((const char *) path);
2135		xmlFree(path);
2136	    }
2137	}
2138	while (*cur == ':')
2139	    cur++;
2140    }
2141}
2142
2143/**
2144 * xmlCatalogCleanup:
2145 *
2146 * Free up all the memory associated with catalogs
2147 */
2148void
2149xmlCatalogCleanup(void) {
2150    if (xmlDebugCatalogs)
2151	xmlGenericError(xmlGenericErrorContext,
2152		"Catalogs cleanup\n");
2153    if (xmlCatalogXMLFiles != NULL)
2154	xmlHashFree(xmlCatalogXMLFiles, NULL);
2155    xmlCatalogXMLFiles = NULL;
2156    if (xmlDefaultXMLCatalogList != NULL)
2157	xmlFreeCatalogEntryList(xmlDefaultXMLCatalogList);
2158    xmlDefaultXMLCatalogList = NULL;
2159    if (xmlDefaultCatalog != NULL)
2160	xmlHashFree(xmlDefaultCatalog,
2161		    (xmlHashDeallocator) xmlFreeCatalogEntry);
2162    xmlDefaultCatalog = NULL;
2163    xmlDebugCatalogs = 0;
2164    xmlDefaultCatalog = NULL;
2165    xmlCatalogInitialized = 0;
2166}
2167
2168/**
2169 * xmlCatalogGetSystem:
2170 * @pubId:  the public ID string
2171 *
2172 * Try to lookup the system ID associated to a public ID
2173 * DEPRECATED, use xmlCatalogResolveSystem()
2174 *
2175 * Returns the system ID if found or NULL otherwise.
2176 */
2177const xmlChar *
2178xmlCatalogGetSystem(const xmlChar *sysID) {
2179    xmlChar *ret;
2180    static xmlChar result[1000];
2181
2182    if (sysID == NULL)
2183	return(NULL);
2184
2185    if (!xmlCatalogInitialized)
2186	xmlInitializeCatalog();
2187
2188    /*
2189     * Check first the XML catalogs
2190     */
2191    if (xmlDefaultXMLCatalogList != NULL) {
2192	ret = xmlCatalogListXMLResolve(xmlDefaultXMLCatalogList, NULL, sysID);
2193	if (ret != NULL) {
2194	    snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret);
2195	    result[sizeof(result) - 1] = 0;
2196	    return(result);
2197	}
2198    }
2199
2200    if (xmlDefaultCatalog != NULL)
2201	return(xmlCatalogGetSGMLSystem(xmlDefaultCatalog, sysID));
2202    return(NULL);
2203}
2204
2205/**
2206 * xmlCatalogResolveSystem:
2207 * @sysId:  the public ID string
2208 *
2209 * Try to lookup the catalog resource for a system ID
2210 *
2211 * Returns the system ID if found or NULL otherwise, the value returned
2212 *      must be freed by the caller.
2213 */
2214xmlChar *
2215xmlCatalogResolveSystem(const xmlChar *sysID) {
2216    xmlCatalogEntryPtr catal;
2217    xmlChar *ret;
2218    const xmlChar *sgml;
2219
2220    if (sysID == NULL)
2221	return(NULL);
2222
2223    if (!xmlCatalogInitialized)
2224	xmlInitializeCatalog();
2225
2226    /*
2227     * Check first the XML catalogs
2228     */
2229    catal = xmlDefaultXMLCatalogList;
2230    if (catal != NULL) {
2231	ret = xmlCatalogListXMLResolve(xmlDefaultXMLCatalogList, NULL, sysID);
2232	if ((ret != NULL) && (ret != XML_CATAL_BREAK))
2233	    return(ret);
2234    }
2235
2236    if (xmlDefaultCatalog != NULL) {
2237	sgml = xmlCatalogGetSGMLSystem(xmlDefaultCatalog, sysID);
2238	if (sgml != NULL)
2239	    return(xmlStrdup(sgml));
2240    }
2241    return(NULL);
2242}
2243
2244/**
2245 * xmlCatalogGetPublic:
2246 * @pubId:  the public ID string
2247 *
2248 * Try to lookup the system ID associated to a public ID
2249 * DEPRECATED, use xmlCatalogResolvePublic()
2250 *
2251 * Returns the system ID if found or NULL otherwise.
2252 */
2253const xmlChar *
2254xmlCatalogGetPublic(const xmlChar *pubID) {
2255    xmlChar *ret;
2256    static xmlChar result[1000];
2257
2258    if (pubID == NULL)
2259	return(NULL);
2260
2261    if (!xmlCatalogInitialized)
2262	xmlInitializeCatalog();
2263
2264    /*
2265     * Check first the XML catalogs
2266     */
2267    if (xmlDefaultXMLCatalogList != NULL) {
2268	ret = xmlCatalogListXMLResolve(xmlDefaultXMLCatalogList, pubID, NULL);
2269	if ((ret != NULL) && (ret != XML_CATAL_BREAK)) {
2270	    snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret);
2271	    result[sizeof(result) - 1] = 0;
2272	    return(result);
2273	}
2274    }
2275
2276    if (xmlDefaultCatalog != NULL)
2277	return(xmlCatalogGetSGMLPublic(xmlDefaultCatalog, pubID));
2278    return(NULL);
2279}
2280
2281/**
2282 * xmlCatalogResolvePublic:
2283 * @pubId:  the public ID string
2284 *
2285 * Try to lookup the system ID associated to a public ID
2286 *
2287 * Returns the system ID if found or NULL otherwise, the value returned
2288 *      must be freed by the caller.
2289 */
2290xmlChar *
2291xmlCatalogResolvePublic(const xmlChar *pubID) {
2292    xmlCatalogEntryPtr catal;
2293    xmlChar *ret;
2294    const xmlChar *sgml;
2295
2296    if (pubID == NULL)
2297	return(NULL);
2298
2299    if (!xmlCatalogInitialized)
2300	xmlInitializeCatalog();
2301
2302    /*
2303     * Check first the XML catalogs
2304     */
2305    catal = xmlDefaultXMLCatalogList;
2306    if (catal != NULL) {
2307	ret = xmlCatalogListXMLResolve(xmlDefaultXMLCatalogList, pubID, NULL);
2308	if ((ret != NULL) && (ret != XML_CATAL_BREAK))
2309	    return(ret);
2310    }
2311
2312    if (xmlDefaultCatalog != NULL) {
2313	sgml = xmlCatalogGetSGMLPublic(xmlDefaultCatalog, pubID);
2314	if (sgml != NULL)
2315	    return(xmlStrdup(sgml));
2316    }
2317    return(NULL);
2318}
2319
2320/**
2321 * xmlCatalogResolve:
2322 * @pubId:  the public ID string
2323 * @sysId:  the system ID string
2324 *
2325 * Do a complete resolution lookup of an External Identifier
2326 *
2327 * Returns the URI of the resource or NULL if not found, it must be freed
2328 *      by the caller.
2329 */
2330xmlChar *
2331xmlCatalogResolve(const xmlChar *pubID, const xmlChar *sysID) {
2332    if ((pubID == NULL) && (sysID == NULL))
2333	return(NULL);
2334
2335    if (!xmlCatalogInitialized)
2336	xmlInitializeCatalog();
2337
2338    if (xmlDebugCatalogs) {
2339	if (pubID != NULL) {
2340	    xmlGenericError(xmlGenericErrorContext,
2341		    "Resolve: pubID %s\n", pubID);
2342	} else {
2343	    xmlGenericError(xmlGenericErrorContext,
2344		    "Resolve: sysID %s\n", sysID);
2345	}
2346    }
2347
2348    if (xmlDefaultXMLCatalogList != NULL) {
2349	xmlChar *ret;
2350	ret = xmlCatalogListXMLResolve(xmlDefaultXMLCatalogList, pubID, sysID);
2351	if ((ret != NULL) && (ret != XML_CATAL_BREAK))
2352	    return(ret);
2353    } else {
2354	const xmlChar *ret;
2355
2356	ret = xmlCatalogSGMLResolve(pubID, sysID);
2357	if (ret != NULL)
2358	    return(xmlStrdup(ret));
2359    }
2360    return(NULL);
2361}
2362
2363/**
2364 * xmlCatalogResolveURI:
2365 * @pubId:  the URI
2366 *
2367 * Do a complete resolution lookup of an URI
2368 *
2369 * Returns the URI of the resource or NULL if not found, it must be freed
2370 *      by the caller.
2371 */
2372xmlChar *
2373xmlCatalogResolveURI(const xmlChar *URI) {
2374    if (!xmlCatalogInitialized)
2375	xmlInitializeCatalog();
2376
2377    if (URI == NULL)
2378	return(NULL);
2379
2380    if (xmlDebugCatalogs)
2381	xmlGenericError(xmlGenericErrorContext,
2382		"Resolve URI %s\n", URI);
2383
2384    if (xmlDefaultXMLCatalogList != NULL) {
2385	xmlChar *ret;
2386
2387	ret = xmlCatalogListXMLResolveURI(xmlDefaultXMLCatalogList, URI);
2388	if ((ret != NULL) && (ret != XML_CATAL_BREAK))
2389	    return(ret);
2390    } else {
2391	const xmlChar *ret;
2392
2393	ret = xmlCatalogSGMLResolve(NULL, URI);
2394	if (ret != NULL)
2395	    return(xmlStrdup(ret));
2396    }
2397    return(NULL);
2398}
2399
2400/**
2401 * xmlCatalogDump:
2402 * @out:  the file.
2403 *
2404 * Free up all the memory associated with catalogs
2405 */
2406void
2407xmlCatalogDump(FILE *out) {
2408    if (out == NULL)
2409	return;
2410
2411    if (xmlDefaultXMLCatalogList != NULL) {
2412	xmlDumpXMLCatalog(out, xmlDefaultXMLCatalogList);
2413    } else if (xmlDefaultCatalog != NULL) {
2414	xmlHashScan(xmlDefaultCatalog,
2415		    (xmlHashScanner) xmlCatalogDumpEntry, out);
2416    }
2417}
2418
2419/**
2420 * xmlCatalogAdd:
2421 * @type:  the type of record to add to the catalog
2422 * @orig:  the system, public or prefix to match
2423 * @replace:  the replacement value for the match
2424 *
2425 * Add an entry in the catalog, it may overwrite existing but
2426 * different entries.
2427 *
2428 * Returns 0 if successful, -1 otherwise
2429 */
2430int
2431xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace) {
2432    int res = -1;
2433
2434    if ((xmlDefaultXMLCatalogList == NULL) &&
2435	(xmlStrEqual(type, BAD_CAST "catalog"))) {
2436	xmlDefaultXMLCatalogList = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
2437		orig, xmlCatalogDefaultPrefer);
2438	return(0);
2439    }
2440
2441    if (!xmlCatalogInitialized)
2442	xmlInitializeCatalog();
2443
2444    if (xmlDefaultXMLCatalogList != NULL) {
2445	res = xmlAddXMLCatalog(xmlDefaultXMLCatalogList, type, orig, replace);
2446    } else if (xmlDefaultCatalog != NULL) {
2447	xmlCatalogEntryType typ;
2448
2449	typ = xmlGetSGMLCatalogEntryType(type);
2450	if (type != XML_CATA_NONE) {
2451	    xmlCatalogEntryPtr entry;
2452	    entry = xmlNewCatalogEntry(typ, orig, replace,
2453		                       XML_CATA_PREFER_NONE);
2454	    res = xmlHashAddEntry(xmlDefaultCatalog, orig, entry);
2455	}
2456    }
2457    return(res);
2458}
2459
2460/**
2461 * xmlCatalogRemove:
2462 * @value:  the value to remove
2463 *
2464 * Remove an entry from the catalog
2465 *
2466 * Returns 0 if successful, -1 otherwise
2467 */
2468int
2469xmlCatalogRemove(const xmlChar *value) {
2470    int res = -1;
2471
2472    if (!xmlCatalogInitialized)
2473	xmlInitializeCatalog();
2474
2475    if (xmlDefaultXMLCatalogList != NULL) {
2476	res = xmlDelXMLCatalog(xmlDefaultXMLCatalogList, value);
2477    } else if (xmlDefaultCatalog != NULL) {
2478	TODO
2479    }
2480    return(res);
2481}
2482
2483/**
2484 * xmlCatalogConvert:
2485 *
2486 * Convert all the SGML catalog entries as XML ones
2487 *
2488 * Returns the number of entries converted if successful, -1 otherwise
2489 */
2490int
2491xmlCatalogConvert(void) {
2492    int res = -1;
2493
2494    if (!xmlCatalogInitialized)
2495	xmlInitializeCatalog();
2496
2497    if (xmlDebugCatalogs) {
2498	xmlGenericError(xmlGenericErrorContext,
2499		"Converting SGML catalog to XML\n");
2500    }
2501
2502    if (xmlDefaultXMLCatalogList == NULL) {
2503	xmlDefaultXMLCatalogList = xmlNewCatalogEntry(XML_CATA_CATALOG,
2504			   NULL, BAD_CAST "NewCatalog.xml",
2505			   xmlCatalogDefaultPrefer);
2506    }
2507    if (xmlDefaultCatalog != NULL) {
2508	res = 0;
2509
2510	xmlHashScan(xmlDefaultCatalog,
2511		    (xmlHashScanner) xmlCatalogConvertEntry, &res);
2512    }
2513    return(res);
2514}
2515
2516/**
2517 * xmlCatalogGetDefaults:
2518 *
2519 * Used to get the user preference w.r.t. to what catalogs should
2520 * be accepted
2521 *
2522 * Returns the current xmlCatalogAllow value
2523 */
2524xmlCatalogAllow
2525xmlCatalogGetDefaults(void) {
2526    return(xmlCatalogDefaultAllow);
2527}
2528
2529/**
2530 * xmlCatalogSetDefaults:
2531 *
2532 * Used to set the user preference w.r.t. to what catalogs should
2533 * be accepted
2534 */
2535void
2536xmlCatalogSetDefaults(xmlCatalogAllow allow) {
2537    if (!xmlCatalogInitialized)
2538	xmlInitializeCatalog();
2539    if (xmlDebugCatalogs) {
2540	switch (allow) {
2541	    case XML_CATA_ALLOW_NONE:
2542		xmlGenericError(xmlGenericErrorContext,
2543			"Disabling catalog usage\n");
2544		break;
2545	    case XML_CATA_ALLOW_GLOBAL:
2546		xmlGenericError(xmlGenericErrorContext,
2547			"Allowing only global catalogs\n");
2548		break;
2549	    case XML_CATA_ALLOW_DOCUMENT:
2550		xmlGenericError(xmlGenericErrorContext,
2551			"Allowing only catalogs from the document\n");
2552		break;
2553	    case XML_CATA_ALLOW_ALL:
2554		xmlGenericError(xmlGenericErrorContext,
2555			"Allowing all catalogs\n");
2556		break;
2557	}
2558    }
2559    xmlCatalogDefaultAllow = allow;
2560}
2561
2562/**
2563 * xmlCatalogSetDefaultPrefer:
2564 * @prefer:  the default preference for delegation
2565 *
2566 * Allows to set the preference between public and system for deletion
2567 * in XML Catalog resolution. C.f. section 4.1.1 of the spec
2568 * Values accepted are XML_CATA_PREFER_PUBLIC or XML_CATA_PREFER_SYSTEM
2569 *
2570 * Returns the previous value of the default preference for delegation
2571 */
2572xmlCatalogPrefer
2573xmlCatalogSetDefaultPrefer(xmlCatalogPrefer prefer) {
2574    xmlCatalogPrefer ret = xmlCatalogDefaultPrefer;
2575
2576    if (!xmlCatalogInitialized)
2577	xmlInitializeCatalog();
2578    if (prefer == XML_CATA_PREFER_NONE)
2579	return(ret);
2580
2581    if (xmlDebugCatalogs) {
2582	switch (prefer) {
2583	    case XML_CATA_PREFER_PUBLIC:
2584		xmlGenericError(xmlGenericErrorContext,
2585			"Setting catalog preference to PUBLIC\n");
2586		break;
2587	    case XML_CATA_PREFER_SYSTEM:
2588		xmlGenericError(xmlGenericErrorContext,
2589			"Setting catalog preference to SYSTEM\n");
2590		break;
2591	    case XML_CATA_PREFER_NONE:
2592		break;
2593	}
2594    }
2595    xmlCatalogDefaultPrefer = prefer;
2596    return(ret);
2597}
2598
2599/**
2600 * xmlCatalogSetDebug:
2601 * @level:  the debug level of catalogs required
2602 *
2603 * Used to set the debug level for catalog operation, 0 disable
2604 * debugging, 1 enable it
2605 *
2606 * Returns the previous value of the catalog debugging level
2607 */
2608int
2609xmlCatalogSetDebug(int level) {
2610    int ret = xmlDebugCatalogs;
2611
2612    if (level <= 0)
2613        xmlDebugCatalogs = 0;
2614    else
2615	xmlDebugCatalogs = level;
2616    return(ret);
2617}
2618
2619/**
2620 * xmlCatalogFreeLocal:
2621 * @catalogs:  a document's list of catalogs
2622 *
2623 * Free up the memory associated to the catalog list
2624 */
2625void
2626xmlCatalogFreeLocal(void *catalogs) {
2627    xmlCatalogEntryPtr catal;
2628
2629    catal = (xmlCatalogEntryPtr) catalogs;
2630    if (catal != NULL)
2631	xmlFreeCatalogEntryList(catal);
2632}
2633
2634
2635/**
2636 * xmlCatalogAddLocal:
2637 * @catalogs:  a document's list of catalogs
2638 * @URL:  the URL to a new local catalog
2639 *
2640 * Add the new entry to the catalog list
2641 *
2642 * Returns the updated list
2643 */
2644void *
2645xmlCatalogAddLocal(void *catalogs, const xmlChar *URL) {
2646    xmlCatalogEntryPtr catal, add;
2647
2648    if (!xmlCatalogInitialized)
2649	xmlInitializeCatalog();
2650    if (URL == NULL)
2651	return(catalogs);
2652
2653    if (xmlDebugCatalogs)
2654	xmlGenericError(xmlGenericErrorContext,
2655		"Adding document catalog %s\n", URL);
2656
2657    add = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, URL,
2658	                     xmlCatalogDefaultPrefer);
2659    if (add == NULL)
2660	return(catalogs);
2661
2662    catal = (xmlCatalogEntryPtr) catalogs;
2663    if (catal == NULL)
2664	return((void *) add);
2665
2666    while (catal->next != NULL)
2667	catal = catal->next;
2668    catal->next = add;
2669    return(catalogs);
2670}
2671
2672/**
2673 * xmlCatalogLocalResolve:
2674 * @catalogs:  a document's list of catalogs
2675 * @pubId:  the public ID string
2676 * @sysId:  the system ID string
2677 *
2678 * Do a complete resolution lookup of an External Identifier using a
2679 * document's private catalog list
2680 *
2681 * Returns the URI of the resource or NULL if not found, it must be freed
2682 *      by the caller.
2683 */
2684xmlChar *
2685xmlCatalogLocalResolve(void *catalogs, const xmlChar *pubID,
2686	               const xmlChar *sysID) {
2687    xmlCatalogEntryPtr catal;
2688    xmlChar *ret;
2689
2690    if ((pubID == NULL) && (sysID == NULL))
2691	return(NULL);
2692
2693    if (!xmlCatalogInitialized)
2694	xmlInitializeCatalog();
2695
2696    if (xmlDebugCatalogs) {
2697	if (pubID != NULL) {
2698	    xmlGenericError(xmlGenericErrorContext,
2699		    "Local resolve: pubID %s\n", pubID);
2700	} else {
2701	    xmlGenericError(xmlGenericErrorContext,
2702		    "Local resolve: sysID %s\n", sysID);
2703	}
2704    }
2705
2706    catal = (xmlCatalogEntryPtr) catalogs;
2707    if (catal == NULL)
2708	return(NULL);
2709    ret = xmlCatalogListXMLResolve(catal, pubID, sysID);
2710    if ((ret != NULL) && (ret != XML_CATAL_BREAK))
2711	return(ret);
2712    return(NULL);
2713}
2714
2715/**
2716 * xmlCatalogLocalResolveURI:
2717 * @catalogs:  a document's list of catalogs
2718 * @pubId:  the URI
2719 *
2720 * Do a complete resolution lookup of an URI using a
2721 * document's private catalog list
2722 *
2723 * Returns the URI of the resource or NULL if not found, it must be freed
2724 *      by the caller.
2725 */
2726xmlChar *
2727xmlCatalogLocalResolveURI(void *catalogs, const xmlChar *URI) {
2728    xmlCatalogEntryPtr catal;
2729    xmlChar *ret;
2730
2731    if (URI == NULL)
2732	return(NULL);
2733
2734    if (!xmlCatalogInitialized)
2735	xmlInitializeCatalog();
2736
2737    if (xmlDebugCatalogs)
2738	xmlGenericError(xmlGenericErrorContext,
2739		"Resolve URI %s\n", URI);
2740
2741    catal = (xmlCatalogEntryPtr) catalogs;
2742    if (catal == NULL)
2743	return(NULL);
2744    ret = xmlCatalogListXMLResolveURI(catal, URI);
2745    if ((ret != NULL) && (ret != XML_CATAL_BREAK))
2746	return(ret);
2747    return(NULL);
2748}
2749
2750#endif /* LIBXML_CATALOG_ENABLED */
2751