catalog.c 96.9 KB
Newer Older
1
/**
2
 * catalog.c: set of generic Catalog related routines
3 4 5 6
 *
 * Reference:  SGML Open Technical Resolution TR9401:1997.
 *             http://www.jclark.com/sp/catalog.htm
 *
7 8 9
 *             XML Catalogs Working Draft 06 August 2001
 *             http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
 *
10 11 12 13 14
 * See Copyright for the status of this software.
 *
 * Daniel.Veillard@imag.fr
 */

15
#define IN_LIBXML
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
#include "libxml.h"

#ifdef LIBXML_CATALOG_ENABLED
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
31 32 33
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
34 35 36 37 38 39 40
#include <string.h>
#include <libxml/xmlmemory.h>
#include <libxml/hash.h>
#include <libxml/uri.h>
#include <libxml/parserInternals.h>
#include <libxml/catalog.h>
#include <libxml/xmlerror.h>
41
#include <libxml/threads.h>
42
#include <libxml/globals.h>
43

44 45
#include "buf.h"

46
#define MAX_DELEGATE	50
47
#define MAX_CATAL_DEPTH	50
48

49
#ifdef _WIN32
50
# define PATH_SEPARATOR ';'
51
#else
52
# define PATH_SEPARATOR ':'
53 54
#endif

55 56 57 58
/**
 * TODO:
 *
 * macro to flag unimplemented blocks
59 60 61 62 63
 * XML_CATALOG_PREFER user env to select between system/public prefered
 * option. C.f. Richard Tobin <richard@cogsci.ed.ac.uk>
 *> Just FYI, I am using an environment variable XML_CATALOG_PREFER with
 *> values "system" and "public".  I have made the default be "system" to
 *> match yours.
64
 */
65
#define TODO								\
66 67 68 69
    xmlGenericError(xmlGenericErrorContext,				\
	    "Unimplemented block at %s:%d\n",				\
            __FILE__, __LINE__);

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 78
#endif

79 80 81
#if defined(_WIN32) && defined(_MSC_VER)
#undef XML_XML_DEFAULT_CATALOG
static char XML_XML_DEFAULT_CATALOG[256] = "file:///etc/xml/catalog";
82 83 84 85 86
#if defined(_WIN32_WCE)
/* Windows CE don't have a A variant */
#define GetModuleHandleA GetModuleHandle
#define GetModuleFileNameA GetModuleFileName
#else
87
#if !defined(_WINDOWS_)
88 89 90
void* __stdcall GetModuleHandleA(const char*);
unsigned long __stdcall GetModuleFileNameA(void*, char*, unsigned long);
#endif
91
#endif
92
#endif
93

94
static xmlChar *xmlCatalogNormalizePublic(const xmlChar *pubID);
95
static int xmlExpandCatalog(xmlCatalogPtr catal, const char *filename);
96

97 98 99 100 101 102 103
/************************************************************************
 *									*
 *			Types, all private				*
 *									*
 ************************************************************************/

typedef enum {
104
    XML_CATA_REMOVED = -1,
105 106
    XML_CATA_NONE = 0,
    XML_CATA_CATALOG,
107
    XML_CATA_BROKEN_CATALOG,
108
    XML_CATA_NEXT_CATALOG,
109
    XML_CATA_GROUP,
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
    XML_CATA_PUBLIC,
    XML_CATA_SYSTEM,
    XML_CATA_REWRITE_SYSTEM,
    XML_CATA_DELEGATE_PUBLIC,
    XML_CATA_DELEGATE_SYSTEM,
    XML_CATA_URI,
    XML_CATA_REWRITE_URI,
    XML_CATA_DELEGATE_URI,
    SGML_CATA_SYSTEM,
    SGML_CATA_PUBLIC,
    SGML_CATA_ENTITY,
    SGML_CATA_PENTITY,
    SGML_CATA_DOCTYPE,
    SGML_CATA_LINKTYPE,
    SGML_CATA_NOTATION,
    SGML_CATA_DELEGATE,
    SGML_CATA_BASE,
    SGML_CATA_CATALOG,
    SGML_CATA_DOCUMENT,
    SGML_CATA_SGMLDECL
130 131 132 133 134
} xmlCatalogEntryType;

typedef struct _xmlCatalogEntry xmlCatalogEntry;
typedef xmlCatalogEntry *xmlCatalogEntryPtr;
struct _xmlCatalogEntry {
135 136 137
    struct _xmlCatalogEntry *next;
    struct _xmlCatalogEntry *parent;
    struct _xmlCatalogEntry *children;
138 139 140
    xmlCatalogEntryType type;
    xmlChar *name;
    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
};

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
typedef enum {
    XML_XML_CATALOG_TYPE = 1,
    XML_SGML_CATALOG_TYPE
} xmlCatalogType;

#define XML_MAX_SGML_CATA_DEPTH 10
struct _xmlCatalog {
    xmlCatalogType type;	/* either XML or SGML */

    /*
     * SGML Catalogs are stored as a simple hash table of catalog entries
     * Catalog stack to check against overflows when building the
     * SGML catalog
     */
    char *catalTab[XML_MAX_SGML_CATA_DEPTH];	/* stack of catals */
    int          catalNr;	/* Number of current catal streams */
    int          catalMax;	/* Max number of catal streams */
    xmlHashTablePtr sgml;

    /*
     * XML Catalogs are stored as a tree of Catalog entries
     */
    xmlCatalogPrefer prefer;
    xmlCatalogEntryPtr xml;
};

/************************************************************************
 *									*
 *			Global variables				*
 *									*
 ************************************************************************/

180 181 182 183
/*
 * Those are preferences
 */
static int xmlDebugCatalogs = 0;   /* used for debugging */
184
static xmlCatalogAllow xmlCatalogDefaultAllow = XML_CATA_ALLOW_ALL;
185
static xmlCatalogPrefer xmlCatalogDefaultPrefer = XML_CATA_PREFER_PUBLIC;
186 187 188 189 190

/*
 * Hash table containing all the trees of XML catalogs parsed by
 * the application.
 */
191
static xmlHashTablePtr xmlCatalogXMLFiles = NULL;
192

193 194 195 196 197
/*
 * The default catalog in use by the application
 */
static xmlCatalogPtr xmlDefaultCatalog = NULL;

198 199 200 201 202 203 204 205
/*
 * A mutex for modifying the shared global catalog(s)
 * xmlDefaultCatalog tree.
 * It also protects xmlCatalogXMLFiles
 * The core of this readers/writer scheme is in xmlFetchXMLCatalogFile()
 */
static xmlRMutexPtr xmlCatalogMutex = NULL;

206 207 208 209
/*
 * Whether the catalog support was initialized.
 */
static int xmlCatalogInitialized = 0;
210

211 212
/************************************************************************
 *									*
213
 *			Catalog error handlers				*
214 215 216 217 218 219 220 221 222 223 224 225
 *									*
 ************************************************************************/

