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#define IN_LIBXML
16#include "libxml.h"
17
18#ifdef LIBXML_CATALOG_ENABLED
19#ifdef HAVE_SYS_TYPES_H
20#include <sys/types.h>
21#endif
22#ifdef HAVE_SYS_STAT_H
23#include <sys/stat.h>
24#endif
25#ifdef HAVE_UNISTD_H
26#include <unistd.h>
27#endif
28#ifdef HAVE_FCNTL_H
29#include <fcntl.h>
30#endif
31#ifdef HAVE_STDLIB_H
32#include <stdlib.h>
33#endif
34#include <string.h>
35#include <libxml/xmlmemory.h>
36#include <libxml/hash.h>
37#include <libxml/uri.h>
38#include <libxml/parserInternals.h>
39#include <libxml/catalog.h>
40#include <libxml/xmlerror.h>
41#include <libxml/threads.h>
42#include <libxml/globals.h>
43
44#include "buf.h"
45
46#define MAX_DELEGATE	50
47#define MAX_CATAL_DEPTH	50
48
49#ifdef _WIN32
50# define PATH_SEAPARATOR ';'
51#else
52# define PATH_SEAPARATOR ':'
53#endif
54
55/**
56 * TODO:
57 *
58 * macro to flag unimplemented blocks
59 * XML_CATALOG_PREFER user env to select between system/public prefered
60 * option. C.f. Richard Tobin <richard@cogsci.ed.ac.uk>
61 *> Just FYI, I am using an environment variable XML_CATALOG_PREFER with
62 *> values "system" and "public".  I have made the default be "system" to
63 *> match yours.
64 */
65#define TODO								\
66    xmlGenericError(xmlGenericErrorContext,				\
67	    "Unimplemented block at %s:%d\n",				\
68            __FILE__, __LINE__);
69
70#define XML_URN_PUBID "urn:publicid:"
71#define XML_CATAL_BREAK ((xmlChar *) -1)
72#ifndef XML_XML_DEFAULT_CATALOG
73#define XML_XML_DEFAULT_CATALOG "file:///etc/xml/catalog"
74#endif
75#ifndef XML_SGML_DEFAULT_CATALOG
76#define XML_SGML_DEFAULT_CATALOG "file:///etc/sgml/catalog"
77#endif
78
79#if defined(_WIN32) && defined(_MSC_VER)
80#undef XML_XML_DEFAULT_CATALOG
81static char XML_XML_DEFAULT_CATALOG[256] = "file:///etc/xml/catalog";
82#if defined(_WIN32_WCE)
83/* Windows CE don't have a A variant */
84#define GetModuleHandleA GetModuleHandle
85#define GetModuleFileNameA GetModuleFileName
86#else
87#if !defined(_WINDOWS_)
88void* __stdcall GetModuleHandleA(const char*);
89unsigned long __stdcall GetModuleFileNameA(void*, char*, unsigned long);
90#endif
91#endif
92#endif
93
94static xmlChar *xmlCatalogNormalizePublic(const xmlChar *pubID);
95static int xmlExpandCatalog(xmlCatalogPtr catal, const char *filename);
96
97/************************************************************************
98 *									*
99 *			Types, all private				*
100 *									*
101 ************************************************************************/
102
103typedef enum {
104    XML_CATA_REMOVED = -1,
105    XML_CATA_NONE = 0,
106    XML_CATA_CATALOG,
107    XML_CATA_BROKEN_CATALOG,
108    XML_CATA_NEXT_CATALOG,
109    XML_CATA_GROUP,
110    XML_CATA_PUBLIC,
111    XML_CATA_SYSTEM,
112    XML_CATA_REWRITE_SYSTEM,
113    XML_CATA_DELEGATE_PUBLIC,
114    XML_CATA_DELEGATE_SYSTEM,
115    XML_CATA_URI,
116    XML_CATA_REWRITE_URI,
117    XML_CATA_DELEGATE_URI,
118    SGML_CATA_SYSTEM,
119    SGML_CATA_PUBLIC,
120    SGML_CATA_ENTITY,
121    SGML_CATA_PENTITY,
122    SGML_CATA_DOCTYPE,
123    SGML_CATA_LINKTYPE,
124    SGML_CATA_NOTATION,
125    SGML_CATA_DELEGATE,
126    SGML_CATA_BASE,
127    SGML_CATA_CATALOG,
128    SGML_CATA_DOCUMENT,
129    SGML_CATA_SGMLDECL
130} xmlCatalogEntryType;
131
132typedef struct _xmlCatalogEntry xmlCatalogEntry;
133typedef xmlCatalogEntry *xmlCatalogEntryPtr;
134struct _xmlCatalogEntry {
135    struct _xmlCatalogEntry *next;
136    struct _xmlCatalogEntry *parent;
137    struct _xmlCatalogEntry *children;
138    xmlCatalogEntryType type;
139    xmlChar *name;
140    xmlChar *value;
141    xmlChar *URL;  /* The expanded URL using the base */
142    xmlCatalogPrefer prefer;
143    int dealloc;
144    int depth;
145    struct _xmlCatalogEntry *group;
146};
147
148typedef enum {
149    XML_XML_CATALOG_TYPE = 1,
150    XML_SGML_CATALOG_TYPE
151} xmlCatalogType;
152
153#define XML_MAX_SGML_CATA_DEPTH 10
154struct _xmlCatalog {
155    xmlCatalogType type;	/* either XML or SGML */
156
157    /*
158     * SGML Catalogs are stored as a simple hash table of catalog entries
159     * Catalog stack to check against overflows when building the
160     * SGML catalog
161     */
162    char *catalTab[XML_MAX_SGML_CATA_DEPTH];	/* stack of catals */
163    int          catalNr;	/* Number of current catal streams */
164    int          catalMax;	/* Max number of catal streams */
165    xmlHashTablePtr sgml;
166
167    /*
168     * XML Catalogs are stored as a tree of Catalog entries
169     */
170    xmlCatalogPrefer prefer;
171    xmlCatalogEntryPtr xml;
172};
173
174/************************************************************************
175 *									*
176 *			Global variables				*
177 *									*
178 ************************************************************************/
179
180/*
181 * Those are preferences
182 */
183static int xmlDebugCatalogs = 0;   /* used for debugging */
184static xmlCatalogAllow xmlCatalogDefaultAllow = XML_CATA_ALLOW_ALL;
185static xmlCatalogPrefer xmlCatalogDefaultPrefer = XML_CATA_PREFER_PUBLIC;
186
187/*
188 * Hash table containing all the trees of XML catalogs parsed by
189 * the application.
190 */
191static xmlHashTablePtr xmlCatalogXMLFiles = NULL;
192
193/*
194 * The default catalog in use by the application
195 */
196static xmlCatalogPtr xmlDefaultCatalog = NULL;
197
198/*
199 * A mutex for modifying the shared global catalog(s)
200 * xmlDefaultCatalog tree.
201 * It also protects xmlCatalogXMLFiles
202 * The core of this readers/writer scheme is in xmlFetchXMLCatalogFile()
203 */
204static xmlRMutexPtr xmlCatalogMutex = NULL;
205
206/*
207 * Whether the catalog support was initialized.
208 */
209static int xmlCatalogInitialized = 0;
210
211/************************************************************************
212 *									*
213 *			Catalog error handlers				*
214 *									*
215 ************************************************************************/
216
217/**
218 * xmlCatalogErrMemory:
219 * @extra:  extra informations
220 *
221 * Handle an out of memory condition
222 */
223static void
224xmlCatalogErrMemory(const char *extra)
225{
226    __xmlRaiseError(NULL, NULL, NULL, NULL, NULL, XML_FROM_CATALOG,
227                    XML_ERR_NO_MEMORY, XML_ERR_ERROR, NULL, 0,
228		    extra, NULL, NULL, 0, 0,
229		    "Memory allocation failed : %s\n", extra);
230}
231
232/**
233 * xmlCatalogErr:
234 * @catal: the Catalog entry
235 * @node: the context node
236 * @msg:  the error message
237 * @extra:  extra informations
238 *
239 * Handle a catalog error
240 */
241static void
242xmlCatalogErr(xmlCatalogEntryPtr catal, xmlNodePtr node, int error,
243               const char *msg, const xmlChar *str1, const xmlChar *str2,
244	       const xmlChar *str3)
245{
246    __xmlRaiseError(NULL, NULL, NULL, catal, node, XML_FROM_CATALOG,
247                    error, XML_ERR_ERROR, NULL, 0,
248		    (const char *) str1, (const char *) str2,
249		    (const char *) str3, 0, 0,
250		    msg, str1, str2, str3);
251}
252
253
254/************************************************************************
255 *									*
256 *			Allocation and Freeing				*
257 *									*
258 ************************************************************************/
259
260/**
261 * xmlNewCatalogEntry:
262 * @type:  type of entry
263 * @name:  name of the entry
264 * @value:  value of the entry
265 * @prefer:  the PUBLIC vs. SYSTEM current preference value
266 * @group:  for members of a group, the group entry
267 *
268 * create a new Catalog entry, this type is shared both by XML and
269 * SGML catalogs, but the acceptable types values differs.
270 *
271 * Returns the xmlCatalogEntryPtr or NULL in case of error
272 */
273static xmlCatalogEntryPtr
274xmlNewCatalogEntry(xmlCatalogEntryType type, const xmlChar *name,
275	   const xmlChar *value, const xmlChar *URL, xmlCatalogPrefer prefer,
276	   xmlCatalogEntryPtr group) {
277    xmlCatalogEntryPtr ret;
278    xmlChar *normid = NULL;
279
280    ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry));
281    if (ret == NULL) {
282        xmlCatalogErrMemory("allocating catalog entry");
283	return(NULL);
284    }
285    ret->next = NULL;
286    ret->parent = NULL;
287    ret->children = NULL;
288    ret->type = type;
289    if (type == XML_CATA_PUBLIC || type == XML_CATA_DELEGATE_PUBLIC) {
290        normid = xmlCatalogNormalizePublic(name);
291        if (normid != NULL)
292            name = (*normid != 0 ? normid : NULL);
293    }
294    if (name != NULL)
295	ret->name = xmlStrdup(name);
296    else
297	ret->name = NULL;
298    if (normid != NULL)
299        xmlFree(normid);
300    if (value != NULL)
301	ret->value = xmlStrdup(value);
302    else
303	ret->value = NULL;
304    if (URL == NULL)
305	URL = value;
306    if (URL != NULL)
307	ret->URL = xmlStrdup(URL);
308    else
309	ret->URL = NULL;
310    ret->prefer = prefer;
311    ret->dealloc = 0;
312    ret->depth = 0;
313    ret->group = group;
314    return(ret);
315}
316
317static void
318xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret);
319
320/**
321 * xmlFreeCatalogEntry:
322 * @ret:  a Catalog entry
323 *
324 * Free the memory allocated to a Catalog entry
325 */
326static void
327xmlFreeCatalogEntry(xmlCatalogEntryPtr ret) {
328    if (ret == NULL)
329	return;
330    /*
331     * Entries stored in the file hash must be deallocated
332     * only by the file hash cleaner !
333     */
334    if (ret->dealloc == 1)
335	return;
336
337    if (xmlDebugCatalogs) {
338	if (ret->name != NULL)
339	    xmlGenericError(xmlGenericErrorContext,
340		    "Free catalog entry %s\n", ret->name);
341	else if (ret->value != NULL)
342	    xmlGenericError(xmlGenericErrorContext,
343		    "Free catalog entry %s\n", ret->value);
344	else
345	    xmlGenericError(xmlGenericErrorContext,
346		    "Free catalog entry\n");
347    }
348
349    if (ret->name != NULL)
350	xmlFree(ret->name);
351    if (ret->value != NULL)
352	xmlFree(ret->value);
353    if (ret->URL != NULL)
354	xmlFree(ret->URL);
355    xmlFree(ret);
356}
357
358/**
359 * xmlFreeCatalogEntryList:
360 * @ret:  a Catalog entry list
361 *
362 * Free the memory allocated to a full chained list of Catalog entries
363 */
364static void
365xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret) {
366    xmlCatalogEntryPtr next;
367
368    while (ret != NULL) {
369	next = ret->next;
370	xmlFreeCatalogEntry(ret);
371	ret = next;
372    }
373}
374
375/**
376 * xmlFreeCatalogHashEntryList:
377 * @ret:  a Catalog entry list
378 *
379 * Free the memory allocated to list of Catalog entries from the
380 * catalog file hash.
381 */
382static void
383xmlFreeCatalogHashEntryList(xmlCatalogEntryPtr catal) {
384    xmlCatalogEntryPtr children, next;
385
386    if (catal == NULL)
387	return;
388
389    children = catal->children;
390    while (children != NULL) {
391	next = children->next;
392	children->dealloc = 0;
393	children->children = NULL;
394	xmlFreeCatalogEntry(children);
395	children = next;
396    }
397    catal->dealloc = 0;
398    xmlFreeCatalogEntry(catal);
399}
400
401/**
402 * xmlCreateNewCatalog:
403 * @type:  type of catalog
404 * @prefer:  the PUBLIC vs. SYSTEM current preference value
405 *
406 * create a new Catalog, this type is shared both by XML and
407 * SGML catalogs, but the acceptable types values differs.
408 *
409 * Returns the xmlCatalogPtr or NULL in case of error
410 */
411static xmlCatalogPtr
412xmlCreateNewCatalog(xmlCatalogType type, xmlCatalogPrefer prefer) {
413    xmlCatalogPtr ret;
414
415    ret = (xmlCatalogPtr) xmlMalloc(sizeof(xmlCatalog));
416    if (ret == NULL) {
417        xmlCatalogErrMemory("allocating catalog");
418	return(NULL);
419    }
420    memset(ret, 0, sizeof(xmlCatalog));
421    ret->type = type;
422    ret->catalNr = 0;
423    ret->catalMax = XML_MAX_SGML_CATA_DEPTH;
424    ret->prefer = prefer;
425    if (ret->type == XML_SGML_CATALOG_TYPE)
426	ret->sgml = xmlHashCreate(10);
427    return(ret);
428}
429
430/**
431 * xmlFreeCatalog:
432 * @catal:  a Catalog
433 *
434 * Free the memory allocated to a Catalog
435 */
436void
437xmlFreeCatalog(xmlCatalogPtr catal) {
438    if (catal == NULL)
439	return;
440    if (catal->xml != NULL)
441	xmlFreeCatalogEntryList(catal->xml);
442    if (catal->sgml != NULL)
443	xmlHashFree(catal->sgml,
444		(xmlHashDeallocator) xmlFreeCatalogEntry);
445    xmlFree(catal);
446}
447
448/************************************************************************
449 *									*
450 *			Serializing Catalogs				*
451 *									*
452 ************************************************************************/
453
454#ifdef LIBXML_OUTPUT_ENABLED
455/**
456 * xmlCatalogDumpEntry:
457 * @entry:  the catalog entry
458 * @out:  the file.
459 *
460 * Serialize an SGML Catalog entry
461 */
462static void
463xmlCatalogDumpEntry(xmlCatalogEntryPtr entry, FILE *out) {
464    if ((entry == NULL) || (out == NULL))
465	return;
466    switch (entry->type) {
467	case SGML_CATA_ENTITY:
468	    fprintf(out, "ENTITY "); break;
469	case SGML_CATA_PENTITY:
470	    fprintf(out, "ENTITY %%"); break;
471	case SGML_CATA_DOCTYPE:
472	    fprintf(out, "DOCTYPE "); break;
473	case SGML_CATA_LINKTYPE:
474	    fprintf(out, "LINKTYPE "); break;
475	case SGML_CATA_NOTATION:
476	    fprintf(out, "NOTATION "); break;
477	case SGML_CATA_PUBLIC:
478	    fprintf(out, "PUBLIC "); break;
479	case SGML_CATA_SYSTEM:
480	    fprintf(out, "SYSTEM "); break;
481	case SGML_CATA_DELEGATE:
482	    fprintf(out, "DELEGATE "); break;
483	case SGML_CATA_BASE:
484	    fprintf(out, "BASE "); break;
485	case SGML_CATA_CATALOG:
486	    fprintf(out, "CATALOG "); break;
487	case SGML_CATA_DOCUMENT:
488	    fprintf(out, "DOCUMENT "); break;
489	case SGML_CATA_SGMLDECL:
490	    fprintf(out, "SGMLDECL "); break;
491	default:
492	    return;
493    }
494    switch (entry->type) {
495	case SGML_CATA_ENTITY:
496	case SGML_CATA_PENTITY:
497	case SGML_CATA_DOCTYPE:
498	case SGML_CATA_LINKTYPE:
499	case SGML_CATA_NOTATION:
500	    fprintf(out, "%s", (const char *) entry->name); break;
501	case SGML_CATA_PUBLIC:
502	case SGML_CATA_SYSTEM:
503	case SGML_CATA_SGMLDECL:
504	case SGML_CATA_DOCUMENT:
505	case SGML_CATA_CATALOG:
506	case SGML_CATA_BASE:
507	case SGML_CATA_DELEGATE:
508	    fprintf(out, "\"%s\"", entry->name); break;
509	default:
510	    break;
511    }
512    switch (entry->type) {
513	case SGML_CATA_ENTITY:
514	case SGML_CATA_PENTITY:
515	case SGML_CATA_DOCTYPE:
516	case SGML_CATA_LINKTYPE:
517	case SGML_CATA_NOTATION:
518	case SGML_CATA_PUBLIC:
519	case SGML_CATA_SYSTEM:
520	case SGML_CATA_DELEGATE:
521	    fprintf(out, " \"%s\"", entry->value); break;
522	default:
523	    break;
524    }
525    fprintf(out, "\n");
526}
527
528/**
529 * xmlDumpXMLCatalogNode:
530 * @catal:  top catalog entry
531 * @catalog: pointer to the xml tree
532 * @doc: the containing document
533 * @ns: the current namespace
534 * @cgroup: group node for group members
535 *
536 * Serializes a Catalog entry, called by xmlDumpXMLCatalog and recursively
537 * for group entries
538 */
539static void xmlDumpXMLCatalogNode(xmlCatalogEntryPtr catal, xmlNodePtr catalog,
540		    xmlDocPtr doc, xmlNsPtr ns, xmlCatalogEntryPtr cgroup) {
541    xmlNodePtr node;
542    xmlCatalogEntryPtr cur;
543    /*
544     * add all the catalog entries
545     */
546    cur = catal;
547    while (cur != NULL) {
548        if (cur->group == cgroup) {
549	    switch (cur->type) {
550	        case XML_CATA_REMOVED:
551		    break;
552	        case XML_CATA_BROKEN_CATALOG:
553	        case XML_CATA_CATALOG:
554		    if (cur == catal) {
555			cur = cur->children;
556		        continue;
557		    }
558		    break;
559		case XML_CATA_NEXT_CATALOG:
560		    node = xmlNewDocNode(doc, ns, BAD_CAST "nextCatalog", NULL);
561		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
562		    xmlAddChild(catalog, node);
563                    break;
564		case XML_CATA_NONE:
565		    break;
566		case XML_CATA_GROUP:
567		    node = xmlNewDocNode(doc, ns, BAD_CAST "group", NULL);
568		    xmlSetProp(node, BAD_CAST "id", cur->name);
569		    if (cur->value != NULL) {
570		        xmlNsPtr xns;
571			xns = xmlSearchNsByHref(doc, node, XML_XML_NAMESPACE);
572			if (xns != NULL)
573			    xmlSetNsProp(node, xns, BAD_CAST "base",
574					 cur->value);
575		    }
576		    switch (cur->prefer) {
577			case XML_CATA_PREFER_NONE:
578		            break;
579			case XML_CATA_PREFER_PUBLIC:
580		            xmlSetProp(node, BAD_CAST "prefer", BAD_CAST "public");
581			    break;
582			case XML_CATA_PREFER_SYSTEM:
583		            xmlSetProp(node, BAD_CAST "prefer", BAD_CAST "system");
584			    break;
585		    }
586		    xmlDumpXMLCatalogNode(cur->next, node, doc, ns, cur);
587		    xmlAddChild(catalog, node);
588	            break;
589		case XML_CATA_PUBLIC:
590		    node = xmlNewDocNode(doc, ns, BAD_CAST "public", NULL);
591		    xmlSetProp(node, BAD_CAST "publicId", cur->name);
592		    xmlSetProp(node, BAD_CAST "uri", cur->value);
593		    xmlAddChild(catalog, node);
594		    break;
595		case XML_CATA_SYSTEM:
596		    node = xmlNewDocNode(doc, ns, BAD_CAST "system", NULL);
597		    xmlSetProp(node, BAD_CAST "systemId", cur->name);
598		    xmlSetProp(node, BAD_CAST "uri", cur->value);
599		    xmlAddChild(catalog, node);
600		    break;
601		case XML_CATA_REWRITE_SYSTEM:
602		    node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteSystem", NULL);
603		    xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
604		    xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
605		    xmlAddChild(catalog, node);
606		    break;
607		case XML_CATA_DELEGATE_PUBLIC:
608		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegatePublic", NULL);
609		    xmlSetProp(node, BAD_CAST "publicIdStartString", cur->name);
610		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
611		    xmlAddChild(catalog, node);
612		    break;
613		case XML_CATA_DELEGATE_SYSTEM:
614		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegateSystem", NULL);
615		    xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
616		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
617		    xmlAddChild(catalog, node);
618		    break;
619		case XML_CATA_URI:
620		    node = xmlNewDocNode(doc, ns, BAD_CAST "uri", NULL);
621		    xmlSetProp(node, BAD_CAST "name", cur->name);
622		    xmlSetProp(node, BAD_CAST "uri", cur->value);
623		    xmlAddChild(catalog, node);
624		    break;
625		case XML_CATA_REWRITE_URI:
626		    node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteURI", NULL);
627		    xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
628		    xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
629		    xmlAddChild(catalog, node);
630		    break;
631		case XML_CATA_DELEGATE_URI:
632		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegateURI", NULL);
633		    xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
634		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
635		    xmlAddChild(catalog, node);
636		    break;
637		case SGML_CATA_SYSTEM:
638		case SGML_CATA_PUBLIC:
639		case SGML_CATA_ENTITY:
640		case SGML_CATA_PENTITY:
641		case SGML_CATA_DOCTYPE:
642		case SGML_CATA_LINKTYPE:
643		case SGML_CATA_NOTATION:
644		case SGML_CATA_DELEGATE:
645		case SGML_CATA_BASE:
646		case SGML_CATA_CATALOG:
647		case SGML_CATA_DOCUMENT:
648		case SGML_CATA_SGMLDECL:
649		    break;
650	    }
651        }
652	cur = cur->next;
653    }
654}
655
656static int
657xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) {
658    int ret;
659    xmlDocPtr doc;
660    xmlNsPtr ns;
661    xmlDtdPtr dtd;
662    xmlNodePtr catalog;
663    xmlOutputBufferPtr buf;
664
665    /*
666     * Rebuild a catalog
667     */
668    doc = xmlNewDoc(NULL);
669    if (doc == NULL)
670	return(-1);
671    dtd = xmlNewDtd(doc, BAD_CAST "catalog",
672	       BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN",
673BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd");
674
675    xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
676
677    ns = xmlNewNs(NULL, XML_CATALOGS_NAMESPACE, NULL);
678    if (ns == NULL) {
679	xmlFreeDoc(doc);
680	return(-1);
681    }
682    catalog = xmlNewDocNode(doc, ns, BAD_CAST "catalog", NULL);
683    if (catalog == NULL) {
684	xmlFreeNs(ns);
685	xmlFreeDoc(doc);
686	return(-1);
687    }
688    catalog->nsDef = ns;
689    xmlAddChild((xmlNodePtr) doc, catalog);
690
691    xmlDumpXMLCatalogNode(catal, catalog, doc, ns, NULL);
692
693    /*
694     * reserialize it
695     */
696    buf = xmlOutputBufferCreateFile(out, NULL);
697    if (buf == NULL) {
698	xmlFreeDoc(doc);
699	return(-1);
700    }
701    ret = xmlSaveFormatFileTo(buf, doc, NULL, 1);
702
703    /*
704     * Free it
705     */
706    xmlFreeDoc(doc);
707
708    return(ret);
709}
710#endif /* LIBXML_OUTPUT_ENABLED */
711
712/************************************************************************
713 *									*
714 *			Converting SGML Catalogs to XML			*
715 *									*
716 ************************************************************************/
717
718/**
719 * xmlCatalogConvertEntry:
720 * @entry:  the entry
721 * @catal:  pointer to the catalog being converted
722 *
723 * Convert one entry from the catalog
724 */
725static void
726xmlCatalogConvertEntry(xmlCatalogEntryPtr entry, xmlCatalogPtr catal) {
727    if ((entry == NULL) || (catal == NULL) || (catal->sgml == NULL) ||
728	(catal->xml == NULL))
729	return;
730    switch (entry->type) {
731	case SGML_CATA_ENTITY:
732	    entry->type = XML_CATA_PUBLIC;
733	    break;
734	case SGML_CATA_PENTITY:
735	    entry->type = XML_CATA_PUBLIC;
736	    break;
737	case SGML_CATA_DOCTYPE:
738	    entry->type = XML_CATA_PUBLIC;
739	    break;
740	case SGML_CATA_LINKTYPE:
741	    entry->type = XML_CATA_PUBLIC;
742	    break;
743	case SGML_CATA_NOTATION:
744	    entry->type = XML_CATA_PUBLIC;
745	    break;
746	case SGML_CATA_PUBLIC:
747	    entry->type = XML_CATA_PUBLIC;
748	    break;
749	case SGML_CATA_SYSTEM:
750	    entry->type = XML_CATA_SYSTEM;
751	    break;
752	case SGML_CATA_DELEGATE:
753	    entry->type = XML_CATA_DELEGATE_PUBLIC;
754	    break;
755	case SGML_CATA_CATALOG:
756	    entry->type = XML_CATA_CATALOG;
757	    break;
758	default:
759	    xmlHashRemoveEntry(catal->sgml, entry->name,
760		               (xmlHashDeallocator) xmlFreeCatalogEntry);
761	    return;
762    }
763    /*
764     * Conversion successful, remove from the SGML catalog
765     * and add it to the default XML one
766     */
767    xmlHashRemoveEntry(catal->sgml, entry->name, NULL);
768    entry->parent = catal->xml;
769    entry->next = NULL;
770    if (catal->xml->children == NULL)
771	catal->xml->children = entry;
772    else {
773	xmlCatalogEntryPtr prev;
774
775	prev = catal->xml->children;
776	while (prev->next != NULL)
777	    prev = prev->next;
778	prev->next = entry;
779    }
780}
781
782/**
783 * xmlConvertSGMLCatalog:
784 * @catal: the catalog
785 *
786 * Convert all the SGML catalog entries as XML ones
787 *
788 * Returns the number of entries converted if successful, -1 otherwise
789 */
790int
791xmlConvertSGMLCatalog(xmlCatalogPtr catal) {
792
793    if ((catal == NULL) || (catal->type != XML_SGML_CATALOG_TYPE))
794	return(-1);
795
796    if (xmlDebugCatalogs) {
797	xmlGenericError(xmlGenericErrorContext,
798		"Converting SGML catalog to XML\n");
799    }
800    xmlHashScan(catal->sgml,
801		(xmlHashScanner) xmlCatalogConvertEntry,
802		&catal);
803    return(0);
804}
805
806/************************************************************************
807 *									*
808 *			Helper function					*
809 *									*
810 ************************************************************************/
811
812/**
813 * xmlCatalogUnWrapURN:
814 * @urn:  an "urn:publicid:" to unwrap
815 *
816 * Expand the URN into the equivalent Public Identifier
817 *
818 * Returns the new identifier or NULL, the string must be deallocated
819 *         by the caller.
820 */
821static xmlChar *
822xmlCatalogUnWrapURN(const xmlChar *urn) {
823    xmlChar result[2000];
824    unsigned int i = 0;
825
826    if (xmlStrncmp(urn, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1))
827	return(NULL);
828    urn += sizeof(XML_URN_PUBID) - 1;
829
830    while (*urn != 0) {
831	if (i > sizeof(result) - 4)
832	    break;
833	if (*urn == '+') {
834	    result[i++] = ' ';
835	    urn++;
836	} else if (*urn == ':') {
837	    result[i++] = '/';
838	    result[i++] = '/';
839	    urn++;
840	} else if (*urn == ';') {
841	    result[i++] = ':';
842	    result[i++] = ':';
843	    urn++;
844	} else if (*urn == '%') {
845	    if ((urn[1] == '2') && (urn[2] == 'B'))
846		result[i++] = '+';
847	    else if ((urn[1] == '3') && (urn[2] == 'A'))
848		result[i++] = ':';
849	    else if ((urn[1] == '2') && (urn[2] == 'F'))
850		result[i++] = '/';
851	    else if ((urn[1] == '3') && (urn[2] == 'B'))
852		result[i++] = ';';
853	    else if ((urn[1] == '2') && (urn[2] == '7'))
854		result[i++] = '\'';
855	    else if ((urn[1] == '3') && (urn[2] == 'F'))
856		result[i++] = '?';
857	    else if ((urn[1] == '2') && (urn[2] == '3'))
858		result[i++] = '#';
859	    else if ((urn[1] == '2') && (urn[2] == '5'))
860		result[i++] = '%';
861	    else {
862		result[i++] = *urn;
863		urn++;
864		continue;
865	    }
866	    urn += 3;
867	} else {
868	    result[i++] = *urn;
869	    urn++;
870	}
871    }
872    result[i] = 0;
873
874    return(xmlStrdup(result));
875}
876
877/**
878 * xmlParseCatalogFile:
879 * @filename:  the filename
880 *
881 * parse an XML file and build a tree. It's like xmlParseFile()
882 * except it bypass all catalog lookups.
883 *
884 * Returns the resulting document tree or NULL in case of error
885 */
886
887xmlDocPtr
888xmlParseCatalogFile(const char *filename) {
889    xmlDocPtr ret;
890    xmlParserCtxtPtr ctxt;
891    char *directory = NULL;
892    xmlParserInputPtr inputStream;
893    xmlParserInputBufferPtr buf;
894
895    ctxt = xmlNewParserCtxt();
896    if (ctxt == NULL) {
897#ifdef LIBXML_SAX1_ENABLED
898	if (xmlDefaultSAXHandler.error != NULL) {
899	    xmlDefaultSAXHandler.error(NULL, "out of memory\n");
900	}
901#endif
902	return(NULL);
903    }
904
905    buf = xmlParserInputBufferCreateFilename(filename, XML_CHAR_ENCODING_NONE);
906    if (buf == NULL) {
907	xmlFreeParserCtxt(ctxt);
908	return(NULL);
909    }
910
911    inputStream = xmlNewInputStream(ctxt);
912    if (inputStream == NULL) {
913	xmlFreeParserCtxt(ctxt);
914	return(NULL);
915    }
916
917    inputStream->filename = (char *) xmlCanonicPath((const xmlChar *)filename);
918    inputStream->buf = buf;
919    xmlBufResetInput(buf->buffer, inputStream);
920
921    inputPush(ctxt, inputStream);
922    if ((ctxt->directory == NULL) && (directory == NULL))
923        directory = xmlParserGetDirectory(filename);
924    if ((ctxt->directory == NULL) && (directory != NULL))
925        ctxt->directory = directory;
926    ctxt->valid = 0;
927    ctxt->validate = 0;
928    ctxt->loadsubset = 0;
929    ctxt->pedantic = 0;
930    ctxt->dictNames = 1;
931
932    xmlParseDocument(ctxt);
933
934    if (ctxt->wellFormed)
935	ret = ctxt->myDoc;
936    else {
937        ret = NULL;
938        xmlFreeDoc(ctxt->myDoc);
939        ctxt->myDoc = NULL;
940    }
941    xmlFreeParserCtxt(ctxt);
942
943    return(ret);
944}
945
946/**
947 * xmlLoadFileContent:
948 * @filename:  a file path
949 *
950 * Load a file content into memory.
951 *
952 * Returns a pointer to the 0 terminated string or NULL in case of error
953 */
954static xmlChar *
955xmlLoadFileContent(const char *filename)
956{
957#ifdef HAVE_STAT
958    int fd;
959#else
960    FILE *fd;
961#endif
962    int len;
963    long size;
964
965#ifdef HAVE_STAT
966    struct stat info;
967#endif
968    xmlChar *content;
969
970    if (filename == NULL)
971        return (NULL);
972
973#ifdef HAVE_STAT
974    if (stat(filename, &info) < 0)
975        return (NULL);
976#endif
977
978#ifdef HAVE_STAT
979    if ((fd = open(filename, O_RDONLY)) < 0)
980#else
981    if ((fd = fopen(filename, "rb")) == NULL)
982#endif
983    {
984        return (NULL);
985    }
986#ifdef HAVE_STAT
987    size = info.st_size;
988#else
989    if (fseek(fd, 0, SEEK_END) || (size = ftell(fd)) == EOF || fseek(fd, 0, SEEK_SET)) {        /* File operations denied? ok, just close and return failure */
990        fclose(fd);
991        return (NULL);
992    }
993#endif
994    content = (xmlChar*)xmlMallocAtomic(size + 10);
995    if (content == NULL) {
996        xmlCatalogErrMemory("allocating catalog data");
997        return (NULL);
998    }
999#ifdef HAVE_STAT
1000    len = read(fd, content, size);
1001    close(fd);
1002#else
1003    len = fread(content, 1, size, fd);
1004    fclose(fd);
1005#endif
1006    if (len < 0) {
1007        xmlFree(content);
1008        return (NULL);
1009    }
1010    content[len] = 0;
1011
1012    return(content);
1013}
1014
1015/**
1016 * xmlCatalogNormalizePublic:
1017 * @pubID:  the public ID string
1018 *
1019 *  Normalizes the Public Identifier
1020 *
1021 * Implements 6.2. Public Identifier Normalization
1022 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1023 *
1024 * Returns the new string or NULL, the string must be deallocated
1025 *         by the caller.
1026 */
1027static xmlChar *
1028xmlCatalogNormalizePublic(const xmlChar *pubID)
1029{
1030    int ok = 1;
1031    int white;
1032    const xmlChar *p;
1033    xmlChar *ret;
1034    xmlChar *q;
1035
1036    if (pubID == NULL)
1037        return(NULL);
1038
1039    white = 1;
1040    for (p = pubID;*p != 0 && ok;p++) {
1041        if (!xmlIsBlank_ch(*p))
1042            white = 0;
1043        else if (*p == 0x20 && !white)
1044            white = 1;
1045        else
1046            ok = 0;
1047    }
1048    if (ok && !white)	/* is normalized */
1049        return(NULL);
1050
1051    ret = xmlStrdup(pubID);
1052    q = ret;
1053    white = 0;
1054    for (p = pubID;*p != 0;p++) {
1055        if (xmlIsBlank_ch(*p)) {
1056            if (q != ret)
1057                white = 1;
1058        } else {
1059            if (white) {
1060                *(q++) = 0x20;
1061                white = 0;
1062            }
1063            *(q++) = *p;
1064        }
1065    }
1066    *q = 0;
1067    return(ret);
1068}
1069
1070/************************************************************************
1071 *									*
1072 *			The XML Catalog parser				*
1073 *									*
1074 ************************************************************************/
1075
1076static xmlCatalogEntryPtr
1077xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename);
1078static void
1079xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
1080	                   xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup);
1081static xmlChar *
1082xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1083	              const xmlChar *sysID);
1084static xmlChar *
1085xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI);
1086
1087
1088/**
1089 * xmlGetXMLCatalogEntryType:
1090 * @name:  the name
1091 *
1092 * lookup the internal type associated to an XML catalog entry name
1093 *
1094 * Returns the type associated with that name
1095 */
1096static xmlCatalogEntryType
1097xmlGetXMLCatalogEntryType(const xmlChar *name) {
1098    xmlCatalogEntryType type = XML_CATA_NONE;
1099    if (xmlStrEqual(name, (const xmlChar *) "system"))
1100	type = XML_CATA_SYSTEM;
1101    else if (xmlStrEqual(name, (const xmlChar *) "public"))
1102	type = XML_CATA_PUBLIC;
1103    else if (xmlStrEqual(name, (const xmlChar *) "rewriteSystem"))
1104	type = XML_CATA_REWRITE_SYSTEM;
1105    else if (xmlStrEqual(name, (const xmlChar *) "delegatePublic"))
1106	type = XML_CATA_DELEGATE_PUBLIC;
1107    else if (xmlStrEqual(name, (const xmlChar *) "delegateSystem"))
1108	type = XML_CATA_DELEGATE_SYSTEM;
1109    else if (xmlStrEqual(name, (const xmlChar *) "uri"))
1110	type = XML_CATA_URI;
1111    else if (xmlStrEqual(name, (const xmlChar *) "rewriteURI"))
1112	type = XML_CATA_REWRITE_URI;
1113    else if (xmlStrEqual(name, (const xmlChar *) "delegateURI"))
1114	type = XML_CATA_DELEGATE_URI;
1115    else if (xmlStrEqual(name, (const xmlChar *) "nextCatalog"))
1116	type = XML_CATA_NEXT_CATALOG;
1117    else if (xmlStrEqual(name, (const xmlChar *) "catalog"))
1118	type = XML_CATA_CATALOG;
1119    return(type);
1120}
1121
1122/**
1123 * xmlParseXMLCatalogOneNode:
1124 * @cur:  the XML node
1125 * @type:  the type of Catalog entry
1126 * @name:  the name of the node
1127 * @attrName:  the attribute holding the value
1128 * @uriAttrName:  the attribute holding the URI-Reference
1129 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1130 * @cgroup:  the group which includes this node
1131 *
1132 * Finishes the examination of an XML tree node of a catalog and build
1133 * a Catalog entry from it.
1134 *
1135 * Returns the new Catalog entry node or NULL in case of error.
1136 */
1137static xmlCatalogEntryPtr
1138xmlParseXMLCatalogOneNode(xmlNodePtr cur, xmlCatalogEntryType type,
1139			  const xmlChar *name, const xmlChar *attrName,
1140			  const xmlChar *uriAttrName, xmlCatalogPrefer prefer,
1141			  xmlCatalogEntryPtr cgroup) {
1142    int ok = 1;
1143    xmlChar *uriValue;
1144    xmlChar *nameValue = NULL;
1145    xmlChar *base = NULL;
1146    xmlChar *URL = NULL;
1147    xmlCatalogEntryPtr ret = NULL;
1148
1149    if (attrName != NULL) {
1150	nameValue = xmlGetProp(cur, attrName);
1151	if (nameValue == NULL) {
1152	    xmlCatalogErr(ret, cur, XML_CATALOG_MISSING_ATTR,
1153			  "%s entry lacks '%s'\n", name, attrName, NULL);
1154	    ok = 0;
1155	}
1156    }
1157    uriValue = xmlGetProp(cur, uriAttrName);
1158    if (uriValue == NULL) {
1159	xmlCatalogErr(ret, cur, XML_CATALOG_MISSING_ATTR,
1160		"%s entry lacks '%s'\n", name, uriAttrName, NULL);
1161	ok = 0;
1162    }
1163    if (!ok) {
1164	if (nameValue != NULL)
1165	    xmlFree(nameValue);
1166	if (uriValue != NULL)
1167	    xmlFree(uriValue);
1168	return(NULL);
1169    }
1170
1171    base = xmlNodeGetBase(cur->doc, cur);
1172    URL = xmlBuildURI(uriValue, base);
1173    if (URL != NULL) {
1174	if (xmlDebugCatalogs > 1) {
1175	    if (nameValue != NULL)
1176		xmlGenericError(xmlGenericErrorContext,
1177			"Found %s: '%s' '%s'\n", name, nameValue, URL);
1178	    else
1179		xmlGenericError(xmlGenericErrorContext,
1180			"Found %s: '%s'\n", name, URL);
1181	}
1182	ret = xmlNewCatalogEntry(type, nameValue, uriValue, URL, prefer, cgroup);
1183    } else {
1184	xmlCatalogErr(ret, cur, XML_CATALOG_ENTRY_BROKEN,
1185		"%s entry '%s' broken ?: %s\n", name, uriAttrName, uriValue);
1186    }
1187    if (nameValue != NULL)
1188	xmlFree(nameValue);
1189    if (uriValue != NULL)
1190	xmlFree(uriValue);
1191    if (base != NULL)
1192	xmlFree(base);
1193    if (URL != NULL)
1194	xmlFree(URL);
1195    return(ret);
1196}
1197
1198/**
1199 * xmlParseXMLCatalogNode:
1200 * @cur:  the XML node
1201 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1202 * @parent:  the parent Catalog entry
1203 * @cgroup:  the group which includes this node
1204 *
1205 * Examines an XML tree node of a catalog and build
1206 * a Catalog entry from it adding it to its parent. The examination can
1207 * be recursive.
1208 */
1209static void
1210xmlParseXMLCatalogNode(xmlNodePtr cur, xmlCatalogPrefer prefer,
1211	               xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup)
1212{
1213    xmlChar *base = NULL;
1214    xmlCatalogEntryPtr entry = NULL;
1215
1216    if (cur == NULL)
1217        return;
1218    if (xmlStrEqual(cur->name, BAD_CAST "group")) {
1219        xmlChar *prop;
1220	xmlCatalogPrefer pref = XML_CATA_PREFER_NONE;
1221
1222        prop = xmlGetProp(cur, BAD_CAST "prefer");
1223        if (prop != NULL) {
1224            if (xmlStrEqual(prop, BAD_CAST "system")) {
1225                prefer = XML_CATA_PREFER_SYSTEM;
1226            } else if (xmlStrEqual(prop, BAD_CAST "public")) {
1227                prefer = XML_CATA_PREFER_PUBLIC;
1228            } else {
1229		xmlCatalogErr(parent, cur, XML_CATALOG_PREFER_VALUE,
1230                              "Invalid value for prefer: '%s'\n",
1231			      prop, NULL, NULL);
1232            }
1233            xmlFree(prop);
1234	    pref = prefer;
1235        }
1236	prop = xmlGetProp(cur, BAD_CAST "id");
1237	base = xmlGetNsProp(cur, BAD_CAST "base", XML_XML_NAMESPACE);
1238	entry = xmlNewCatalogEntry(XML_CATA_GROUP, prop, base, NULL, pref, cgroup);
1239	xmlFree(prop);
1240    } else if (xmlStrEqual(cur->name, BAD_CAST "public")) {
1241	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_PUBLIC,
1242		BAD_CAST "public", BAD_CAST "publicId", BAD_CAST "uri", prefer, cgroup);
1243    } else if (xmlStrEqual(cur->name, BAD_CAST "system")) {
1244	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_SYSTEM,
1245		BAD_CAST "system", BAD_CAST "systemId", BAD_CAST "uri", prefer, cgroup);
1246    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteSystem")) {
1247	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_SYSTEM,
1248		BAD_CAST "rewriteSystem", BAD_CAST "systemIdStartString",
1249		BAD_CAST "rewritePrefix", prefer, cgroup);
1250    } else if (xmlStrEqual(cur->name, BAD_CAST "delegatePublic")) {
1251	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_PUBLIC,
1252		BAD_CAST "delegatePublic", BAD_CAST "publicIdStartString",
1253		BAD_CAST "catalog", prefer, cgroup);
1254    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateSystem")) {
1255	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_SYSTEM,
1256		BAD_CAST "delegateSystem", BAD_CAST "systemIdStartString",
1257		BAD_CAST "catalog", prefer, cgroup);
1258    } else if (xmlStrEqual(cur->name, BAD_CAST "uri")) {
1259	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_URI,
1260		BAD_CAST "uri", BAD_CAST "name",
1261		BAD_CAST "uri", prefer, cgroup);
1262    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteURI")) {
1263	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_URI,
1264		BAD_CAST "rewriteURI", BAD_CAST "uriStartString",
1265		BAD_CAST "rewritePrefix", prefer, cgroup);
1266    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateURI")) {
1267	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_URI,
1268		BAD_CAST "delegateURI", BAD_CAST "uriStartString",
1269		BAD_CAST "catalog", prefer, cgroup);
1270    } else if (xmlStrEqual(cur->name, BAD_CAST "nextCatalog")) {
1271	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_NEXT_CATALOG,
1272		BAD_CAST "nextCatalog", NULL,
1273		BAD_CAST "catalog", prefer, cgroup);
1274    }
1275    if (entry != NULL) {
1276        if (parent != NULL) {
1277	    entry->parent = parent;
1278	    if (parent->children == NULL)
1279		parent->children = entry;
1280	    else {
1281		xmlCatalogEntryPtr prev;
1282
1283		prev = parent->children;
1284		while (prev->next != NULL)
1285		    prev = prev->next;
1286		prev->next = entry;
1287	    }
1288	}
1289	if (entry->type == XML_CATA_GROUP) {
1290	    /*
1291	     * Recurse to propagate prefer to the subtree
1292	     * (xml:base handling is automated)
1293	     */
1294            xmlParseXMLCatalogNodeList(cur->children, prefer, parent, entry);
1295	}
1296    }
1297    if (base != NULL)
1298	xmlFree(base);
1299}
1300
1301/**
1302 * xmlParseXMLCatalogNodeList:
1303 * @cur:  the XML node list of siblings
1304 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1305 * @parent:  the parent Catalog entry
1306 * @cgroup:  the group which includes this list
1307 *
1308 * Examines a list of XML sibling nodes of a catalog and build
1309 * a list of Catalog entry from it adding it to the parent.
1310 * The examination will recurse to examine node subtrees.
1311 */
1312static void
1313xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
1314	                   xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup) {
1315    while (cur != NULL) {
1316	if ((cur->ns != NULL) && (cur->ns->href != NULL) &&
1317	    (xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
1318	    xmlParseXMLCatalogNode(cur, prefer, parent, cgroup);
1319	}
1320	cur = cur->next;
1321    }
1322    /* TODO: sort the list according to REWRITE lengths and prefer value */
1323}
1324
1325/**
1326 * xmlParseXMLCatalogFile:
1327 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1328 * @filename:  the filename for the catalog
1329 *
1330 * Parses the catalog file to extract the XML tree and then analyze the
1331 * tree to build a list of Catalog entries corresponding to this catalog
1332 *
1333 * Returns the resulting Catalog entries list
1334 */
1335static xmlCatalogEntryPtr
1336xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename) {
1337    xmlDocPtr doc;
1338    xmlNodePtr cur;
1339    xmlChar *prop;
1340    xmlCatalogEntryPtr parent = NULL;
1341
1342    if (filename == NULL)
1343        return(NULL);
1344
1345    doc = xmlParseCatalogFile((const char *) filename);
1346    if (doc == NULL) {
1347	if (xmlDebugCatalogs)
1348	    xmlGenericError(xmlGenericErrorContext,
1349		    "Failed to parse catalog %s\n", filename);
1350	return(NULL);
1351    }
1352
1353    if (xmlDebugCatalogs)
1354	xmlGenericError(xmlGenericErrorContext,
1355		"%d Parsing catalog %s\n", xmlGetThreadId(), filename);
1356
1357    cur = xmlDocGetRootElement(doc);
1358    if ((cur != NULL) && (xmlStrEqual(cur->name, BAD_CAST "catalog")) &&
1359	(cur->ns != NULL) && (cur->ns->href != NULL) &&
1360	(xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
1361
1362	parent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
1363				    (const xmlChar *)filename, NULL, prefer, NULL);
1364        if (parent == NULL) {
1365	    xmlFreeDoc(doc);
1366	    return(NULL);
1367	}
1368
1369	prop = xmlGetProp(cur, BAD_CAST "prefer");
1370	if (prop != NULL) {
1371	    if (xmlStrEqual(prop, BAD_CAST "system")) {
1372		prefer = XML_CATA_PREFER_SYSTEM;
1373	    } else if (xmlStrEqual(prop, BAD_CAST "public")) {
1374		prefer = XML_CATA_PREFER_PUBLIC;
1375	    } else {
1376		xmlCatalogErr(NULL, cur, XML_CATALOG_PREFER_VALUE,
1377			      "Invalid value for prefer: '%s'\n",
1378			      prop, NULL, NULL);
1379	    }
1380	    xmlFree(prop);
1381	}
1382	cur = cur->children;
1383	xmlParseXMLCatalogNodeList(cur, prefer, parent, NULL);
1384    } else {
1385	xmlCatalogErr(NULL, (xmlNodePtr) doc, XML_CATALOG_NOT_CATALOG,
1386		      "File %s is not an XML Catalog\n",
1387		      filename, NULL, NULL);
1388	xmlFreeDoc(doc);
1389	return(NULL);
1390    }
1391    xmlFreeDoc(doc);
1392    return(parent);
1393}
1394
1395/**
1396 * xmlFetchXMLCatalogFile:
1397 * @catal:  an existing but incomplete catalog entry
1398 *
1399 * Fetch and parse the subcatalog referenced by an entry
1400 *
1401 * Returns 0 in case of success, -1 otherwise
1402 */
1403static int
1404xmlFetchXMLCatalogFile(xmlCatalogEntryPtr catal) {
1405    xmlCatalogEntryPtr doc;
1406
1407    if (catal == NULL)
1408	return(-1);
1409    if (catal->URL == NULL)
1410	return(-1);
1411
1412    /*
1413     * lock the whole catalog for modification
1414     */
1415    xmlRMutexLock(xmlCatalogMutex);
1416    if (catal->children != NULL) {
1417	/* Okay someone else did it in the meantime */
1418	xmlRMutexUnlock(xmlCatalogMutex);
1419	return(0);
1420    }
1421
1422    if (xmlCatalogXMLFiles != NULL) {
1423	doc = (xmlCatalogEntryPtr)
1424	    xmlHashLookup(xmlCatalogXMLFiles, catal->URL);
1425	if (doc != NULL) {
1426	    if (xmlDebugCatalogs)
1427		xmlGenericError(xmlGenericErrorContext,
1428		    "Found %s in file hash\n", catal->URL);
1429
1430	    if (catal->type == XML_CATA_CATALOG)
1431		catal->children = doc->children;
1432	    else
1433		catal->children = doc;
1434	    catal->dealloc = 0;
1435	    xmlRMutexUnlock(xmlCatalogMutex);
1436	    return(0);
1437	}
1438	if (xmlDebugCatalogs)
1439	    xmlGenericError(xmlGenericErrorContext,
1440		"%s not found in file hash\n", catal->URL);
1441    }
1442
1443    /*
1444     * Fetch and parse. Note that xmlParseXMLCatalogFile does not
1445     * use the existing catalog, there is no recursion allowed at
1446     * that level.
1447     */
1448    doc = xmlParseXMLCatalogFile(catal->prefer, catal->URL);
1449    if (doc == NULL) {
1450	catal->type = XML_CATA_BROKEN_CATALOG;
1451	xmlRMutexUnlock(xmlCatalogMutex);
1452	return(-1);
1453    }
1454
1455    if (catal->type == XML_CATA_CATALOG)
1456	catal->children = doc->children;
1457    else
1458	catal->children = doc;
1459
1460    doc->dealloc = 1;
1461
1462    if (xmlCatalogXMLFiles == NULL)
1463	xmlCatalogXMLFiles = xmlHashCreate(10);
1464    if (xmlCatalogXMLFiles != NULL) {
1465	if (xmlDebugCatalogs)
1466	    xmlGenericError(xmlGenericErrorContext,
1467		"%s added to file hash\n", catal->URL);
1468	xmlHashAddEntry(xmlCatalogXMLFiles, catal->URL, doc);
1469    }
1470    xmlRMutexUnlock(xmlCatalogMutex);
1471    return(0);
1472}
1473
1474/************************************************************************
1475 *									*
1476 *			XML Catalog handling				*
1477 *									*
1478 ************************************************************************/
1479
1480/**
1481 * xmlAddXMLCatalog:
1482 * @catal:  top of an XML catalog
1483 * @type:  the type of record to add to the catalog
1484 * @orig:  the system, public or prefix to match (or NULL)
1485 * @replace:  the replacement value for the match
1486 *
1487 * Add an entry in the XML catalog, it may overwrite existing but
1488 * different entries.
1489 *
1490 * Returns 0 if successful, -1 otherwise
1491 */
1492static int
1493xmlAddXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *type,
1494	      const xmlChar *orig, const xmlChar *replace) {
1495    xmlCatalogEntryPtr cur;
1496    xmlCatalogEntryType typ;
1497    int doregister = 0;
1498
1499    if ((catal == NULL) ||
1500	((catal->type != XML_CATA_CATALOG) &&
1501	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1502	return(-1);
1503    if (catal->children == NULL) {
1504	xmlFetchXMLCatalogFile(catal);
1505    }
1506    if (catal->children == NULL)
1507	doregister = 1;
1508
1509    typ = xmlGetXMLCatalogEntryType(type);
1510    if (typ == XML_CATA_NONE) {
1511	if (xmlDebugCatalogs)
1512	    xmlGenericError(xmlGenericErrorContext,
1513		    "Failed to add unknown element %s to catalog\n", type);
1514	return(-1);
1515    }
1516
1517    cur = catal->children;
1518    /*
1519     * Might be a simple "update in place"
1520     */
1521    if (cur != NULL) {
1522	while (cur != NULL) {
1523	    if ((orig != NULL) && (cur->type == typ) &&
1524		(xmlStrEqual(orig, cur->name))) {
1525		if (xmlDebugCatalogs)
1526		    xmlGenericError(xmlGenericErrorContext,
1527			    "Updating element %s to catalog\n", type);
1528		if (cur->value != NULL)
1529		    xmlFree(cur->value);
1530		if (cur->URL != NULL)
1531		    xmlFree(cur->URL);
1532		cur->value = xmlStrdup(replace);
1533		cur->URL = xmlStrdup(replace);
1534		return(0);
1535	    }
1536	    if (cur->next == NULL)
1537		break;
1538	    cur = cur->next;
1539	}
1540    }
1541    if (xmlDebugCatalogs)
1542	xmlGenericError(xmlGenericErrorContext,
1543		"Adding element %s to catalog\n", type);
1544    if (cur == NULL)
1545	catal->children = xmlNewCatalogEntry(typ, orig, replace,
1546		                             NULL, catal->prefer, NULL);
1547    else
1548	cur->next = xmlNewCatalogEntry(typ, orig, replace,
1549		                       NULL, catal->prefer, NULL);
1550    if (doregister) {
1551        catal->type = XML_CATA_CATALOG;
1552	cur = (xmlCatalogEntryPtr)xmlHashLookup(xmlCatalogXMLFiles, catal->URL);
1553	if (cur != NULL)
1554	    cur->children = catal->children;
1555    }
1556
1557    return(0);
1558}
1559
1560/**
1561 * xmlDelXMLCatalog:
1562 * @catal:  top of an XML catalog
1563 * @value:  the value to remove from the catalog
1564 *
1565 * Remove entries in the XML catalog where the value or the URI
1566 * is equal to @value
1567 *
1568 * Returns the number of entries removed if successful, -1 otherwise
1569 */
1570static int
1571xmlDelXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *value) {
1572    xmlCatalogEntryPtr cur;
1573    int ret = 0;
1574
1575    if ((catal == NULL) ||
1576	((catal->type != XML_CATA_CATALOG) &&
1577	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1578	return(-1);
1579    if (value == NULL)
1580	return(-1);
1581    if (catal->children == NULL) {
1582	xmlFetchXMLCatalogFile(catal);
1583    }
1584
1585    /*
1586     * Scan the children
1587     */
1588    cur = catal->children;
1589    while (cur != NULL) {
1590	if (((cur->name != NULL) && (xmlStrEqual(value, cur->name))) ||
1591	    (xmlStrEqual(value, cur->value))) {
1592	    if (xmlDebugCatalogs) {
1593		if (cur->name != NULL)
1594		    xmlGenericError(xmlGenericErrorContext,
1595			    "Removing element %s from catalog\n", cur->name);
1596		else
1597		    xmlGenericError(xmlGenericErrorContext,
1598			    "Removing element %s from catalog\n", cur->value);
1599	    }
1600	    cur->type = XML_CATA_REMOVED;
1601	}
1602	cur = cur->next;
1603    }
1604    return(ret);
1605}
1606
1607/**
1608 * xmlCatalogXMLResolve:
1609 * @catal:  a catalog list
1610 * @pubID:  the public ID string
1611 * @sysID:  the system ID string
1612 *
1613 * Do a complete resolution lookup of an External Identifier for a
1614 * list of catalog entries.
1615 *
1616 * Implements (or tries to) 7.1. External Identifier Resolution
1617 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1618 *
1619 * Returns the URI of the resource or NULL if not found
1620 */
1621static xmlChar *
1622xmlCatalogXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1623	              const xmlChar *sysID) {
1624    xmlChar *ret = NULL;
1625    xmlCatalogEntryPtr cur;
1626    int haveDelegate = 0;
1627    int haveNext = 0;
1628
1629    /*
1630     * protection against loops
1631     */
1632    if (catal->depth > MAX_CATAL_DEPTH) {
1633	xmlCatalogErr(catal, NULL, XML_CATALOG_RECURSION,
1634		      "Detected recursion in catalog %s\n",
1635		      catal->name, NULL, NULL);
1636	return(NULL);
1637    }
1638    catal->depth++;
1639
1640    /*
1641     * First tries steps 2/ 3/ 4/ if a system ID is provided.
1642     */
1643    if (sysID != NULL) {
1644	xmlCatalogEntryPtr rewrite = NULL;
1645	int lenrewrite = 0, len;
1646	cur = catal;
1647	haveDelegate = 0;
1648	while (cur != NULL) {
1649	    switch (cur->type) {
1650		case XML_CATA_SYSTEM:
1651		    if (xmlStrEqual(sysID, cur->name)) {
1652			if (xmlDebugCatalogs)
1653			    xmlGenericError(xmlGenericErrorContext,
1654				    "Found system match %s, using %s\n",
1655				            cur->name, cur->URL);
1656			catal->depth--;
1657			return(xmlStrdup(cur->URL));
1658		    }
1659		    break;
1660		case XML_CATA_REWRITE_SYSTEM:
1661		    len = xmlStrlen(cur->name);
1662		    if ((len > lenrewrite) &&
1663			(!xmlStrncmp(sysID, cur->name, len))) {
1664			lenrewrite = len;
1665			rewrite = cur;
1666		    }
1667		    break;
1668		case XML_CATA_DELEGATE_SYSTEM:
1669		    if (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))
1670			haveDelegate++;
1671		    break;
1672		case XML_CATA_NEXT_CATALOG:
1673		    haveNext++;
1674		    break;
1675		default:
1676		    break;
1677	    }
1678	    cur = cur->next;
1679	}
1680	if (rewrite != NULL) {
1681	    if (xmlDebugCatalogs)
1682		xmlGenericError(xmlGenericErrorContext,
1683			"Using rewriting rule %s\n", rewrite->name);
1684	    ret = xmlStrdup(rewrite->URL);
1685	    if (ret != NULL)
1686		ret = xmlStrcat(ret, &sysID[lenrewrite]);
1687	    catal->depth--;
1688	    return(ret);
1689	}
1690	if (haveDelegate) {
1691	    const xmlChar *delegates[MAX_DELEGATE];
1692	    int nbList = 0, i;
1693
1694	    /*
1695	     * Assume the entries have been sorted by decreasing substring
1696	     * matches when the list was produced.
1697	     */
1698	    cur = catal;
1699	    while (cur != NULL) {
1700		if ((cur->type == XML_CATA_DELEGATE_SYSTEM) &&
1701		    (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))) {
1702		    for (i = 0;i < nbList;i++)
1703			if (xmlStrEqual(cur->URL, delegates[i]))
1704			    break;
1705		    if (i < nbList) {
1706			cur = cur->next;
1707			continue;
1708		    }
1709		    if (nbList < MAX_DELEGATE)
1710			delegates[nbList++] = cur->URL;
1711
1712		    if (cur->children == NULL) {
1713			xmlFetchXMLCatalogFile(cur);
1714		    }
1715		    if (cur->children != NULL) {
1716			if (xmlDebugCatalogs)
1717			    xmlGenericError(xmlGenericErrorContext,
1718				    "Trying system delegate %s\n", cur->URL);
1719			ret = xmlCatalogListXMLResolve(
1720				cur->children, NULL, sysID);
1721			if (ret != NULL) {
1722			    catal->depth--;
1723			    return(ret);
1724			}
1725		    }
1726		}
1727		cur = cur->next;
1728	    }
1729	    /*
1730	     * Apply the cut algorithm explained in 4/
1731	     */
1732	    catal->depth--;
1733	    return(XML_CATAL_BREAK);
1734	}
1735    }
1736    /*
1737     * Then tries 5/ 6/ if a public ID is provided
1738     */
1739    if (pubID != NULL) {
1740	cur = catal;
1741	haveDelegate = 0;
1742	while (cur != NULL) {
1743	    switch (cur->type) {
1744		case XML_CATA_PUBLIC:
1745		    if (xmlStrEqual(pubID, cur->name)) {
1746			if (xmlDebugCatalogs)
1747			    xmlGenericError(xmlGenericErrorContext,
1748				    "Found public match %s\n", cur->name);
1749			catal->depth--;
1750			return(xmlStrdup(cur->URL));
1751		    }
1752		    break;
1753		case XML_CATA_DELEGATE_PUBLIC:
1754		    if (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)) &&
1755			(cur->prefer == XML_CATA_PREFER_PUBLIC))
1756			haveDelegate++;
1757		    break;
1758		case XML_CATA_NEXT_CATALOG:
1759		    if (sysID == NULL)
1760			haveNext++;
1761		    break;
1762		default:
1763		    break;
1764	    }
1765	    cur = cur->next;
1766	}
1767	if (haveDelegate) {
1768	    const xmlChar *delegates[MAX_DELEGATE];
1769	    int nbList = 0, i;
1770
1771	    /*
1772	     * Assume the entries have been sorted by decreasing substring
1773	     * matches when the list was produced.
1774	     */
1775	    cur = catal;
1776	    while (cur != NULL) {
1777		if ((cur->type == XML_CATA_DELEGATE_PUBLIC) &&
1778		    (cur->prefer == XML_CATA_PREFER_PUBLIC) &&
1779		    (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)))) {
1780
1781		    for (i = 0;i < nbList;i++)
1782			if (xmlStrEqual(cur->URL, delegates[i]))
1783			    break;
1784		    if (i < nbList) {
1785			cur = cur->next;
1786			continue;
1787		    }
1788		    if (nbList < MAX_DELEGATE)
1789			delegates[nbList++] = cur->URL;
1790
1791		    if (cur->children == NULL) {
1792			xmlFetchXMLCatalogFile(cur);
1793		    }
1794		    if (cur->children != NULL) {
1795			if (xmlDebugCatalogs)
1796			    xmlGenericError(xmlGenericErrorContext,
1797				    "Trying public delegate %s\n", cur->URL);
1798			ret = xmlCatalogListXMLResolve(
1799				cur->children, pubID, NULL);
1800			if (ret != NULL) {
1801			    catal->depth--;
1802			    return(ret);
1803			}
1804		    }
1805		}
1806		cur = cur->next;
1807	    }
1808	    /*
1809	     * Apply the cut algorithm explained in 4/
1810	     */
1811	    catal->depth--;
1812	    return(XML_CATAL_BREAK);
1813	}
1814    }
1815    if (haveNext) {
1816	cur = catal;
1817	while (cur != NULL) {
1818	    if (cur->type == XML_CATA_NEXT_CATALOG) {
1819		if (cur->children == NULL) {
1820		    xmlFetchXMLCatalogFile(cur);
1821		}
1822		if (cur->children != NULL) {
1823		    ret = xmlCatalogListXMLResolve(cur->children, pubID, sysID);
1824		    if (ret != NULL) {
1825			catal->depth--;
1826			return(ret);
1827		    } else if (catal->depth > MAX_CATAL_DEPTH) {
1828		        return(NULL);
1829		    }
1830		}
1831	    }
1832	    cur = cur->next;
1833	}
1834    }
1835
1836    catal->depth--;
1837    return(NULL);
1838}
1839
1840/**
1841 * xmlCatalogXMLResolveURI:
1842 * @catal:  a catalog list
1843 * @URI:  the URI
1844 * @sysID:  the system ID string
1845 *
1846 * Do a complete resolution lookup of an External Identifier for a
1847 * list of catalog entries.
1848 *
1849 * Implements (or tries to) 7.2.2. URI Resolution
1850 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1851 *
1852 * Returns the URI of the resource or NULL if not found
1853 */
1854static xmlChar *
1855xmlCatalogXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) {
1856    xmlChar *ret = NULL;
1857    xmlCatalogEntryPtr cur;
1858    int haveDelegate = 0;
1859    int haveNext = 0;
1860    xmlCatalogEntryPtr rewrite = NULL;
1861    int lenrewrite = 0, len;
1862
1863    if (catal == NULL)
1864	return(NULL);
1865
1866    if (URI == NULL)
1867	return(NULL);
1868
1869    if (catal->depth > MAX_CATAL_DEPTH) {
1870	xmlCatalogErr(catal, NULL, XML_CATALOG_RECURSION,
1871		      "Detected recursion in catalog %s\n",
1872		      catal->name, NULL, NULL);
1873	return(NULL);
1874    }
1875
1876    /*
1877     * First tries steps 2/ 3/ 4/ if a system ID is provided.
1878     */
1879    cur = catal;
1880    haveDelegate = 0;
1881    while (cur != NULL) {
1882	switch (cur->type) {
1883	    case XML_CATA_URI:
1884		if (xmlStrEqual(URI, cur->name)) {
1885		    if (xmlDebugCatalogs)
1886			xmlGenericError(xmlGenericErrorContext,
1887				"Found URI match %s\n", cur->name);
1888		    return(xmlStrdup(cur->URL));
1889		}
1890		break;
1891	    case XML_CATA_REWRITE_URI:
1892		len = xmlStrlen(cur->name);
1893		if ((len > lenrewrite) &&
1894		    (!xmlStrncmp(URI, cur->name, len))) {
1895		    lenrewrite = len;
1896		    rewrite = cur;
1897		}
1898		break;
1899	    case XML_CATA_DELEGATE_URI:
1900		if (!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))
1901		    haveDelegate++;
1902		break;
1903	    case XML_CATA_NEXT_CATALOG:
1904		haveNext++;
1905		break;
1906	    default:
1907		break;
1908	}
1909	cur = cur->next;
1910    }
1911    if (rewrite != NULL) {
1912	if (xmlDebugCatalogs)
1913	    xmlGenericError(xmlGenericErrorContext,
1914		    "Using rewriting rule %s\n", rewrite->name);
1915	ret = xmlStrdup(rewrite->URL);
1916	if (ret != NULL)
1917	    ret = xmlStrcat(ret, &URI[lenrewrite]);
1918	return(ret);
1919    }
1920    if (haveDelegate) {
1921	const xmlChar *delegates[MAX_DELEGATE];
1922	int nbList = 0, i;
1923
1924	/*
1925	 * Assume the entries have been sorted by decreasing substring
1926	 * matches when the list was produced.
1927	 */
1928	cur = catal;
1929	while (cur != NULL) {
1930	    if (((cur->type == XML_CATA_DELEGATE_SYSTEM) ||
1931	         (cur->type == XML_CATA_DELEGATE_URI)) &&
1932		(!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))) {
1933		for (i = 0;i < nbList;i++)
1934		    if (xmlStrEqual(cur->URL, delegates[i]))
1935			break;
1936		if (i < nbList) {
1937		    cur = cur->next;
1938		    continue;
1939		}
1940		if (nbList < MAX_DELEGATE)
1941		    delegates[nbList++] = cur->URL;
1942
1943		if (cur->children == NULL) {
1944		    xmlFetchXMLCatalogFile(cur);
1945		}
1946		if (cur->children != NULL) {
1947		    if (xmlDebugCatalogs)
1948			xmlGenericError(xmlGenericErrorContext,
1949				"Trying URI delegate %s\n", cur->URL);
1950		    ret = xmlCatalogListXMLResolveURI(
1951			    cur->children, URI);
1952		    if (ret != NULL)
1953			return(ret);
1954		}
1955	    }
1956	    cur = cur->next;
1957	}
1958	/*
1959	 * Apply the cut algorithm explained in 4/
1960	 */
1961	return(XML_CATAL_BREAK);
1962    }
1963    if (haveNext) {
1964	cur = catal;
1965	while (cur != NULL) {
1966	    if (cur->type == XML_CATA_NEXT_CATALOG) {
1967		if (cur->children == NULL) {
1968		    xmlFetchXMLCatalogFile(cur);
1969		}
1970		if (cur->children != NULL) {
1971		    ret = xmlCatalogListXMLResolveURI(cur->children, URI);
1972		    if (ret != NULL)
1973			return(ret);
1974		}
1975	    }
1976	    cur = cur->next;
1977	}
1978    }
1979
1980    return(NULL);
1981}
1982
1983/**
1984 * xmlCatalogListXMLResolve:
1985 * @catal:  a catalog list
1986 * @pubID:  the public ID string
1987 * @sysID:  the system ID string
1988 *
1989 * Do a complete resolution lookup of an External Identifier for a
1990 * list of catalogs
1991 *
1992 * Implements (or tries to) 7.1. External Identifier Resolution
1993 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1994 *
1995 * Returns the URI of the resource or NULL if not found
1996 */
1997static xmlChar *
1998xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1999	              const xmlChar *sysID) {
2000    xmlChar *ret = NULL;
2001    xmlChar *urnID = NULL;
2002    xmlChar *normid;
2003
2004    if (catal == NULL)
2005        return(NULL);
2006    if ((pubID == NULL) && (sysID == NULL))
2007	return(NULL);
2008
2009    normid = xmlCatalogNormalizePublic(pubID);
2010    if (normid != NULL)
2011        pubID = (*normid != 0 ? normid : NULL);
2012
2013    if (!xmlStrncmp(pubID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
2014	urnID = xmlCatalogUnWrapURN(pubID);
2015	if (xmlDebugCatalogs) {
2016	    if (urnID == NULL)
2017		xmlGenericError(xmlGenericErrorContext,
2018			"Public URN ID %s expanded to NULL\n", pubID);
2019	    else
2020		xmlGenericError(xmlGenericErrorContext,
2021			"Public URN ID expanded to %s\n", urnID);
2022	}
2023	ret = xmlCatalogListXMLResolve(catal, urnID, sysID);
2024	if (urnID != NULL)
2025	    xmlFree(urnID);
2026	if (normid != NULL)
2027	    xmlFree(normid);
2028	return(ret);
2029    }
2030    if (!xmlStrncmp(sysID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
2031	urnID = xmlCatalogUnWrapURN(sysID);
2032	if (xmlDebugCatalogs) {
2033	    if (urnID == NULL)
2034		xmlGenericError(xmlGenericErrorContext,
2035			"System URN ID %s expanded to NULL\n", sysID);
2036	    else
2037		xmlGenericError(xmlGenericErrorContext,
2038			"System URN ID expanded to %s\n", urnID);
2039	}
2040	if (pubID == NULL)
2041	    ret = xmlCatalogListXMLResolve(catal, urnID, NULL);
2042	else if (xmlStrEqual(pubID, urnID))
2043	    ret = xmlCatalogListXMLResolve(catal, pubID, NULL);
2044	else {
2045	    ret = xmlCatalogListXMLResolve(catal, pubID, urnID);
2046	}
2047	if (urnID != NULL)
2048	    xmlFree(urnID);
2049	if (normid != NULL)
2050	    xmlFree(normid);
2051	return(ret);
2052    }
2053    while (catal != NULL) {
2054	if (catal->type == XML_CATA_CATALOG) {
2055	    if (catal->children == NULL) {
2056		xmlFetchXMLCatalogFile(catal);
2057	    }
2058	    if (catal->children != NULL) {
2059		ret = xmlCatalogXMLResolve(catal->children, pubID, sysID);
2060		if (ret != NULL) {
2061		    break;
2062                } else if ((catal->children != NULL) &&
2063		           (catal->children->depth > MAX_CATAL_DEPTH)) {
2064	            ret = NULL;
2065		    break;
2066	        }
2067	    }
2068	}
2069	catal = catal->next;
2070    }
2071    if (normid != NULL)
2072	xmlFree(normid);
2073    return(ret);
2074}
2075
2076/**
2077 * xmlCatalogListXMLResolveURI:
2078 * @catal:  a catalog list
2079 * @URI:  the URI
2080 *
2081 * Do a complete resolution lookup of an URI for a list of catalogs
2082 *
2083 * Implements (or tries to) 7.2. URI Resolution
2084 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
2085 *
2086 * Returns the URI of the resource or NULL if not found
2087 */
2088static xmlChar *
2089xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) {
2090    xmlChar *ret = NULL;
2091    xmlChar *urnID = NULL;
2092
2093    if (catal == NULL)
2094        return(NULL);
2095    if (URI == NULL)
2096	return(NULL);
2097
2098    if (!xmlStrncmp(URI, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
2099	urnID = xmlCatalogUnWrapURN(URI);
2100	if (xmlDebugCatalogs) {
2101	    if (urnID == NULL)
2102		xmlGenericError(xmlGenericErrorContext,
2103			"URN ID %s expanded to NULL\n", URI);
2104	    else
2105		xmlGenericError(xmlGenericErrorContext,
2106			"URN ID expanded to %s\n", urnID);
2107	}
2108	ret = xmlCatalogListXMLResolve(catal, urnID, NULL);
2109	if (urnID != NULL)
2110	    xmlFree(urnID);
2111	return(ret);
2112    }
2113    while (catal != NULL) {
2114	if (catal->type == XML_CATA_CATALOG) {
2115	    if (catal->children == NULL) {
2116		xmlFetchXMLCatalogFile(catal);
2117	    }
2118	    if (catal->children != NULL) {
2119		ret = xmlCatalogXMLResolveURI(catal->children, URI);
2120		if (ret != NULL)
2121		    return(ret);
2122	    }
2123	}
2124	catal = catal->next;
2125    }
2126    return(ret);
2127}
2128
2129/************************************************************************
2130 *									*
2131 *			The SGML Catalog parser				*
2132 *									*
2133 ************************************************************************/
2134
2135
2136#define RAW *cur
2137#define NEXT cur++;
2138#define SKIP(x) cur += x;
2139
2140#define SKIP_BLANKS while (IS_BLANK_CH(*cur)) NEXT;
2141
2142/**
2143 * xmlParseSGMLCatalogComment:
2144 * @cur:  the current character
2145 *
2146 * Skip a comment in an SGML catalog
2147 *
2148 * Returns new current character
2149 */
2150static const xmlChar *
2151xmlParseSGMLCatalogComment(const xmlChar *cur) {
2152    if ((cur[0] != '-') || (cur[1] != '-'))
2153	return(cur);
2154    SKIP(2);
2155    while ((cur[0] != 0) && ((cur[0] != '-') || ((cur[1] != '-'))))
2156	NEXT;
2157    if (cur[0] == 0) {
2158	return(NULL);
2159    }
2160    return(cur + 2);
2161}
2162
2163/**
2164 * xmlParseSGMLCatalogPubid:
2165 * @cur:  the current character
2166 * @id:  the return location
2167 *
2168 * Parse an SGML catalog ID
2169 *
2170 * Returns new current character and store the value in @id
2171 */
2172static const xmlChar *
2173xmlParseSGMLCatalogPubid(const xmlChar *cur, xmlChar **id) {
2174    xmlChar *buf = NULL, *tmp;
2175    int len = 0;
2176    int size = 50;
2177    xmlChar stop;
2178    int count = 0;
2179
2180    *id = NULL;
2181
2182    if (RAW == '"') {
2183        NEXT;
2184	stop = '"';
2185    } else if (RAW == '\'') {
2186        NEXT;
2187	stop = '\'';
2188    } else {
2189	stop = ' ';
2190    }
2191    buf = (xmlChar *) xmlMallocAtomic(size * sizeof(xmlChar));
2192    if (buf == NULL) {
2193        xmlCatalogErrMemory("allocating public ID");
2194	return(NULL);
2195    }
2196    while (IS_PUBIDCHAR_CH(*cur) || (*cur == '?')) {
2197	if ((*cur == stop) && (stop != ' '))
2198	    break;
2199	if ((stop == ' ') && (IS_BLANK_CH(*cur)))
2200	    break;
2201	if (len + 1 >= size) {
2202	    size *= 2;
2203	    tmp = (xmlChar *) xmlRealloc(buf, size * sizeof(xmlChar));
2204	    if (tmp == NULL) {
2205		xmlCatalogErrMemory("allocating public ID");
2206		xmlFree(buf);
2207		return(NULL);
2208	    }
2209	    buf = tmp;
2210	}
2211	buf[len++] = *cur;
2212	count++;
2213	NEXT;
2214    }
2215    buf[len] = 0;
2216    if (stop == ' ') {
2217	if (!IS_BLANK_CH(*cur)) {
2218	    xmlFree(buf);
2219	    return(NULL);
2220	}
2221    } else {
2222	if (*cur != stop) {
2223	    xmlFree(buf);
2224	    return(NULL);
2225	}
2226	NEXT;
2227    }
2228    *id = buf;
2229    return(cur);
2230}
2231
2232/**
2233 * xmlParseSGMLCatalogName:
2234 * @cur:  the current character
2235 * @name:  the return location
2236 *
2237 * Parse an SGML catalog name
2238 *
2239 * Returns new current character and store the value in @name
2240 */
2241static const xmlChar *
2242xmlParseSGMLCatalogName(const xmlChar *cur, xmlChar **name) {
2243    xmlChar buf[XML_MAX_NAMELEN + 5];
2244    int len = 0;
2245    int c;
2246
2247    *name = NULL;
2248
2249    /*
2250     * Handler for more complex cases
2251     */
2252    c = *cur;
2253    if ((!IS_LETTER(c) && (c != '_') && (c != ':'))) {
2254	return(NULL);
2255    }
2256
2257    while (((IS_LETTER(c)) || (IS_DIGIT(c)) ||
2258            (c == '.') || (c == '-') ||
2259	    (c == '_') || (c == ':'))) {
2260	buf[len++] = c;
2261	cur++;
2262	c = *cur;
2263	if (len >= XML_MAX_NAMELEN)
2264	    return(NULL);
2265    }
2266    *name = xmlStrndup(buf, len);
2267    return(cur);
2268}
2269
2270/**
2271 * xmlGetSGMLCatalogEntryType:
2272 * @name:  the entry name
2273 *
2274 * Get the Catalog entry type for a given SGML Catalog name
2275 *
2276 * Returns Catalog entry type
2277 */
2278static xmlCatalogEntryType
2279xmlGetSGMLCatalogEntryType(const xmlChar *name) {
2280    xmlCatalogEntryType type = XML_CATA_NONE;
2281    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
2282	type = SGML_CATA_SYSTEM;
2283    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
2284	type = SGML_CATA_PUBLIC;
2285    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
2286	type = SGML_CATA_DELEGATE;
2287    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
2288	type = SGML_CATA_ENTITY;
2289    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
2290	type = SGML_CATA_DOCTYPE;
2291    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
2292	type = SGML_CATA_LINKTYPE;
2293    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
2294	type = SGML_CATA_NOTATION;
2295    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
2296	type = SGML_CATA_SGMLDECL;
2297    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
2298	type = SGML_CATA_DOCUMENT;
2299    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
2300	type = SGML_CATA_CATALOG;
2301    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
2302	type = SGML_CATA_BASE;
2303    return(type);
2304}
2305
2306/**
2307 * xmlParseSGMLCatalog:
2308 * @catal:  the SGML Catalog
2309 * @value:  the content of the SGML Catalog serialization
2310 * @file:  the filepath for the catalog
2311 * @super:  should this be handled as a Super Catalog in which case
2312 *          parsing is not recursive
2313 *
2314 * Parse an SGML catalog content and fill up the @catal hash table with
2315 * the new entries found.
2316 *
2317 * Returns 0 in case of success, -1 in case of error.
2318 */
2319static int
2320xmlParseSGMLCatalog(xmlCatalogPtr catal, const xmlChar *value,
2321	            const char *file, int super) {
2322    const xmlChar *cur = value;
2323    xmlChar *base = NULL;
2324    int res;
2325
2326    if ((cur == NULL) || (file == NULL))
2327        return(-1);
2328    base = xmlStrdup((const xmlChar *) file);
2329
2330    while ((cur != NULL) && (cur[0] != 0)) {
2331	SKIP_BLANKS;
2332	if (cur[0] == 0)
2333	    break;
2334	if ((cur[0] == '-') && (cur[1] == '-')) {
2335	    cur = xmlParseSGMLCatalogComment(cur);
2336	    if (cur == NULL) {
2337		/* error */
2338		break;
2339	    }
2340	} else {
2341	    xmlChar *sysid = NULL;
2342	    xmlChar *name = NULL;
2343	    xmlCatalogEntryType type = XML_CATA_NONE;
2344
2345	    cur = xmlParseSGMLCatalogName(cur, &name);
2346	    if (name == NULL) {
2347		/* error */
2348		break;
2349	    }
2350	    if (!IS_BLANK_CH(*cur)) {
2351		/* error */
2352		break;
2353	    }
2354	    SKIP_BLANKS;
2355	    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
2356                type = SGML_CATA_SYSTEM;
2357	    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
2358                type = SGML_CATA_PUBLIC;
2359	    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
2360                type = SGML_CATA_DELEGATE;
2361	    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
2362                type = SGML_CATA_ENTITY;
2363	    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
2364                type = SGML_CATA_DOCTYPE;
2365	    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
2366                type = SGML_CATA_LINKTYPE;
2367	    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
2368                type = SGML_CATA_NOTATION;
2369	    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
2370                type = SGML_CATA_SGMLDECL;
2371	    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
2372                type = SGML_CATA_DOCUMENT;
2373	    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
2374                type = SGML_CATA_CATALOG;
2375	    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
2376                type = SGML_CATA_BASE;
2377	    else if (xmlStrEqual(name, (const xmlChar *) "OVERRIDE")) {
2378		xmlFree(name);
2379		cur = xmlParseSGMLCatalogName(cur, &name);
2380		if (name == NULL) {
2381		    /* error */
2382		    break;
2383		}
2384		xmlFree(name);
2385		continue;
2386	    }
2387	    xmlFree(name);
2388	    name = NULL;
2389
2390	    switch(type) {
2391		case SGML_CATA_ENTITY:
2392		    if (*cur == '%')
2393			type = SGML_CATA_PENTITY;
2394		case SGML_CATA_PENTITY:
2395		case SGML_CATA_DOCTYPE:
2396		case SGML_CATA_LINKTYPE:
2397		case SGML_CATA_NOTATION:
2398		    cur = xmlParseSGMLCatalogName(cur, &name);
2399		    if (cur == NULL) {
2400			/* error */
2401			break;
2402		    }
2403		    if (!IS_BLANK_CH(*cur)) {
2404			/* error */
2405			break;
2406		    }
2407		    SKIP_BLANKS;
2408		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
2409		    if (cur == NULL) {
2410			/* error */
2411			break;
2412		    }
2413		    break;
2414		case SGML_CATA_PUBLIC:
2415		case SGML_CATA_SYSTEM:
2416		case SGML_CATA_DELEGATE:
2417		    cur = xmlParseSGMLCatalogPubid(cur, &name);
2418		    if (cur == NULL) {
2419			/* error */
2420			break;
2421		    }
2422		    if (type != SGML_CATA_SYSTEM) {
2423		        xmlChar *normid;
2424
2425		        normid = xmlCatalogNormalizePublic(name);
2426		        if (normid != NULL) {
2427		            if (name != NULL)
2428		                xmlFree(name);
2429		            if (*normid != 0)
2430		                name = normid;
2431		            else {
2432		                xmlFree(normid);
2433		                name = NULL;
2434		            }
2435		        }
2436		    }
2437		    if (!IS_BLANK_CH(*cur)) {
2438			/* error */
2439			break;
2440		    }
2441		    SKIP_BLANKS;
2442		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
2443		    if (cur == NULL) {
2444			/* error */
2445			break;
2446		    }
2447		    break;
2448		case SGML_CATA_BASE:
2449		case SGML_CATA_CATALOG:
2450		case SGML_CATA_DOCUMENT:
2451		case SGML_CATA_SGMLDECL:
2452		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
2453		    if (cur == NULL) {
2454			/* error */
2455			break;
2456		    }
2457		    break;
2458		default:
2459		    break;
2460	    }
2461	    if (cur == NULL) {
2462		if (name != NULL)
2463		    xmlFree(name);
2464		if (sysid != NULL)
2465		    xmlFree(sysid);
2466		break;
2467	    } else if (type == SGML_CATA_BASE) {
2468		if (base != NULL)
2469		    xmlFree(base);
2470		base = xmlStrdup(sysid);
2471	    } else if ((type == SGML_CATA_PUBLIC) ||
2472		       (type == SGML_CATA_SYSTEM)) {
2473		xmlChar *filename;
2474
2475		filename = xmlBuildURI(sysid, base);
2476		if (filename != NULL) {
2477		    xmlCatalogEntryPtr entry;
2478
2479		    entry = xmlNewCatalogEntry(type, name, filename,
2480			                       NULL, XML_CATA_PREFER_NONE, NULL);
2481		    res = xmlHashAddEntry(catal->sgml, name, entry);
2482		    if (res < 0) {
2483			xmlFreeCatalogEntry(entry);
2484		    }
2485		    xmlFree(filename);
2486		}
2487
2488	    } else if (type == SGML_CATA_CATALOG) {
2489		if (super) {
2490		    xmlCatalogEntryPtr entry;
2491
2492		    entry = xmlNewCatalogEntry(type, sysid, NULL, NULL,
2493			                       XML_CATA_PREFER_NONE, NULL);
2494		    res = xmlHashAddEntry(catal->sgml, sysid, entry);
2495		    if (res < 0) {
2496			xmlFreeCatalogEntry(entry);
2497		    }
2498		} else {
2499		    xmlChar *filename;
2500
2501		    filename = xmlBuildURI(sysid, base);
2502		    if (filename != NULL) {
2503			xmlExpandCatalog(catal, (const char *)filename);
2504			xmlFree(filename);
2505		    }
2506		}
2507	    }
2508	    /*
2509	     * drop anything else we won't handle it
2510	     */
2511	    if (name != NULL)
2512		xmlFree(name);
2513	    if (sysid != NULL)
2514		xmlFree(sysid);
2515	}
2516    }
2517    if (base != NULL)
2518	xmlFree(base);
2519    if (cur == NULL)
2520	return(-1);
2521    return(0);
2522}
2523
2524/************************************************************************
2525 *									*
2526 *			SGML Catalog handling				*
2527 *									*
2528 ************************************************************************/
2529
2530/**
2531 * xmlCatalogGetSGMLPublic:
2532 * @catal:  an SGML catalog hash
2533 * @pubID:  the public ID string
2534 *
2535 * Try to lookup the catalog local reference associated to a public ID
2536 *
2537 * Returns the local resource if found or NULL otherwise.
2538 */
2539static const xmlChar *
2540xmlCatalogGetSGMLPublic(xmlHashTablePtr catal, const xmlChar *pubID) {
2541    xmlCatalogEntryPtr entry;
2542    xmlChar *normid;
2543
2544    if (catal == NULL)
2545	return(NULL);
2546
2547    normid = xmlCatalogNormalizePublic(pubID);
2548    if (normid != NULL)
2549        pubID = (*normid != 0 ? normid : NULL);
2550
2551    entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, pubID);
2552    if (entry == NULL) {
2553	if (normid != NULL)
2554	    xmlFree(normid);
2555	return(NULL);
2556    }
2557    if (entry->type == SGML_CATA_PUBLIC) {
2558	if (normid != NULL)
2559	    xmlFree(normid);
2560	return(entry->URL);
2561    }
2562    if (normid != NULL)
2563        xmlFree(normid);
2564    return(NULL);
2565}
2566
2567/**
2568 * xmlCatalogGetSGMLSystem:
2569 * @catal:  an SGML catalog hash
2570 * @sysID:  the system ID string
2571 *
2572 * Try to lookup the catalog local reference for a system ID
2573 *
2574 * Returns the local resource if found or NULL otherwise.
2575 */
2576static const xmlChar *
2577xmlCatalogGetSGMLSystem(xmlHashTablePtr catal, const xmlChar *sysID) {
2578    xmlCatalogEntryPtr entry;
2579
2580    if (catal == NULL)
2581	return(NULL);
2582
2583    entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, sysID);
2584    if (entry == NULL)
2585	return(NULL);
2586    if (entry->type == SGML_CATA_SYSTEM)
2587	return(entry->URL);
2588    return(NULL);
2589}
2590
2591/**
2592 * xmlCatalogSGMLResolve:
2593 * @catal:  the SGML catalog
2594 * @pubID:  the public ID string
2595 * @sysID:  the system ID string
2596 *
2597 * Do a complete resolution lookup of an External Identifier
2598 *
2599 * Returns the URI of the resource or NULL if not found
2600 */
2601static const xmlChar *
2602xmlCatalogSGMLResolve(xmlCatalogPtr catal, const xmlChar *pubID,
2603	              const xmlChar *sysID) {
2604    const xmlChar *ret = NULL;
2605
2606    if (catal->sgml == NULL)
2607	return(NULL);
2608
2609    if (pubID != NULL)
2610	ret = xmlCatalogGetSGMLPublic(catal->sgml, pubID);
2611    if (ret != NULL)
2612	return(ret);
2613    if (sysID != NULL)
2614	ret = xmlCatalogGetSGMLSystem(catal->sgml, sysID);
2615    if (ret != NULL)
2616	return(ret);
2617    return(NULL);
2618}
2619
2620/************************************************************************
2621 *									*
2622 *			Specific Public interfaces			*
2623 *									*
2624 ************************************************************************/
2625
2626/**
2627 * xmlLoadSGMLSuperCatalog:
2628 * @filename:  a file path
2629 *
2630 * Load an SGML super catalog. It won't expand CATALOG or DELEGATE
2631 * references. This is only needed for manipulating SGML Super Catalogs
2632 * like adding and removing CATALOG or DELEGATE entries.
2633 *
2634 * Returns the catalog parsed or NULL in case of error
2635 */
2636xmlCatalogPtr
2637xmlLoadSGMLSuperCatalog(const char *filename)
2638{
2639    xmlChar *content;
2640    xmlCatalogPtr catal;
2641    int ret;
2642
2643    content = xmlLoadFileContent(filename);
2644    if (content == NULL)
2645        return(NULL);
2646
2647    catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer);
2648    if (catal == NULL) {
2649	xmlFree(content);
2650	return(NULL);
2651    }
2652
2653    ret = xmlParseSGMLCatalog(catal, content, filename, 1);
2654    xmlFree(content);
2655    if (ret < 0) {
2656	xmlFreeCatalog(catal);
2657	return(NULL);
2658    }
2659    return (catal);
2660}
2661
2662/**
2663 * xmlLoadACatalog:
2664 * @filename:  a file path
2665 *
2666 * Load the catalog and build the associated data structures.
2667 * This can be either an XML Catalog or an SGML Catalog
2668 * It will recurse in SGML CATALOG entries. On the other hand XML
2669 * Catalogs are not handled recursively.
2670 *
2671 * Returns the catalog parsed or NULL in case of error
2672 */
2673xmlCatalogPtr
2674xmlLoadACatalog(const char *filename)
2675{
2676    xmlChar *content;
2677    xmlChar *first;
2678    xmlCatalogPtr catal;
2679    int ret;
2680
2681    content = xmlLoadFileContent(filename);
2682    if (content == NULL)
2683        return(NULL);
2684
2685
2686    first = content;
2687
2688    while ((*first != 0) && (*first != '-') && (*first != '<') &&
2689	   (!(((*first >= 'A') && (*first <= 'Z')) ||
2690	      ((*first >= 'a') && (*first <= 'z')))))
2691	first++;
2692
2693    if (*first != '<') {
2694	catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer);
2695	if (catal == NULL) {
2696	    xmlFree(content);
2697	    return(NULL);
2698	}
2699        ret = xmlParseSGMLCatalog(catal, content, filename, 0);
2700	if (ret < 0) {
2701	    xmlFreeCatalog(catal);
2702	    xmlFree(content);
2703	    return(NULL);
2704	}
2705    } else {
2706	catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE, xmlCatalogDefaultPrefer);
2707	if (catal == NULL) {
2708	    xmlFree(content);
2709	    return(NULL);
2710	}
2711        catal->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
2712		       NULL, BAD_CAST filename, xmlCatalogDefaultPrefer, NULL);
2713    }
2714    xmlFree(content);
2715    return (catal);
2716}
2717
2718/**
2719 * xmlExpandCatalog:
2720 * @catal:  a catalog
2721 * @filename:  a file path
2722 *
2723 * Load the catalog and expand the existing catal structure.
2724 * This can be either an XML Catalog or an SGML Catalog
2725 *
2726 * Returns 0 in case of success, -1 in case of error
2727 */
2728static int
2729xmlExpandCatalog(xmlCatalogPtr catal, const char *filename)
2730{
2731    int ret;
2732
2733    if ((catal == NULL) || (filename == NULL))
2734	return(-1);
2735
2736
2737    if (catal->type == XML_SGML_CATALOG_TYPE) {
2738	xmlChar *content;
2739
2740	content = xmlLoadFileContent(filename);
2741	if (content == NULL)
2742	    return(-1);
2743
2744        ret = xmlParseSGMLCatalog(catal, content, filename, 0);
2745	if (ret < 0) {
2746	    xmlFree(content);
2747	    return(-1);
2748	}
2749	xmlFree(content);
2750    } else {
2751	xmlCatalogEntryPtr tmp, cur;
2752	tmp = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
2753		       NULL, BAD_CAST filename, xmlCatalogDefaultPrefer, NULL);
2754
2755	cur = catal->xml;
2756	if (cur == NULL) {
2757	    catal->xml = tmp;
2758	} else {
2759	    while (cur->next != NULL) cur = cur->next;
2760	    cur->next = tmp;
2761	}
2762    }
2763    return (0);
2764}
2765
2766/**
2767 * xmlACatalogResolveSystem:
2768 * @catal:  a Catalog
2769 * @sysID:  the system ID string
2770 *
2771 * Try to lookup the catalog resource for a system ID
2772 *
2773 * Returns the resource if found or NULL otherwise, the value returned
2774 *      must be freed by the caller.
2775 */
2776xmlChar *
2777xmlACatalogResolveSystem(xmlCatalogPtr catal, const xmlChar *sysID) {
2778    xmlChar *ret = NULL;
2779
2780    if ((sysID == NULL) || (catal == NULL))
2781	return(NULL);
2782
2783    if (xmlDebugCatalogs)
2784	xmlGenericError(xmlGenericErrorContext,
2785		"Resolve sysID %s\n", sysID);
2786
2787    if (catal->type == XML_XML_CATALOG_TYPE) {
2788	ret = xmlCatalogListXMLResolve(catal->xml, NULL, sysID);
2789	if (ret == XML_CATAL_BREAK)
2790	    ret = NULL;
2791    } else {
2792	const xmlChar *sgml;
2793
2794	sgml = xmlCatalogGetSGMLSystem(catal->sgml, sysID);
2795	if (sgml != NULL)
2796	    ret = xmlStrdup(sgml);
2797    }
2798    return(ret);
2799}
2800
2801/**
2802 * xmlACatalogResolvePublic:
2803 * @catal:  a Catalog
2804 * @pubID:  the public ID string
2805 *
2806 * Try to lookup the catalog local reference associated to a public ID in that catalog
2807 *
2808 * Returns the local resource if found or NULL otherwise, the value returned
2809 *      must be freed by the caller.
2810 */
2811xmlChar *
2812xmlACatalogResolvePublic(xmlCatalogPtr catal, const xmlChar *pubID) {
2813    xmlChar *ret = NULL;
2814
2815    if ((pubID == NULL) || (catal == NULL))
2816	return(NULL);
2817
2818    if (xmlDebugCatalogs)
2819	xmlGenericError(xmlGenericErrorContext,
2820		"Resolve pubID %s\n", pubID);
2821
2822    if (catal->type == XML_XML_CATALOG_TYPE) {
2823	ret = xmlCatalogListXMLResolve(catal->xml, pubID, NULL);
2824	if (ret == XML_CATAL_BREAK)
2825	    ret = NULL;
2826    } else {
2827	const xmlChar *sgml;
2828
2829	sgml = xmlCatalogGetSGMLPublic(catal->sgml, pubID);
2830	if (sgml != NULL)
2831	    ret = xmlStrdup(sgml);
2832    }
2833    return(ret);
2834}
2835
2836/**
2837 * xmlACatalogResolve:
2838 * @catal:  a Catalog
2839 * @pubID:  the public ID string
2840 * @sysID:  the system ID string
2841 *
2842 * Do a complete resolution lookup of an External Identifier
2843 *
2844 * Returns the URI of the resource or NULL if not found, it must be freed
2845 *      by the caller.
2846 */
2847xmlChar *
2848xmlACatalogResolve(xmlCatalogPtr catal, const xmlChar * pubID,
2849                   const xmlChar * sysID)
2850{
2851    xmlChar *ret = NULL;
2852
2853    if (((pubID == NULL) && (sysID == NULL)) || (catal == NULL))
2854        return (NULL);
2855
2856    if (xmlDebugCatalogs) {
2857         if ((pubID != NULL) && (sysID != NULL)) {
2858             xmlGenericError(xmlGenericErrorContext,
2859                             "Resolve: pubID %s sysID %s\n", pubID, sysID);
2860         } else if (pubID != NULL) {
2861             xmlGenericError(xmlGenericErrorContext,
2862                             "Resolve: pubID %s\n", pubID);
2863         } else {
2864             xmlGenericError(xmlGenericErrorContext,
2865                             "Resolve: sysID %s\n", sysID);
2866         }
2867    }
2868
2869    if (catal->type == XML_XML_CATALOG_TYPE) {
2870        ret = xmlCatalogListXMLResolve(catal->xml, pubID, sysID);
2871	if (ret == XML_CATAL_BREAK)
2872	    ret = NULL;
2873    } else {
2874        const xmlChar *sgml;
2875
2876        sgml = xmlCatalogSGMLResolve(catal, pubID, sysID);
2877        if (sgml != NULL)
2878            ret = xmlStrdup(sgml);
2879    }
2880    return (ret);
2881}
2882
2883/**
2884 * xmlACatalogResolveURI:
2885 * @catal:  a Catalog
2886 * @URI:  the URI
2887 *
2888 * Do a complete resolution lookup of an URI
2889 *
2890 * Returns the URI of the resource or NULL if not found, it must be freed
2891 *      by the caller.
2892 */
2893xmlChar *
2894xmlACatalogResolveURI(xmlCatalogPtr catal, const xmlChar *URI) {
2895    xmlChar *ret = NULL;
2896
2897    if ((URI == NULL) || (catal == NULL))
2898	return(NULL);
2899
2900    if (xmlDebugCatalogs)
2901	xmlGenericError(xmlGenericErrorContext,
2902		"Resolve URI %s\n", URI);
2903
2904    if (catal->type == XML_XML_CATALOG_TYPE) {
2905	ret = xmlCatalogListXMLResolveURI(catal->xml, URI);
2906	if (ret == XML_CATAL_BREAK)
2907	    ret = NULL;
2908    } else {
2909	const xmlChar *sgml;
2910
2911	sgml = xmlCatalogSGMLResolve(catal, NULL, URI);
2912	if (sgml != NULL)
2913            ret = xmlStrdup(sgml);
2914    }
2915    return(ret);
2916}
2917
2918#ifdef LIBXML_OUTPUT_ENABLED
2919/**
2920 * xmlACatalogDump:
2921 * @catal:  a Catalog
2922 * @out:  the file.
2923 *
2924 * Dump the given catalog to the given file.
2925 */
2926void
2927xmlACatalogDump(xmlCatalogPtr catal, FILE *out) {
2928    if ((out == NULL) || (catal == NULL))
2929	return;
2930
2931    if (catal->type == XML_XML_CATALOG_TYPE) {
2932	xmlDumpXMLCatalog(out, catal->xml);
2933    } else {
2934	xmlHashScan(catal->sgml,
2935		    (xmlHashScanner) xmlCatalogDumpEntry, out);
2936    }
2937}
2938#endif /* LIBXML_OUTPUT_ENABLED */
2939
2940/**
2941 * xmlACatalogAdd:
2942 * @catal:  a Catalog
2943 * @type:  the type of record to add to the catalog
2944 * @orig:  the system, public or prefix to match
2945 * @replace:  the replacement value for the match
2946 *
2947 * Add an entry in the catalog, it may overwrite existing but
2948 * different entries.
2949 *
2950 * Returns 0 if successful, -1 otherwise
2951 */
2952int
2953xmlACatalogAdd(xmlCatalogPtr catal, const xmlChar * type,
2954              const xmlChar * orig, const xmlChar * replace)
2955{
2956    int res = -1;
2957
2958    if (catal == NULL)
2959	return(-1);
2960
2961    if (catal->type == XML_XML_CATALOG_TYPE) {
2962        res = xmlAddXMLCatalog(catal->xml, type, orig, replace);
2963    } else {
2964        xmlCatalogEntryType cattype;
2965
2966        cattype = xmlGetSGMLCatalogEntryType(type);
2967        if (cattype != XML_CATA_NONE) {
2968            xmlCatalogEntryPtr entry;
2969
2970            entry = xmlNewCatalogEntry(cattype, orig, replace, NULL,
2971                                       XML_CATA_PREFER_NONE, NULL);
2972	    if (catal->sgml == NULL)
2973		catal->sgml = xmlHashCreate(10);
2974            res = xmlHashAddEntry(catal->sgml, orig, entry);
2975        }
2976    }
2977    return (res);
2978}
2979
2980/**
2981 * xmlACatalogRemove:
2982 * @catal:  a Catalog
2983 * @value:  the value to remove
2984 *
2985 * Remove an entry from the catalog
2986 *
2987 * Returns the number of entries removed if successful, -1 otherwise
2988 */
2989int
2990xmlACatalogRemove(xmlCatalogPtr catal, const xmlChar *value) {
2991    int res = -1;
2992
2993    if ((catal == NULL) || (value == NULL))
2994	return(-1);
2995
2996    if (catal->type == XML_XML_CATALOG_TYPE) {
2997	res = xmlDelXMLCatalog(catal->xml, value);
2998    } else {
2999	res = xmlHashRemoveEntry(catal->sgml, value,
3000		(xmlHashDeallocator) xmlFreeCatalogEntry);
3001	if (res == 0)
3002	    res = 1;
3003    }
3004    return(res);
3005}
3006
3007/**
3008 * xmlNewCatalog:
3009 * @sgml:  should this create an SGML catalog
3010 *
3011 * create a new Catalog.
3012 *
3013 * Returns the xmlCatalogPtr or NULL in case of error
3014 */
3015xmlCatalogPtr
3016xmlNewCatalog(int sgml) {
3017    xmlCatalogPtr catal = NULL;
3018
3019    if (sgml) {
3020	catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE,
3021		                    xmlCatalogDefaultPrefer);
3022        if ((catal != NULL) && (catal->sgml == NULL))
3023	    catal->sgml = xmlHashCreate(10);
3024    } else
3025	catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE,
3026		                    xmlCatalogDefaultPrefer);
3027    return(catal);
3028}
3029
3030/**
3031 * xmlCatalogIsEmpty:
3032 * @catal:  should this create an SGML catalog
3033 *
3034 * Check is a catalog is empty
3035 *
3036 * Returns 1 if the catalog is empty, 0 if not, amd -1 in case of error.
3037 */
3038int
3039xmlCatalogIsEmpty(xmlCatalogPtr catal) {
3040    if (catal == NULL)
3041	return(-1);
3042
3043    if (catal->type == XML_XML_CATALOG_TYPE) {
3044	if (catal->xml == NULL)
3045	    return(1);
3046	if ((catal->xml->type != XML_CATA_CATALOG) &&
3047	    (catal->xml->type != XML_CATA_BROKEN_CATALOG))
3048	    return(-1);
3049	if (catal->xml->children == NULL)
3050	    return(1);
3051        return(0);
3052    } else {
3053	int res;
3054
3055	if (catal->sgml == NULL)
3056	    return(1);
3057	res = xmlHashSize(catal->sgml);
3058	if (res == 0)
3059	    return(1);
3060	if (res < 0)
3061	    return(-1);
3062    }
3063    return(0);
3064}
3065
3066/************************************************************************
3067 *									*
3068 *   Public interfaces manipulating the global shared default catalog	*
3069 *									*
3070 ************************************************************************/
3071
3072/**
3073 * xmlInitializeCatalogData:
3074 *
3075 * Do the catalog initialization only of global data, doesn't try to load
3076 * any catalog actually.
3077 * this function is not thread safe, catalog initialization should
3078 * preferably be done once at startup
3079 */
3080static void
3081xmlInitializeCatalogData(void) {
3082    if (xmlCatalogInitialized != 0)
3083	return;
3084
3085    if (getenv("XML_DEBUG_CATALOG"))
3086	xmlDebugCatalogs = 1;
3087    xmlCatalogMutex = xmlNewRMutex();
3088
3089    xmlCatalogInitialized = 1;
3090}
3091/**
3092 * xmlInitializeCatalog:
3093 *
3094 * Do the catalog initialization.
3095 * this function is not thread safe, catalog initialization should
3096 * preferably be done once at startup
3097 */
3098void
3099xmlInitializeCatalog(void) {
3100    if (xmlCatalogInitialized != 0)
3101	return;
3102
3103    xmlInitializeCatalogData();
3104    xmlRMutexLock(xmlCatalogMutex);
3105
3106    if (getenv("XML_DEBUG_CATALOG"))
3107	xmlDebugCatalogs = 1;
3108
3109    if (xmlDefaultCatalog == NULL) {
3110	const char *catalogs;
3111	char *path;
3112	const char *cur, *paths;
3113	xmlCatalogPtr catal;
3114	xmlCatalogEntryPtr *nextent;
3115
3116	catalogs = (const char *) getenv("XML_CATALOG_FILES");
3117	if (catalogs == NULL)
3118#if defined(_WIN32) && defined(_MSC_VER)
3119    {
3120		void* hmodule;
3121		hmodule = GetModuleHandleA("libxml2.dll");
3122		if (hmodule == NULL)
3123			hmodule = GetModuleHandleA(NULL);
3124		if (hmodule != NULL) {
3125			char buf[256];
3126			unsigned long len = GetModuleFileNameA(hmodule, buf, 255);
3127			if (len != 0) {
3128				char* p = &(buf[len]);
3129				while (*p != '\\' && p > buf)
3130					p--;
3131				if (p != buf) {
3132					xmlChar* uri;
3133					strncpy(p, "\\..\\etc\\catalog", 255 - (p - buf));
3134					uri = xmlCanonicPath((const xmlChar*)buf);
3135					if (uri != NULL) {
3136						strncpy(XML_XML_DEFAULT_CATALOG, uri, 255);
3137						xmlFree(uri);
3138					}
3139				}
3140			}
3141		}
3142		catalogs = XML_XML_DEFAULT_CATALOG;
3143    }
3144#else
3145	    catalogs = XML_XML_DEFAULT_CATALOG;
3146#endif
3147
3148	catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE,
3149		xmlCatalogDefaultPrefer);
3150	if (catal != NULL) {
3151	    /* the XML_CATALOG_FILES envvar is allowed to contain a
3152	       space-separated list of entries. */
3153	    cur = catalogs;
3154	    nextent = &catal->xml;
3155	    while (*cur != '\0') {
3156		while (xmlIsBlank_ch(*cur))
3157		    cur++;
3158		if (*cur != 0) {
3159		    paths = cur;
3160		    while ((*cur != 0) && (!xmlIsBlank_ch(*cur)))
3161			cur++;
3162		    path = (char *) xmlStrndup((const xmlChar *)paths, cur - paths);
3163		    if (path != NULL) {
3164			*nextent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
3165				NULL, BAD_CAST path, xmlCatalogDefaultPrefer, NULL);
3166			if (*nextent != NULL)
3167			    nextent = &((*nextent)->next);
3168			xmlFree(path);
3169		    }
3170		}
3171	    }
3172	    xmlDefaultCatalog = catal;
3173	}
3174    }
3175
3176    xmlRMutexUnlock(xmlCatalogMutex);
3177}
3178
3179
3180/**
3181 * xmlLoadCatalog:
3182 * @filename:  a file path
3183 *
3184 * Load the catalog and makes its definitions effective for the default
3185 * external entity loader. It will recurse in SGML CATALOG entries.
3186 * this function is not thread safe, catalog initialization should
3187 * preferably be done once at startup
3188 *
3189 * Returns 0 in case of success -1 in case of error
3190 */
3191int
3192xmlLoadCatalog(const char *filename)
3193{
3194    int ret;
3195    xmlCatalogPtr catal;
3196
3197    if (!xmlCatalogInitialized)
3198	xmlInitializeCatalogData();
3199
3200    xmlRMutexLock(xmlCatalogMutex);
3201
3202    if (xmlDefaultCatalog == NULL) {
3203	catal = xmlLoadACatalog(filename);
3204	if (catal == NULL) {
3205	    xmlRMutexUnlock(xmlCatalogMutex);
3206	    return(-1);
3207	}
3208
3209	xmlDefaultCatalog = catal;
3210	xmlRMutexUnlock(xmlCatalogMutex);
3211	return(0);
3212    }
3213
3214    ret = xmlExpandCatalog(xmlDefaultCatalog, filename);
3215    xmlRMutexUnlock(xmlCatalogMutex);
3216    return(ret);
3217}
3218
3219/**
3220 * xmlLoadCatalogs:
3221 * @pathss:  a list of directories separated by a colon or a space.
3222 *
3223 * Load the catalogs and makes their definitions effective for the default
3224 * external entity loader.
3225 * this function is not thread safe, catalog initialization should
3226 * preferably be done once at startup
3227 */
3228void
3229xmlLoadCatalogs(const char *pathss) {
3230    const char *cur;
3231    const char *paths;
3232    xmlChar *path;
3233#ifdef _WIN32
3234    int i, iLen;
3235#endif
3236
3237    if (pathss == NULL)
3238	return;
3239
3240    cur = pathss;
3241    while (*cur != 0) {
3242	while (xmlIsBlank_ch(*cur)) cur++;
3243	if (*cur != 0) {
3244	    paths = cur;
3245	    while ((*cur != 0) && (*cur != PATH_SEAPARATOR) && (!xmlIsBlank_ch(*cur)))
3246		cur++;
3247	    path = xmlStrndup((const xmlChar *)paths, cur - paths);
3248#ifdef _WIN32
3249        iLen = strlen((const char*)path);
3250        for(i = 0; i < iLen; i++) {
3251            if(path[i] == '\\') {
3252                path[i] = '/';
3253            }
3254        }
3255#endif
3256	    if (path != NULL) {
3257		xmlLoadCatalog((const char *) path);
3258		xmlFree(path);
3259	    }
3260	}
3261	while (*cur == PATH_SEAPARATOR)
3262	    cur++;
3263    }
3264}
3265
3266/**
3267 * xmlCatalogCleanup:
3268 *
3269 * Free up all the memory associated with catalogs
3270 */
3271void
3272xmlCatalogCleanup(void) {
3273    if (xmlCatalogInitialized == 0)
3274        return;
3275
3276    xmlRMutexLock(xmlCatalogMutex);
3277    if (xmlDebugCatalogs)
3278	xmlGenericError(xmlGenericErrorContext,
3279		"Catalogs cleanup\n");
3280    if (xmlCatalogXMLFiles != NULL)
3281	xmlHashFree(xmlCatalogXMLFiles,
3282		    (xmlHashDeallocator)xmlFreeCatalogHashEntryList);
3283    xmlCatalogXMLFiles = NULL;
3284    if (xmlDefaultCatalog != NULL)
3285	xmlFreeCatalog(xmlDefaultCatalog);
3286    xmlDefaultCatalog = NULL;
3287    xmlDebugCatalogs = 0;
3288    xmlCatalogInitialized = 0;
3289    xmlRMutexUnlock(xmlCatalogMutex);
3290    xmlFreeRMutex(xmlCatalogMutex);
3291}
3292
3293/**
3294 * xmlCatalogResolveSystem:
3295 * @sysID:  the system ID string
3296 *
3297 * Try to lookup the catalog resource for a system ID
3298 *
3299 * Returns the resource if found or NULL otherwise, the value returned
3300 *      must be freed by the caller.
3301 */
3302xmlChar *
3303xmlCatalogResolveSystem(const xmlChar *sysID) {
3304    xmlChar *ret;
3305
3306    if (!xmlCatalogInitialized)
3307	xmlInitializeCatalog();
3308
3309    ret = xmlACatalogResolveSystem(xmlDefaultCatalog, sysID);
3310    return(ret);
3311}
3312
3313/**
3314 * xmlCatalogResolvePublic:
3315 * @pubID:  the public ID string
3316 *
3317 * Try to lookup the catalog reference associated to a public ID
3318 *
3319 * Returns the resource if found or NULL otherwise, the value returned
3320 *      must be freed by the caller.
3321 */
3322xmlChar *
3323xmlCatalogResolvePublic(const xmlChar *pubID) {
3324    xmlChar *ret;
3325
3326    if (!xmlCatalogInitialized)
3327	xmlInitializeCatalog();
3328
3329    ret = xmlACatalogResolvePublic(xmlDefaultCatalog, pubID);
3330    return(ret);
3331}
3332
3333/**
3334 * xmlCatalogResolve:
3335 * @pubID:  the public ID string
3336 * @sysID:  the system ID string
3337 *
3338 * Do a complete resolution lookup of an External Identifier
3339 *
3340 * Returns the URI of the resource or NULL if not found, it must be freed
3341 *      by the caller.
3342 */
3343xmlChar *
3344xmlCatalogResolve(const xmlChar *pubID, const xmlChar *sysID) {
3345    xmlChar *ret;
3346
3347    if (!xmlCatalogInitialized)
3348	xmlInitializeCatalog();
3349
3350    ret = xmlACatalogResolve(xmlDefaultCatalog, pubID, sysID);
3351    return(ret);
3352}
3353
3354/**
3355 * xmlCatalogResolveURI:
3356 * @URI:  the URI
3357 *
3358 * Do a complete resolution lookup of an URI
3359 *
3360 * Returns the URI of the resource or NULL if not found, it must be freed
3361 *      by the caller.
3362 */
3363xmlChar *
3364xmlCatalogResolveURI(const xmlChar *URI) {
3365    xmlChar *ret;
3366
3367    if (!xmlCatalogInitialized)
3368	xmlInitializeCatalog();
3369
3370    ret = xmlACatalogResolveURI(xmlDefaultCatalog, URI);
3371    return(ret);
3372}
3373
3374#ifdef LIBXML_OUTPUT_ENABLED
3375/**
3376 * xmlCatalogDump:
3377 * @out:  the file.
3378 *
3379 * Dump all the global catalog content to the given file.
3380 */
3381void
3382xmlCatalogDump(FILE *out) {
3383    if (out == NULL)
3384	return;
3385
3386    if (!xmlCatalogInitialized)
3387	xmlInitializeCatalog();
3388
3389    xmlACatalogDump(xmlDefaultCatalog, out);
3390}
3391#endif /* LIBXML_OUTPUT_ENABLED */
3392
3393/**
3394 * xmlCatalogAdd:
3395 * @type:  the type of record to add to the catalog
3396 * @orig:  the system, public or prefix to match
3397 * @replace:  the replacement value for the match
3398 *
3399 * Add an entry in the catalog, it may overwrite existing but
3400 * different entries.
3401 * If called before any other catalog routine, allows to override the
3402 * default shared catalog put in place by xmlInitializeCatalog();
3403 *
3404 * Returns 0 if successful, -1 otherwise
3405 */
3406int
3407xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace) {
3408    int res = -1;
3409
3410    if (!xmlCatalogInitialized)
3411	xmlInitializeCatalogData();
3412
3413    xmlRMutexLock(xmlCatalogMutex);
3414    /*
3415     * Specific case where one want to override the default catalog
3416     * put in place by xmlInitializeCatalog();
3417     */
3418    if ((xmlDefaultCatalog == NULL) &&
3419	(xmlStrEqual(type, BAD_CAST "catalog"))) {
3420	xmlDefaultCatalog = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE,
3421		                          xmlCatalogDefaultPrefer);
3422	xmlDefaultCatalog->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
3423				    orig, NULL,  xmlCatalogDefaultPrefer, NULL);
3424
3425	xmlRMutexUnlock(xmlCatalogMutex);
3426	return(0);
3427    }
3428
3429    res = xmlACatalogAdd(xmlDefaultCatalog, type, orig, replace);
3430    xmlRMutexUnlock(xmlCatalogMutex);
3431    return(res);
3432}
3433
3434/**
3435 * xmlCatalogRemove:
3436 * @value:  the value to remove
3437 *
3438 * Remove an entry from the catalog
3439 *
3440 * Returns the number of entries removed if successful, -1 otherwise
3441 */
3442int
3443xmlCatalogRemove(const xmlChar *value) {
3444    int res;
3445
3446    if (!xmlCatalogInitialized)
3447	xmlInitializeCatalog();
3448
3449    xmlRMutexLock(xmlCatalogMutex);
3450    res = xmlACatalogRemove(xmlDefaultCatalog, value);
3451    xmlRMutexUnlock(xmlCatalogMutex);
3452    return(res);
3453}
3454
3455/**
3456 * xmlCatalogConvert:
3457 *
3458 * Convert all the SGML catalog entries as XML ones
3459 *
3460 * Returns the number of entries converted if successful, -1 otherwise
3461 */
3462int
3463xmlCatalogConvert(void) {
3464    int res = -1;
3465
3466    if (!xmlCatalogInitialized)
3467	xmlInitializeCatalog();
3468
3469    xmlRMutexLock(xmlCatalogMutex);
3470    res = xmlConvertSGMLCatalog(xmlDefaultCatalog);
3471    xmlRMutexUnlock(xmlCatalogMutex);
3472    return(res);
3473}
3474
3475/************************************************************************
3476 *									*
3477 *	Public interface manipulating the common preferences		*
3478 *									*
3479 ************************************************************************/
3480
3481/**
3482 * xmlCatalogGetDefaults:
3483 *
3484 * Used to get the user preference w.r.t. to what catalogs should
3485 * be accepted
3486 *
3487 * Returns the current xmlCatalogAllow value
3488 */
3489xmlCatalogAllow
3490xmlCatalogGetDefaults(void) {
3491    return(xmlCatalogDefaultAllow);
3492}
3493
3494/**
3495 * xmlCatalogSetDefaults:
3496 * @allow:  what catalogs should be accepted
3497 *
3498 * Used to set the user preference w.r.t. to what catalogs should
3499 * be accepted
3500 */
3501void
3502xmlCatalogSetDefaults(xmlCatalogAllow allow) {
3503    if (xmlDebugCatalogs) {
3504	switch (allow) {
3505	    case XML_CATA_ALLOW_NONE:
3506		xmlGenericError(xmlGenericErrorContext,
3507			"Disabling catalog usage\n");
3508		break;
3509	    case XML_CATA_ALLOW_GLOBAL:
3510		xmlGenericError(xmlGenericErrorContext,
3511			"Allowing only global catalogs\n");
3512		break;
3513	    case XML_CATA_ALLOW_DOCUMENT:
3514		xmlGenericError(xmlGenericErrorContext,
3515			"Allowing only catalogs from the document\n");
3516		break;
3517	    case XML_CATA_ALLOW_ALL:
3518		xmlGenericError(xmlGenericErrorContext,
3519			"Allowing all catalogs\n");
3520		break;
3521	}
3522    }
3523    xmlCatalogDefaultAllow = allow;
3524}
3525
3526/**
3527 * xmlCatalogSetDefaultPrefer:
3528 * @prefer:  the default preference for delegation
3529 *
3530 * Allows to set the preference between public and system for deletion
3531 * in XML Catalog resolution. C.f. section 4.1.1 of the spec
3532 * Values accepted are XML_CATA_PREFER_PUBLIC or XML_CATA_PREFER_SYSTEM
3533 *
3534 * Returns the previous value of the default preference for delegation
3535 */
3536xmlCatalogPrefer
3537xmlCatalogSetDefaultPrefer(xmlCatalogPrefer prefer) {
3538    xmlCatalogPrefer ret = xmlCatalogDefaultPrefer;
3539
3540    if (prefer == XML_CATA_PREFER_NONE)
3541	return(ret);
3542
3543    if (xmlDebugCatalogs) {
3544	switch (prefer) {
3545	    case XML_CATA_PREFER_PUBLIC:
3546		xmlGenericError(xmlGenericErrorContext,
3547			"Setting catalog preference to PUBLIC\n");
3548		break;
3549	    case XML_CATA_PREFER_SYSTEM:
3550		xmlGenericError(xmlGenericErrorContext,
3551			"Setting catalog preference to SYSTEM\n");
3552		break;
3553	    case XML_CATA_PREFER_NONE:
3554		break;
3555	}
3556    }
3557    xmlCatalogDefaultPrefer = prefer;
3558    return(ret);
3559}
3560
3561/**
3562 * xmlCatalogSetDebug:
3563 * @level:  the debug level of catalogs required
3564 *
3565 * Used to set the debug level for catalog operation, 0 disable
3566 * debugging, 1 enable it
3567 *
3568 * Returns the previous value of the catalog debugging level
3569 */
3570int
3571xmlCatalogSetDebug(int level) {
3572    int ret = xmlDebugCatalogs;
3573
3574    if (level <= 0)
3575        xmlDebugCatalogs = 0;
3576    else
3577	xmlDebugCatalogs = level;
3578    return(ret);
3579}
3580
3581/************************************************************************
3582 *									*
3583 *   Minimal interfaces used for per-document catalogs by the parser	*
3584 *									*
3585 ************************************************************************/
3586
3587/**
3588 * xmlCatalogFreeLocal:
3589 * @catalogs:  a document's list of catalogs
3590 *
3591 * Free up the memory associated to the catalog list
3592 */
3593void
3594xmlCatalogFreeLocal(void *catalogs) {
3595    xmlCatalogEntryPtr catal;
3596
3597    if (!xmlCatalogInitialized)
3598	xmlInitializeCatalog();
3599
3600    catal = (xmlCatalogEntryPtr) catalogs;
3601    if (catal != NULL)
3602	xmlFreeCatalogEntryList(catal);
3603}
3604
3605
3606/**
3607 * xmlCatalogAddLocal:
3608 * @catalogs:  a document's list of catalogs
3609 * @URL:  the URL to a new local catalog
3610 *
3611 * Add the new entry to the catalog list
3612 *
3613 * Returns the updated list
3614 */
3615void *
3616xmlCatalogAddLocal(void *catalogs, const xmlChar *URL) {
3617    xmlCatalogEntryPtr catal, add;
3618
3619    if (!xmlCatalogInitialized)
3620	xmlInitializeCatalog();
3621
3622    if (URL == NULL)
3623	return(catalogs);
3624
3625    if (xmlDebugCatalogs)
3626	xmlGenericError(xmlGenericErrorContext,
3627		"Adding document catalog %s\n", URL);
3628
3629    add = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, URL, NULL,
3630	                     xmlCatalogDefaultPrefer, NULL);
3631    if (add == NULL)
3632	return(catalogs);
3633
3634    catal = (xmlCatalogEntryPtr) catalogs;
3635    if (catal == NULL)
3636	return((void *) add);
3637
3638    while (catal->next != NULL)
3639	catal = catal->next;
3640    catal->next = add;
3641    return(catalogs);
3642}
3643
3644/**
3645 * xmlCatalogLocalResolve:
3646 * @catalogs:  a document's list of catalogs
3647 * @pubID:  the public ID string
3648 * @sysID:  the system ID string
3649 *
3650 * Do a complete resolution lookup of an External Identifier using a
3651 * document's private catalog list
3652 *
3653 * Returns the URI of the resource or NULL if not found, it must be freed
3654 *      by the caller.
3655 */
3656xmlChar *
3657xmlCatalogLocalResolve(void *catalogs, const xmlChar *pubID,
3658	               const xmlChar *sysID) {
3659    xmlCatalogEntryPtr catal;
3660    xmlChar *ret;
3661
3662    if (!xmlCatalogInitialized)
3663	xmlInitializeCatalog();
3664
3665    if ((pubID == NULL) && (sysID == NULL))
3666	return(NULL);
3667
3668    if (xmlDebugCatalogs) {
3669        if ((pubID != NULL) && (sysID != NULL)) {
3670            xmlGenericError(xmlGenericErrorContext,
3671                            "Local Resolve: pubID %s sysID %s\n", pubID, sysID);
3672        } else if (pubID != NULL) {
3673            xmlGenericError(xmlGenericErrorContext,
3674                            "Local Resolve: pubID %s\n", pubID);
3675        } else {
3676            xmlGenericError(xmlGenericErrorContext,
3677                            "Local Resolve: sysID %s\n", sysID);
3678        }
3679    }
3680
3681    catal = (xmlCatalogEntryPtr) catalogs;
3682    if (catal == NULL)
3683	return(NULL);
3684    ret = xmlCatalogListXMLResolve(catal, pubID, sysID);
3685    if ((ret != NULL) && (ret != XML_CATAL_BREAK))
3686	return(ret);
3687    return(NULL);
3688}
3689
3690/**
3691 * xmlCatalogLocalResolveURI:
3692 * @catalogs:  a document's list of catalogs
3693 * @URI:  the URI
3694 *
3695 * Do a complete resolution lookup of an URI using a
3696 * document's private catalog list
3697 *
3698 * Returns the URI of the resource or NULL if not found, it must be freed
3699 *      by the caller.
3700 */
3701xmlChar *
3702xmlCatalogLocalResolveURI(void *catalogs, const xmlChar *URI) {
3703    xmlCatalogEntryPtr catal;
3704    xmlChar *ret;
3705
3706    if (!xmlCatalogInitialized)
3707	xmlInitializeCatalog();
3708
3709    if (URI == NULL)
3710	return(NULL);
3711
3712    if (xmlDebugCatalogs)
3713	xmlGenericError(xmlGenericErrorContext,
3714		"Resolve URI %s\n", URI);
3715
3716    catal = (xmlCatalogEntryPtr) catalogs;
3717    if (catal == NULL)
3718	return(NULL);
3719    ret = xmlCatalogListXMLResolveURI(catal, URI);
3720    if ((ret != NULL) && (ret != XML_CATAL_BREAK))
3721	return(ret);
3722    return(NULL);
3723}
3724
3725/************************************************************************
3726 *									*
3727 *			Deprecated interfaces				*
3728 *									*
3729 ************************************************************************/
3730/**
3731 * xmlCatalogGetSystem:
3732 * @sysID:  the system ID string
3733 *
3734 * Try to lookup the catalog reference associated to a system ID
3735 * DEPRECATED, use xmlCatalogResolveSystem()
3736 *
3737 * Returns the resource if found or NULL otherwise.
3738 */
3739const xmlChar *
3740xmlCatalogGetSystem(const xmlChar *sysID) {
3741    xmlChar *ret;
3742    static xmlChar result[1000];
3743    static int msg = 0;
3744
3745    if (!xmlCatalogInitialized)
3746	xmlInitializeCatalog();
3747
3748    if (msg == 0) {
3749	xmlGenericError(xmlGenericErrorContext,
3750		"Use of deprecated xmlCatalogGetSystem() call\n");
3751	msg++;
3752    }
3753
3754    if (sysID == NULL)
3755	return(NULL);
3756
3757    /*
3758     * Check first the XML catalogs
3759     */
3760    if (xmlDefaultCatalog != NULL) {
3761	ret = xmlCatalogListXMLResolve(xmlDefaultCatalog->xml, NULL, sysID);
3762	if ((ret != NULL) && (ret != XML_CATAL_BREAK)) {
3763	    snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret);
3764	    result[sizeof(result) - 1] = 0;
3765	    return(result);
3766	}
3767    }
3768
3769    if (xmlDefaultCatalog != NULL)
3770	return(xmlCatalogGetSGMLSystem(xmlDefaultCatalog->sgml, sysID));
3771    return(NULL);
3772}
3773
3774/**
3775 * xmlCatalogGetPublic:
3776 * @pubID:  the public ID string
3777 *
3778 * Try to lookup the catalog reference associated to a public ID
3779 * DEPRECATED, use xmlCatalogResolvePublic()
3780 *
3781 * Returns the resource if found or NULL otherwise.
3782 */
3783const xmlChar *
3784xmlCatalogGetPublic(const xmlChar *pubID) {
3785    xmlChar *ret;
3786    static xmlChar result[1000];
3787    static int msg = 0;
3788
3789    if (!xmlCatalogInitialized)
3790	xmlInitializeCatalog();
3791
3792    if (msg == 0) {
3793	xmlGenericError(xmlGenericErrorContext,
3794		"Use of deprecated xmlCatalogGetPublic() call\n");
3795	msg++;
3796    }
3797
3798    if (pubID == NULL)
3799	return(NULL);
3800
3801    /*
3802     * Check first the XML catalogs
3803     */
3804    if (xmlDefaultCatalog != NULL) {
3805	ret = xmlCatalogListXMLResolve(xmlDefaultCatalog->xml, pubID, NULL);
3806	if ((ret != NULL) && (ret != XML_CATAL_BREAK)) {
3807	    snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret);
3808	    result[sizeof(result) - 1] = 0;
3809	    return(result);
3810	}
3811    }
3812
3813    if (xmlDefaultCatalog != NULL)
3814	return(xmlCatalogGetSGMLPublic(xmlDefaultCatalog->sgml, pubID));
3815    return(NULL);
3816}
3817
3818#define bottom_catalog
3819#include "elfgcchack.h"
3820#endif /* LIBXML_CATALOG_ENABLED */
3821