/**
 * xmlCatalogErrMemory:
 * @extra:  extra informations
 *
 * Handle an out of memory condition
 */
static void
xmlCatalogErrMemory(const char *extra)
{
226
    __xmlRaiseError(NULL, NULL, NULL, NULL, NULL, XML_FROM_CATALOG,
227 228 229 230 231 232 233 234 235 236 237 238 239 240
                    XML_ERR_NO_MEMORY, XML_ERR_ERROR, NULL, 0,
		    extra, NULL, NULL, 0, 0,
		    "Memory allocation failed : %s\n", extra);
}

/**
 * xmlCatalogErr:
 * @catal: the Catalog entry
 * @node: the context node
 * @msg:  the error message
 * @extra:  extra informations
 *
 * Handle a catalog error
 */
241
static void LIBXML_ATTR_FORMAT(4,0)
242 243 244 245
xmlCatalogErr(xmlCatalogEntryPtr catal, xmlNodePtr node, int error,
               const char *msg, const xmlChar *str1, const xmlChar *str2,
	       const xmlChar *str3)
{
246
    __xmlRaiseError(NULL, NULL, NULL, catal, node, XML_FROM_CATALOG,
247 248 249 250 251 252
                    error, XML_ERR_ERROR, NULL, 0,
		    (const char *) str1, (const char *) str2,
		    (const char *) str3, 0, 0,
		    msg, str1, str2, str3);
}

253

254 255
/************************************************************************
 *									*
256
 *			Allocation and Freeing				*
257 258 259
 *									*
 ************************************************************************/

260 261 262 263 264 265
/**
 * xmlNewCatalogEntry:
 * @type:  type of entry
 * @name:  name of the entry
 * @value:  value of the entry
 * @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 270 271 272
 * SGML catalogs, but the acceptable types values differs.
 *
 * Returns the xmlCatalogEntryPtr or NULL in case of error
 */
273
static xmlCatalogEntryPtr
274
xmlNewCatalogEntry(xmlCatalogEntryType type, const xmlChar *name,
275 276
	   const xmlChar *value, const xmlChar *URL, xmlCatalogPrefer prefer,
	   xmlCatalogEntryPtr group) {
277
    xmlCatalogEntryPtr ret;
278
    xmlChar *normid = NULL;
279 280 281

    ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry));
    if (ret == NULL) {
282
        xmlCatalogErrMemory("allocating catalog entry");
283 284
	return(NULL);
    }
285 286 287
    ret->next = NULL;
    ret->parent = NULL;
    ret->children = NULL;
288
    ret->type = type;
289 290 291 292 293
    if (type == XML_CATA_PUBLIC || type == XML_CATA_DELEGATE_PUBLIC) {
        normid = xmlCatalogNormalizePublic(name);
        if (normid != NULL)
            name = (*normid != 0 ? normid : NULL);
    }
294 295 296 297
    if (name != NULL)
	ret->name = xmlStrdup(name);
    else
	ret->name = NULL;
298 299
    if (normid != NULL)
        xmlFree(normid);
300 301 302 303
    if (value != NULL)
	ret->value = xmlStrdup(value);
    else
	ret->value = NULL;
304 305 306 307 308 309
    if (URL == NULL)
	URL = value;
    if (URL != NULL)
	ret->URL = xmlStrdup(URL);
    else
	ret->URL = NULL;
310
    ret->prefer = prefer;
311
    ret->dealloc = 0;
312
    ret->depth = 0;
313
    ret->group = group;
314 315 316
    return(ret);
}

317 318 319
static void
xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret);

320 321
/**
 * xmlFreeCatalogEntry:
322
 * @payload:  a Catalog entry
323 324 325
 *
 * Free the memory allocated to a Catalog entry
 */
326
static void
327 328
xmlFreeCatalogEntry(void *payload, const xmlChar *name ATTRIBUTE_UNUSED) {
    xmlCatalogEntryPtr ret = (xmlCatalogEntryPtr) payload;
329 330
    if (ret == NULL)
	return;
331
    /*
332
     * Entries stored in the file hash must be deallocated
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
     * only by the file hash cleaner !
     */
    if (ret->dealloc == 1)
	return;

    if (xmlDebugCatalogs) {
	if (ret->name != NULL)
	    xmlGenericError(xmlGenericErrorContext,
		    "Free catalog entry %s\n", ret->name);
	else if (ret->value != NULL)
	    xmlGenericError(xmlGenericErrorContext,
		    "Free catalog entry %s\n", ret->value);
	else
	    xmlGenericError(xmlGenericErrorContext,
		    "Free catalog entry\n");
    }

350 351 352 353
    if (ret->name != NULL)
	xmlFree(ret->name);
    if (ret->value != NULL)
	xmlFree(ret->value);
354 355
    if (ret->URL != NULL)
	xmlFree(ret->URL);
356 357 358
    xmlFree(ret);
}

359 360 361 362 363 364
/**
 * xmlFreeCatalogEntryList:
 * @ret:  a Catalog entry list
 *
 * Free the memory allocated to a full chained list of Catalog entries
 */
365 366 367 368 369 370
static void
xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret) {
    xmlCatalogEntryPtr next;

    while (ret != NULL) {
	next = ret->next;
371
	xmlFreeCatalogEntry(ret, NULL);
372 373 374 375
	ret = next;
    }
}

376 377
/**
 * xmlFreeCatalogHashEntryList:
378
 * @payload:  a Catalog entry list
379 380 381 382 383
 *
 * Free the memory allocated to list of Catalog entries from the
 * catalog file hash.
 */
static void
384 385 386
xmlFreeCatalogHashEntryList(void *payload,
                            const xmlChar *name ATTRIBUTE_UNUSED) {
    xmlCatalogEntryPtr catal = (xmlCatalogEntryPtr) payload;
387 388 389 390 391 392 393 394 395 396
    xmlCatalogEntryPtr children, next;

    if (catal == NULL)
	return;

    children = catal->children;
    while (children != NULL) {
	next = children->next;
	children->dealloc = 0;
	children->children = NULL;
397
	xmlFreeCatalogEntry(children, NULL);
398 399 400
	children = next;
    }
    catal->dealloc = 0;
401
    xmlFreeCatalogEntry(catal, NULL);
402 403
}

404
/**
405
 * xmlCreateNewCatalog:
406 407 408
 * @type:  type of catalog
 * @prefer:  the PUBLIC vs. SYSTEM current preference value
 *
409
 * create a new Catalog, this type is shared both by XML and
410 411 412 413 414
 * SGML catalogs, but the acceptable types values differs.
 *
 * Returns the xmlCatalogPtr or NULL in case of error
 */
static xmlCatalogPtr
415
xmlCreateNewCatalog(xmlCatalogType type, xmlCatalogPrefer prefer) {
416 417 418 419
    xmlCatalogPtr ret;

    ret = (xmlCatalogPtr) xmlMalloc(sizeof(xmlCatalog));
    if (ret == NULL) {
420
        xmlCatalogErrMemory("allocating catalog");
421 422 423 424 425 426 427
	return(NULL);
    }
    memset(ret, 0, sizeof(xmlCatalog));
    ret->type = type;
    ret->catalNr = 0;
    ret->catalMax = XML_MAX_SGML_CATA_DEPTH;
    ret->prefer = prefer;
428 429
    if (ret->type == XML_SGML_CATALOG_TYPE)
	ret->sgml = xmlHashCreate(10);
430 431 432 433 434
    return(ret);
}

/**
 * xmlFreeCatalog:
435
 * @catal:  a Catalog
436 437 438 439 440 441 442 443 444 445
 *
 * Free the memory allocated to a Catalog
 */
void
xmlFreeCatalog(xmlCatalogPtr catal) {
    if (catal == NULL)
	return;
    if (catal->xml != NULL)
	xmlFreeCatalogEntryList(catal->xml);
    if (catal->sgml != NULL)
446
	xmlHashFree(catal->sgml, xmlFreeCatalogEntry);
447 448 449 450 451 452 453 454 455
    xmlFree(catal);
}

/************************************************************************
 *									*
 *			Serializing Catalogs				*
 *									*
 ************************************************************************/

456
#ifdef LIBXML_OUTPUT_ENABLED
457 458
/**
 * xmlCatalogDumpEntry:
459
 * @entry:  the catalog entry
460 461
 * @out:  the file.
 *
462
 * Serialize an SGML Catalog entry
463 464
 */
static void
465 466 467 468
xmlCatalogDumpEntry(void *payload, void *data,
                    const xmlChar *name ATTRIBUTE_UNUSED) {
    xmlCatalogEntryPtr entry = (xmlCatalogEntryPtr) payload;
    FILE *out = (FILE *) data;
469 470 471
    if ((entry == NULL) || (out == NULL))
	return;
    switch (entry->type) {
472
	case SGML_CATA_ENTITY:
473
	    fprintf(out, "ENTITY "); break;
474
	case SGML_CATA_PENTITY:
475
	    fprintf(out, "ENTITY %%"); break;
476
	case SGML_CATA_DOCTYPE:
477
	    fprintf(out, "DOCTYPE "); break;
478
	case SGML_CATA_LINKTYPE:
479
	    fprintf(out, "LINKTYPE "); break;
480
	case SGML_CATA_NOTATION:
481
	    fprintf(out, "NOTATION "); break;
482
	case SGML_CATA_PUBLIC:
483
	    fprintf(out, "PUBLIC "); break;
484
	case SGML_CATA_SYSTEM:
485
	    fprintf(out, "SYSTEM "); break;
486
	case SGML_CATA_DELEGATE:
487
	    fprintf(out, "DELEGATE "); break;
488
	case SGML_CATA_BASE:
489
	    fprintf(out, "BASE "); break;
490
	case SGML_CATA_CATALOG:
491
	    fprintf(out, "CATALOG "); break;
492
	case SGML_CATA_DOCUMENT:
493
	    fprintf(out, "DOCUMENT "); break;
494
	case SGML_CATA_SGMLDECL:
495 496 497 498 499
	    fprintf(out, "SGMLDECL "); break;
	default:
	    return;
    }
    switch (entry->type) {
500 501 502 503 504
	case SGML_CATA_ENTITY:
	case SGML_CATA_PENTITY:
	case SGML_CATA_DOCTYPE:
	case SGML_CATA_LINKTYPE:
	case SGML_CATA_NOTATION:
505
	    fprintf(out, "%s", (const char *) entry->name); break;
506 507 508 509 510 511 512
	case SGML_CATA_PUBLIC:
	case SGML_CATA_SYSTEM:
	case SGML_CATA_SGMLDECL:
	case SGML_CATA_DOCUMENT:
	case SGML_CATA_CATALOG:
	case SGML_CATA_BASE:
	case SGML_CATA_DELEGATE:
513 514 515 516 517
	    fprintf(out, "\"%s\"", entry->name); break;
	default:
	    break;
    }
    switch (entry->type) {
518 519 520 521 522 523 524 525
	case SGML_CATA_ENTITY:
	case SGML_CATA_PENTITY:
	case SGML_CATA_DOCTYPE:
	case SGML_CATA_LINKTYPE:
	case SGML_CATA_NOTATION:
	case SGML_CATA_PUBLIC:
	case SGML_CATA_SYSTEM:
	case SGML_CATA_DELEGATE:
526 527 528 529 530 531 532
	    fprintf(out, " \"%s\"", entry->value); break;
	default:
	    break;
    }
    fprintf(out, "\n");
}

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
/**
 * xmlDumpXMLCatalogNode:
 * @catal:  top catalog entry
 * @catalog: pointer to the xml tree
 * @doc: the containing document
 * @ns: the current namespace
 * @cgroup: group node for group members
 *
 * Serializes a Catalog entry, called by xmlDumpXMLCatalog and recursively
 * for group entries
 */
static void xmlDumpXMLCatalogNode(xmlCatalogEntryPtr catal, xmlNodePtr catalog,
		    xmlDocPtr doc, xmlNsPtr ns, xmlCatalogEntryPtr cgroup) {
    xmlNodePtr node;
    xmlCatalogEntryPtr cur;
    /*
     * add all the catalog entries
     */
    cur = catal;
    while (cur != NULL) {
        if (cur->group == cgroup) {
	    switch (cur->type) {
	        case XML_CATA_REMOVED:
		    break;
	        case XML_CATA_BROKEN_CATALOG:
	        case XML_CATA_CATALOG:
		    if (cur == catal) {
			cur = cur->children;
		        continue;
		    }
		    break;
		case XML_CATA_NEXT_CATALOG:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "nextCatalog", NULL);
		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
		    xmlAddChild(catalog, node);
                    break;
		case XML_CATA_NONE:
		    break;
		case XML_CATA_GROUP:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "group", NULL);
		    xmlSetProp(node, BAD_CAST "id", cur->name);
574 575 576 577 578
		    if (cur->value != NULL) {
		        xmlNsPtr xns;
			xns = xmlSearchNsByHref(doc, node, XML_XML_NAMESPACE);
			if (xns != NULL)
			    xmlSetNsProp(node, xns, BAD_CAST "base",
579
					 cur->value);
580
		    }
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
		    switch (cur->prefer) {
			case XML_CATA_PREFER_NONE:
		            break;
			case XML_CATA_PREFER_PUBLIC:
		            xmlSetProp(node, BAD_CAST "prefer", BAD_CAST "public");
			    break;
			case XML_CATA_PREFER_SYSTEM:
		            xmlSetProp(node, BAD_CAST "prefer", BAD_CAST "system");
			    break;
		    }
		    xmlDumpXMLCatalogNode(cur->next, node, doc, ns, cur);
		    xmlAddChild(catalog, node);
	            break;
		case XML_CATA_PUBLIC:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "public", NULL);
		    xmlSetProp(node, BAD_CAST "publicId", cur->name);
		    xmlSetProp(node, BAD_CAST "uri", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_SYSTEM:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "system", NULL);
		    xmlSetProp(node, BAD_CAST "systemId", cur->name);
		    xmlSetProp(node, BAD_CAST "uri", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_REWRITE_SYSTEM:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteSystem", NULL);
		    xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
		    xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_DELEGATE_PUBLIC:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegatePublic", NULL);
		    xmlSetProp(node, BAD_CAST "publicIdStartString", cur->name);
		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_DELEGATE_SYSTEM:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegateSystem", NULL);
		    xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_URI:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "uri", NULL);
		    xmlSetProp(node, BAD_CAST "name", cur->name);
		    xmlSetProp(node, BAD_CAST "uri", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_REWRITE_URI:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteURI", NULL);
		    xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
		    xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case XML_CATA_DELEGATE_URI:
		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegateURI", NULL);
		    xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
		    xmlAddChild(catalog, node);
		    break;
		case SGML_CATA_SYSTEM:
		case SGML_CATA_PUBLIC:
		case SGML_CATA_ENTITY:
		case SGML_CATA_PENTITY:
		case SGML_CATA_DOCTYPE:
		case SGML_CATA_LINKTYPE:
		case SGML_CATA_NOTATION:
		case SGML_CATA_DELEGATE:
		case SGML_CATA_BASE:
		case SGML_CATA_CATALOG:
		case SGML_CATA_DOCUMENT:
		case SGML_CATA_SGMLDECL:
		    break;
	    }
        }
	cur = cur->next;
    }
}

661 662 663 664 665 666
static int
xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) {
    int ret;
    xmlDocPtr doc;
    xmlNsPtr ns;
    xmlDtdPtr dtd;
667
    xmlNodePtr catalog;
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
    xmlOutputBufferPtr buf;

    /*
     * Rebuild a catalog
     */
    doc = xmlNewDoc(NULL);
    if (doc == NULL)
	return(-1);
    dtd = xmlNewDtd(doc, BAD_CAST "catalog",
	       BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN",
BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd");

    xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);

    ns = xmlNewNs(NULL, XML_CATALOGS_NAMESPACE, NULL);
    if (ns == NULL) {
	xmlFreeDoc(doc);
	return(-1);
    }
    catalog = xmlNewDocNode(doc, ns, BAD_CAST "catalog", NULL);
    if (catalog == NULL) {
	xmlFreeNs(ns);
	xmlFreeDoc(doc);
	return(-1);
    }
    catalog->nsDef = ns;
    xmlAddChild((xmlNodePtr) doc, catalog);

696
    xmlDumpXMLCatalogNode(catal, catalog, doc, ns, NULL);
697

698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
    /*
     * reserialize it
     */
    buf = xmlOutputBufferCreateFile(out, NULL);
    if (buf == NULL) {
	xmlFreeDoc(doc);
	return(-1);
    }
    ret = xmlSaveFormatFileTo(buf, doc, NULL, 1);

    /*
     * Free it
     */
    xmlFreeDoc(doc);

    return(ret);
}
715
#endif /* LIBXML_OUTPUT_ENABLED */
716 717 718 719 720 721 722

/************************************************************************
 *									*
 *			Converting SGML Catalogs to XML			*
 *									*
 ************************************************************************/

723 724 725
/**
 * xmlCatalogConvertEntry:
 * @entry:  the entry
726
 * @catal:  pointer to the catalog being converted
727
 *
728
 * Convert one entry from the catalog
729 730
 */
static void
731 732 733 734
xmlCatalogConvertEntry(void *payload, void *data,
                       const xmlChar *name ATTRIBUTE_UNUSED) {
    xmlCatalogEntryPtr entry = (xmlCatalogEntryPtr) payload;
    xmlCatalogPtr catal = (xmlCatalogPtr) data;
735 736
    if ((entry == NULL) || (catal == NULL) || (catal->sgml == NULL) ||
	(catal->xml == NULL))
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
	return;
    switch (entry->type) {
	case SGML_CATA_ENTITY:
	    entry->type = XML_CATA_PUBLIC;
	    break;
	case SGML_CATA_PENTITY:
	    entry->type = XML_CATA_PUBLIC;
	    break;
	case SGML_CATA_DOCTYPE:
	    entry->type = XML_CATA_PUBLIC;
	    break;
	case SGML_CATA_LINKTYPE:
	    entry->type = XML_CATA_PUBLIC;
	    break;
	case SGML_CATA_NOTATION:
	    entry->type = XML_CATA_PUBLIC;
	    break;
	case SGML_CATA_PUBLIC:
	    entry->type = XML_CATA_PUBLIC;
	    break;
	case SGML_CATA_SYSTEM:
	    entry->type = XML_CATA_SYSTEM;
	    break;
	case SGML_CATA_DELEGATE:
	    entry->type = XML_CATA_DELEGATE_PUBLIC;
	    break;
	case SGML_CATA_CATALOG:
	    entry->type = XML_CATA_CATALOG;
	    break;
	default:
767
	    xmlHashRemoveEntry(catal->sgml, entry->name, xmlFreeCatalogEntry);
768 769 770 771 772 773
	    return;
    }
    /*
     * Conversion successful, remove from the SGML catalog
     * and add it to the default XML one
     */
774 775
    xmlHashRemoveEntry(catal->sgml, entry->name, NULL);
    entry->parent = catal->xml;
776
    entry->next = NULL;
777 778
    if (catal->xml->children == NULL)
	catal->xml->children = entry;
779 780 781
    else {
	xmlCatalogEntryPtr prev;

782
	prev = catal->xml->children;
783 784 785 786
	while (prev->next != NULL)
	    prev = prev->next;
	prev->next = entry;
    }
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
}

/**
 * xmlConvertSGMLCatalog:
 * @catal: the catalog
 *
 * Convert all the SGML catalog entries as XML ones
 *
 * Returns the number of entries converted if successful, -1 otherwise
 */
int
xmlConvertSGMLCatalog(xmlCatalogPtr catal) {

    if ((catal == NULL) || (catal->type != XML_SGML_CATALOG_TYPE))
	return(-1);

    if (xmlDebugCatalogs) {
	xmlGenericError(xmlGenericErrorContext,
		"Converting SGML catalog to XML\n");
    }
807
    xmlHashScan(catal->sgml, xmlCatalogConvertEntry, &catal);
808
    return(0);
809 810
}

811 812 813 814 815 816 817 818
/************************************************************************
 *									*
 *			Helper function					*
 *									*
 ************************************************************************/

/**
 * xmlCatalogUnWrapURN:
819
 * @urn:  an "urn:publicid:" to unwrap
820 821 822 823 824 825 826 827 828 829 830 831 832 833
 *
 * Expand the URN into the equivalent Public Identifier
 *
 * Returns the new identifier or NULL, the string must be deallocated
 *         by the caller.
 */
static xmlChar *
xmlCatalogUnWrapURN(const xmlChar *urn) {
    xmlChar result[2000];
    unsigned int i = 0;

    if (xmlStrncmp(urn, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1))
	return(NULL);
    urn += sizeof(XML_URN_PUBID) - 1;
834

835
    while (*urn != 0) {
836
	if (i > sizeof(result) - 4)
837 838 839 840 841 842 843 844 845 846 847 848 849
	    break;
	if (*urn == '+') {
	    result[i++] = ' ';
	    urn++;
	} else if (*urn == ':') {
	    result[i++] = '/';
	    result[i++] = '/';
	    urn++;
	} else if (*urn == ';') {
	    result[i++] = ':';
	    result[i++] = ':';
	    urn++;
	} else if (*urn == '%') {
850
	    if ((urn[1] == '2') && (urn[2] == 'B'))
851
		result[i++] = '+';
852
	    else if ((urn[1] == '3') && (urn[2] == 'A'))
853
		result[i++] = ':';
854
	    else if ((urn[1] == '2') && (urn[2] == 'F'))
855
		result[i++] = '/';
856
	    else if ((urn[1] == '3') && (urn[2] == 'B'))
857
		result[i++] = ';';
858
	    else if ((urn[1] == '2') && (urn[2] == '7'))
859
		result[i++] = '\'';
860
	    else if ((urn[1] == '3') && (urn[2] == 'F'))
861
		result[i++] = '?';
862
	    else if ((urn[1] == '2') && (urn[2] == '3'))
863
		result[i++] = '#';
864
	    else if ((urn[1] == '2') && (urn[2] == '5'))
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
		result[i++] = '%';
	    else {
		result[i++] = *urn;
		urn++;
		continue;
	    }
	    urn += 3;
	} else {
	    result[i++] = *urn;
	    urn++;
	}
    }
    result[i] = 0;

    return(xmlStrdup(result));
}

882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
/**
 * xmlParseCatalogFile:
 * @filename:  the filename
 *
 * parse an XML file and build a tree. It's like xmlParseFile()
 * except it bypass all catalog lookups.
 *
 * Returns the resulting document tree or NULL in case of error
 */

xmlDocPtr
xmlParseCatalogFile(const char *filename) {
    xmlDocPtr ret;
    xmlParserCtxtPtr ctxt;
    char *directory = NULL;
    xmlParserInputPtr inputStream;
    xmlParserInputBufferPtr buf;

    ctxt = xmlNewParserCtxt();
    if (ctxt == NULL) {
902
#ifdef LIBXML_SAX1_ENABLED
903 904 905
	if (xmlDefaultSAXHandler.error != NULL) {
	    xmlDefaultSAXHandler.error(NULL, "out of memory\n");
	}
906
#endif
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
	return(NULL);
    }

    buf = xmlParserInputBufferCreateFilename(filename, XML_CHAR_ENCODING_NONE);
    if (buf == NULL) {
	xmlFreeParserCtxt(ctxt);
	return(NULL);
    }

    inputStream = xmlNewInputStream(ctxt);
    if (inputStream == NULL) {
	xmlFreeParserCtxt(ctxt);
	return(NULL);
    }

922
    inputStream->filename = (char *) xmlCanonicPath((const xmlChar *)filename);
923
    inputStream->buf = buf;
924
    xmlBufResetInput(buf->buffer, inputStream);
925 926 927 928 929 930

    inputPush(ctxt, inputStream);
    if ((ctxt->directory == NULL) && (directory == NULL))
        directory = xmlParserGetDirectory(filename);
    if ((ctxt->directory == NULL) && (directory != NULL))
        ctxt->directory = directory;
931 932 933 934
    ctxt->valid = 0;
    ctxt->validate = 0;
    ctxt->loadsubset = 0;
    ctxt->pedantic = 0;
935
    ctxt->dictNames = 1;
936 937 938 939 940 941 942 943 944 945 946

    xmlParseDocument(ctxt);

    if (ctxt->wellFormed)
	ret = ctxt->myDoc;
    else {
        ret = NULL;
        xmlFreeDoc(ctxt->myDoc);
        ctxt->myDoc = NULL;
    }
    xmlFreeParserCtxt(ctxt);
947

948 949 950
    return(ret);
}

951 952 953 954 955 956 957 958
/**
 * xmlLoadFileContent:
 * @filename:  a file path
 *
 * Load a file content into memory.
 *
 * Returns a pointer to the 0 terminated string or NULL in case of error
 */
959
static xmlChar *
960 961 962 963 964 965 966 967 968
xmlLoadFileContent(const char *filename)
{
#ifdef HAVE_STAT
    int fd;
#else
    FILE *fd;
#endif
    int len;
    long size;
969

970 971 972 973 974 975 976 977 978 979 980 981 982 983
#ifdef HAVE_STAT
    struct stat info;
#endif
    xmlChar *content;

    if (filename == NULL)
        return (NULL);

#ifdef HAVE_STAT
    if (stat(filename, &info) < 0)
        return (NULL);
#endif

#ifdef HAVE_STAT
984
    if ((fd = open(filename, O_RDONLY)) < 0)
985
#else
986
    if ((fd = fopen(filename, "rb")) == NULL)
987
#endif
988
    {
989 990 991 992 993 994 995 996 997 998
        return (NULL);
    }
#ifdef HAVE_STAT
    size = info.st_size;
#else
    if (fseek(fd, 0, SEEK_END) || (size = ftell(fd)) == EOF || fseek(fd, 0, SEEK_SET)) {        /* File operations denied? ok, just close and return failure */
        fclose(fd);
        return (NULL);
    }
#endif
999
    content = (xmlChar*)xmlMallocAtomic(size + 10);
1000
    if (content == NULL) {
1001
        xmlCatalogErrMemory("allocating catalog data");
1002 1003 1004 1005 1006
#ifdef HAVE_STAT
	close(fd);
#else
	fclose(fd);
#endif
1007 1008 1009 1010
        return (NULL);
    }
#ifdef HAVE_STAT
    len = read(fd, content, size);
1011
    close(fd);
1012 1013
#else
    len = fread(content, 1, size, fd);
1014
    fclose(fd);
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
#endif
    if (len < 0) {
        xmlFree(content);
        return (NULL);
    }
    content[len] = 0;

    return(content);
}

1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
/**
 * xmlCatalogNormalizePublic:
 * @pubID:  the public ID string
 *
 *  Normalizes the Public Identifier
 *
 * Implements 6.2. Public Identifier Normalization
 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
 *
 * Returns the new string or NULL, the string must be deallocated
 *         by the caller.
 */
static xmlChar *
xmlCatalogNormalizePublic(const xmlChar *pubID)
{
    int ok = 1;
    int white;
    const xmlChar *p;
    xmlChar *ret;
    xmlChar *q;

    if (pubID == NULL)
        return(NULL);

    white = 1;
    for (p = pubID;*p != 0 && ok;p++) {
        if (!xmlIsBlank_ch(*p))
            white = 0;
        else if (*p == 0x20 && !white)
            white = 1;
        else
            ok = 0;
    }
    if (ok && !white)	/* is normalized */
        return(NULL);

    ret = xmlStrdup(pubID);
    q = ret;
    white = 0;
    for (p = pubID;*p != 0;p++) {
        if (xmlIsBlank_ch(*p)) {
            if (q != ret)
                white = 1;
        } else {
            if (white) {
                *(q++) = 0x20;
                white = 0;
            }
            *(q++) = *p;
        }
    }
    *q = 0;
    return(ret);
}

1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
/************************************************************************
 *									*
 *			The XML Catalog parser				*
 *									*
 ************************************************************************/

static xmlCatalogEntryPtr
xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename);
static void
xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
1090
	                   xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup);
1091 1092 1093 1094 1095
static xmlChar *
xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
	              const xmlChar *sysID);
static xmlChar *
xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI);
1096

1097 1098 1099 1100 1101 1102 1103

/**
 * xmlGetXMLCatalogEntryType:
 * @name:  the name
 *
 * lookup the internal type associated to an XML catalog entry name
 *
1104
 * Returns the type associated with that name
1105
 */
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
static xmlCatalogEntryType
xmlGetXMLCatalogEntryType(const xmlChar *name) {
    xmlCatalogEntryType type = XML_CATA_NONE;
    if (xmlStrEqual(name, (const xmlChar *) "system"))
	type = XML_CATA_SYSTEM;
    else if (xmlStrEqual(name, (const xmlChar *) "public"))
	type = XML_CATA_PUBLIC;
    else if (xmlStrEqual(name, (const xmlChar *) "rewriteSystem"))
	type = XML_CATA_REWRITE_SYSTEM;
    else if (xmlStrEqual(name, (const xmlChar *) "delegatePublic"))
	type = XML_CATA_DELEGATE_PUBLIC;
    else if (xmlStrEqual(name, (const xmlChar *) "delegateSystem"))
	type = XML_CATA_DELEGATE_SYSTEM;
    else if (xmlStrEqual(name, (const xmlChar *) "uri"))
	type = XML_CATA_URI;
    else if (xmlStrEqual(name, (const xmlChar *) "rewriteURI"))
	type = XML_CATA_REWRITE_URI;
    else if (xmlStrEqual(name, (const xmlChar *) "delegateURI"))
	type = XML_CATA_DELEGATE_URI;
    else if (xmlStrEqual(name, (const xmlChar *) "nextCatalog"))
	type = XML_CATA_NEXT_CATALOG;
    else if (xmlStrEqual(name, (const xmlChar *) "catalog"))
	type = XML_CATA_CATALOG;
    return(type);
}

1132 1133 1134 1135 1136 1137 1138 1139
/**
 * xmlParseXMLCatalogOneNode:
 * @cur:  the XML node
 * @type:  the type of Catalog entry
 * @name:  the name of the node
 * @attrName:  the attribute holding the value
 * @uriAttrName:  the attribute holding the URI-Reference
 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1140
 * @cgroup:  the group which includes this node
1141 1142 1143 1144 1145 1146
 *
 * Finishes the examination of an XML tree node of a catalog and build
 * a Catalog entry from it.
 *
 * Returns the new Catalog entry node or NULL in case of error.
 */
1147 1148 1149
static xmlCatalogEntryPtr
xmlParseXMLCatalogOneNode(xmlNodePtr cur, xmlCatalogEntryType type,
			  const xmlChar *name, const xmlChar *attrName,
1150 1151
			  const xmlChar *uriAttrName, xmlCatalogPrefer prefer,
			  xmlCatalogEntryPtr cgroup) {
1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
    int ok = 1;
    xmlChar *uriValue;
    xmlChar *nameValue = NULL;
    xmlChar *base = NULL;
    xmlChar *URL = NULL;
    xmlCatalogEntryPtr ret = NULL;

    if (attrName != NULL) {
	nameValue = xmlGetProp(cur, attrName);
	if (nameValue == NULL) {
1162 1163
	    xmlCatalogErr(ret, cur, XML_CATALOG_MISSING_ATTR,
			  "%s entry lacks '%s'\n", name, attrName, NULL);
1164 1165 1166 1167 1168
	    ok = 0;
	}
    }
    uriValue = xmlGetProp(cur, uriAttrName);
    if (uriValue == NULL) {
1169 1170
	xmlCatalogErr(ret, cur, XML_CATALOG_MISSING_ATTR,
		"%s entry lacks '%s'\n", name, uriAttrName, NULL);
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
	ok = 0;
    }
    if (!ok) {
	if (nameValue != NULL)
	    xmlFree(nameValue);
	if (uriValue != NULL)
	    xmlFree(uriValue);
	return(NULL);
    }

    base = xmlNodeGetBase(cur->doc, cur);
    URL = xmlBuildURI(uriValue, base);
    if (URL != NULL) {
1184
	if (xmlDebugCatalogs > 1) {
1185
	    if (nameValue != NULL)
1186 1187
		xmlGenericError(xmlGenericErrorContext,
			"Found %s: '%s' '%s'\n", name, nameValue, URL);
1188
	    else
1189 1190
		xmlGenericError(xmlGenericErrorContext,
			"Found %s: '%s'\n", name, URL);
1191
	}
1192
	ret = xmlNewCatalogEntry(type, nameValue, uriValue, URL, prefer, cgroup);
1193
    } else {
1194
	xmlCatalogErr(ret, cur, XML_CATALOG_ENTRY_BROKEN,
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207
		"%s entry '%s' broken ?: %s\n", name, uriAttrName, uriValue);
    }
    if (nameValue != NULL)
	xmlFree(nameValue);
    if (uriValue != NULL)
	xmlFree(uriValue);
    if (base != NULL)
	xmlFree(base);
    if (URL != NULL)
	xmlFree(URL);
    return(ret);
}

1208 1209 1210 1211 1212
/**
 * xmlParseXMLCatalogNode:
 * @cur:  the XML node
 * @prefer:  the PUBLIC vs. SYSTEM current preference value
 * @parent:  the parent Catalog entry
1213
 * @cgroup:  the group which includes this node
1214 1215 1216 1217 1218
 *
 * Examines an XML tree node of a catalog and build
 * a Catalog entry from it adding it to its parent. The examination can
 * be recursive.
 */
1219 1220
static void
xmlParseXMLCatalogNode(xmlNodePtr cur, xmlCatalogPrefer prefer,
1221
	               xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup)
1222 1223 1224 1225 1226 1227 1228 1229
{
    xmlChar *base = NULL;
    xmlCatalogEntryPtr entry = NULL;

    if (cur == NULL)
        return;
    if (xmlStrEqual(cur->name, BAD_CAST "group")) {
        xmlChar *prop;
1230
	xmlCatalogPrefer pref = XML_CATA_PREFER_NONE;
1231 1232 1233 1234 1235 1236 1237 1238

        prop = xmlGetProp(cur, BAD_CAST "prefer");
        if (prop != NULL) {
            if (xmlStrEqual(prop, BAD_CAST "system")) {
                prefer = XML_CATA_PREFER_SYSTEM;
            } else if (xmlStrEqual(prop, BAD_CAST "public")) {
                prefer = XML_CATA_PREFER_PUBLIC;
            } else {
1239 1240 1241
		xmlCatalogErr(parent, cur, XML_CATALOG_PREFER_VALUE,
                              "Invalid value for prefer: '%s'\n",
			      prop, NULL, NULL);
1242 1243
            }
            xmlFree(prop);
1244
	    pref = prefer;
1245
        }
1246 1247 1248
	prop = xmlGetProp(cur, BAD_CAST "id");
	base = xmlGetNsProp(cur, BAD_CAST "base", XML_XML_NAMESPACE);
	entry = xmlNewCatalogEntry(XML_CATA_GROUP, prop, base, NULL, pref, cgroup);
William M. Brack's avatar
William M. Brack committed
1249
	xmlFree(prop);
1250 1251
    } else if (xmlStrEqual(cur->name, BAD_CAST "public")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_PUBLIC,
1252
		BAD_CAST "public", BAD_CAST "publicId", BAD_CAST "uri", prefer, cgroup);
1253 1254
    } else if (xmlStrEqual(cur->name, BAD_CAST "system")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_SYSTEM,
1255
		BAD_CAST "system", BAD_CAST "systemId", BAD_CAST "uri", prefer, cgroup);
1256 1257 1258
    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteSystem")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_SYSTEM,
		BAD_CAST "rewriteSystem", BAD_CAST "systemIdStartString",
1259
		BAD_CAST "rewritePrefix", prefer, cgroup);
1260 1261 1262
    } else if (xmlStrEqual(cur->name, BAD_CAST "delegatePublic")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_PUBLIC,
		BAD_CAST "delegatePublic", BAD_CAST "publicIdStartString",
1263
		BAD_CAST "catalog", prefer, cgroup);
1264 1265 1266
    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateSystem")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_SYSTEM,
		BAD_CAST "delegateSystem", BAD_CAST "systemIdStartString",
1267
		BAD_CAST "catalog", prefer, cgroup);
1268 1269 1270
    } else if (xmlStrEqual(cur->name, BAD_CAST "uri")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_URI,
		BAD_CAST "uri", BAD_CAST "name",
1271
		BAD_CAST "uri", prefer, cgroup);
1272 1273 1274
    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteURI")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_URI,
		BAD_CAST "rewriteURI", BAD_CAST "uriStartString",
1275
		BAD_CAST "rewritePrefix", prefer, cgroup);
1276 1277 1278
    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateURI")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_URI,
		BAD_CAST "delegateURI", BAD_CAST "uriStartString",
1279
		BAD_CAST "catalog", prefer, cgroup);
1280 1281 1282
    } else if (xmlStrEqual(cur->name, BAD_CAST "nextCatalog")) {
	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_NEXT_CATALOG,
		BAD_CAST "nextCatalog", NULL,
1283
		BAD_CAST "catalog", prefer, cgroup);
1284
    }
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
    if (entry != NULL) {
        if (parent != NULL) {
	    entry->parent = parent;
	    if (parent->children == NULL)
		parent->children = entry;
	    else {
		xmlCatalogEntryPtr prev;

		prev = parent->children;
		while (prev->next != NULL)
		    prev = prev->next;
		prev->next = entry;
	    }
	}
	if (entry->type == XML_CATA_GROUP) {
	    /*
	     * Recurse to propagate prefer to the subtree
	     * (xml:base handling is automated)
	     */
            xmlParseXMLCatalogNodeList(cur->children, prefer, parent, entry);
1305
	}
1306
    }
1307 1308 1309 1310
    if (base != NULL)
	xmlFree(base);
}

1311 1312 1313 1314 1315
/**
 * xmlParseXMLCatalogNodeList:
 * @cur:  the XML node list of siblings
 * @prefer:  the PUBLIC vs. SYSTEM current preference value
 * @parent:  the parent Catalog entry
1316
 * @cgroup:  the group which includes this list
1317 1318 1319 1320 1321
 *
 * Examines a list of XML sibling nodes of a catalog and build
 * a list of Catalog entry from it adding it to the parent.
 * The examination will recurse to examine node subtrees.
 */
1322 1323
static void
xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
1324
	                   xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup) {
1325 1326 1327
    while (cur != NULL) {
	if ((cur->ns != NULL) && (cur->ns->href != NULL) &&
	    (xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
1328
	    xmlParseXMLCatalogNode(cur, prefer, parent, cgroup);
1329 1330 1331 1332 1333 1334
	}
	cur = cur->next;
    }
    /* TODO: sort the list according to REWRITE lengths and prefer value */
}

1335 1336 1337 1338 1339 1340 1341
/**
 * xmlParseXMLCatalogFile:
 * @prefer:  the PUBLIC vs. SYSTEM current preference value
 * @filename:  the filename for the catalog
 *
 * Parses the catalog file to extract the XML tree and then analyze the
 * tree to build a list of Catalog entries corresponding to this catalog
1342
 *
1343 1344
 * Returns the resulting Catalog entries list
 */
1345 1346 1347 1348 1349 1350 1351 1352 1353 1354
static xmlCatalogEntryPtr
xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename) {
    xmlDocPtr doc;
    xmlNodePtr cur;
    xmlChar *prop;
    xmlCatalogEntryPtr parent = NULL;

    if (filename == NULL)
        return(NULL);

1355
    doc = xmlParseCatalogFile((const char *) filename);
1356 1357 1358 1359
    if (doc == NULL) {
	if (xmlDebugCatalogs)
	    xmlGenericError(xmlGenericErrorContext,
		    "Failed to parse catalog %s\n", filename);
1360
	return(NULL);
1361 1362 1363 1364
    }

    if (xmlDebugCatalogs)
	xmlGenericError(xmlGenericErrorContext,
1365
		"%d Parsing catalog %s\n", xmlGetThreadId(), filename);
1366 1367 1368 1369 1370 1371

    cur = xmlDocGetRootElement(doc);
    if ((cur != NULL) && (xmlStrEqual(cur->name, BAD_CAST "catalog")) &&
	(cur->ns != NULL) && (cur->ns->href != NULL) &&
	(xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {

1372
	parent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
1373
				    (const xmlChar *)filename, NULL, prefer, NULL);
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385
        if (parent == NULL) {
	    xmlFreeDoc(doc);
	    return(NULL);
	}

	prop = xmlGetProp(cur, BAD_CAST "prefer");
	if (prop != NULL) {
	    if (xmlStrEqual(prop, BAD_CAST "system")) {
		prefer = XML_CATA_PREFER_SYSTEM;
	    } else if (xmlStrEqual(prop, BAD_CAST "public")) {
		prefer = XML_CATA_PREFER_PUBLIC;
	    } else {
1386 1387 1388
		xmlCatalogErr(NULL, cur, XML_CATALOG_PREFER_VALUE,
			      "Invalid value for prefer: '%s'\n",
			      prop, NULL, NULL);
1389 1390 1391 1392
	    }
	    xmlFree(prop);
	}
	cur = cur->children;
1393
	xmlParseXMLCatalogNodeList(cur, prefer, parent, NULL);
1394
    } else {
1395 1396 1397
	xmlCatalogErr(NULL, (xmlNodePtr) doc, XML_CATALOG_NOT_CATALOG,
		      "File %s is not an XML Catalog\n",
		      filename, NULL, NULL);
1398 1399 1400 1401 1402 1403 1404
	xmlFreeDoc(doc);
	return(NULL);
    }
    xmlFreeDoc(doc);
    return(parent);
}

1405 1406 1407 1408 1409
/**
 * xmlFetchXMLCatalogFile:
 * @catal:  an existing but incomplete catalog entry
 *
 * Fetch and parse the subcatalog referenced by an entry
1410
 *
1411 1412 1413 1414
 * Returns 0 in case of success, -1 otherwise
 */
static int
xmlFetchXMLCatalogFile(xmlCatalogEntryPtr catal) {
1415
    xmlCatalogEntryPtr doc;
1416

1417
    if (catal == NULL)
1418
	return(-1);
1419
    if (catal->URL == NULL)
1420 1421
	return(-1);

1422 1423 1424 1425 1426 1427 1428 1429 1430 1431
    /*
     * lock the whole catalog for modification
     */
    xmlRMutexLock(xmlCatalogMutex);
    if (catal->children != NULL) {
	/* Okay someone else did it in the meantime */
	xmlRMutexUnlock(xmlCatalogMutex);
	return(0);
    }

1432 1433
    if (xmlCatalogXMLFiles != NULL) {
	doc = (xmlCatalogEntryPtr)
1434
	    xmlHashLookup(xmlCatalogXMLFiles, catal->URL);
1435 1436 1437
	if (doc != NULL) {
	    if (xmlDebugCatalogs)
		xmlGenericError(xmlGenericErrorContext,
1438
		    "Found %s in file hash\n", catal->URL);
1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449

	    if (catal->type == XML_CATA_CATALOG)
		catal->children = doc->children;
	    else
		catal->children = doc;
	    catal->dealloc = 0;
	    xmlRMutexUnlock(xmlCatalogMutex);
	    return(0);
	}
	if (xmlDebugCatalogs)
	    xmlGenericError(xmlGenericErrorContext,
1450
		"%s not found in file hash\n", catal->URL);
1451 1452
    }

1453
    /*
1454
     * Fetch and parse. Note that xmlParseXMLCatalogFile does not
1455
     * use the existing catalog, there is no recursion allowed at
1456
     * that level.
1457
     */
1458
    doc = xmlParseXMLCatalogFile(catal->prefer, catal->URL);
1459
    if (doc == NULL) {
1460
	catal->type = XML_CATA_BROKEN_CATALOG;
1461
	xmlRMutexUnlock(xmlCatalogMutex);
1462
	return(-1);
1463
    }
1464

1465 1466 1467 1468 1469 1470 1471
    if (catal->type == XML_CATA_CATALOG)
	catal->children = doc->children;
    else
	catal->children = doc;

    doc->dealloc = 1;

1472 1473 1474
    if (xmlCatalogXMLFiles == NULL)
	xmlCatalogXMLFiles = xmlHashCreate(10);
    if (xmlCatalogXMLFiles != NULL) {
1475 1476
	if (xmlDebugCatalogs)
	    xmlGenericError(xmlGenericErrorContext,
1477 1478
		"%s added to file hash\n", catal->URL);
	xmlHashAddEntry(xmlCatalogXMLFiles, catal->URL, doc);
1479
    }
1480
    xmlRMutexUnlock(xmlCatalogMutex);
1481 1482 1483
    return(0);
}

1484 1485 1486 1487 1488
/************************************************************************
 *									*
 *			XML Catalog handling				*
 *									*
 ************************************************************************/
1489 1490 1491 1492 1493

/**
 * xmlAddXMLCatalog:
 * @catal:  top of an XML catalog
 * @type:  the type of record to add to the catalog
1494
 * @orig:  the system, public or prefix to match (or NULL)
1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506
 * @replace:  the replacement value for the match
 *
 * Add an entry in the XML catalog, it may overwrite existing but
 * different entries.
 *
 * Returns 0 if successful, -1 otherwise
 */
static int
xmlAddXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *type,
	      const xmlChar *orig, const xmlChar *replace) {
    xmlCatalogEntryPtr cur;
    xmlCatalogEntryType typ;
1507
    int doregister = 0;
1508

1509
    if ((catal == NULL) ||
1510 1511
	((catal->type != XML_CATA_CATALOG) &&
	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1512
	return(-1);
1513 1514 1515
    if (catal->children == NULL) {
	xmlFetchXMLCatalogFile(catal);
    }
1516 1517 1518
    if (catal->children == NULL)
	doregister = 1;

1519
    typ = xmlGetXMLCatalogEntryType(type);
1520 1521 1522 1523
    if (typ == XML_CATA_NONE) {
	if (xmlDebugCatalogs)
	    xmlGenericError(xmlGenericErrorContext,
		    "Failed to add unknown element %s to catalog\n", type);
1524
	return(-1);
1525
    }
1526 1527 1528 1529 1530 1531 1532

    cur = catal->children;
    /*
     * Might be a simple "update in place"
     */
    if (cur != NULL) {
	while (cur != NULL) {
1533 1534
	    if ((orig != NULL) && (cur->type == typ) &&
		(xmlStrEqual(orig, cur->name))) {
1535 1536 1537
		if (xmlDebugCatalogs)
		    xmlGenericError(xmlGenericErrorContext,
			    "Updating element %s to catalog\n", type);
1538 1539
		if (cur->value != NULL)
		    xmlFree(cur->value);
1540 1541
		if (cur->URL != NULL)
		    xmlFree(cur->URL);
1542
		cur->value = xmlStrdup(replace);
1543
		cur->URL = xmlStrdup(replace);
1544
		return(0);
1545 1546 1547 1548 1549 1550
	    }
	    if (cur->next == NULL)
		break;
	    cur = cur->next;
	}
    }
1551 1552 1553
    if (xmlDebugCatalogs)
	xmlGenericError(xmlGenericErrorContext,
		"Adding element %s to catalog\n", type);
1554
    if (cur == NULL)
1555
	catal->children = xmlNewCatalogEntry(typ, orig, replace,
1556
		                             NULL, catal->prefer, NULL);
1557
    else
1558
	cur->next = xmlNewCatalogEntry(typ, orig, replace,
1559
		                       NULL, catal->prefer, NULL);
1560
    if (doregister) {
1561
        catal->type = XML_CATA_CATALOG;