1 /*
2  * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * Licensed to the Apache Software Foundation (ASF) under one or more
6  * contributor license agreements.  See the NOTICE file distributed with
7  * this work for additional information regarding copyright ownership.
8  * The ASF licenses this file to You under the Apache License, Version 2.0
9  * (the "License"); you may not use this file except in compliance with
10  * the License.  You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 // Catalog.java - Represents OASIS Open Catalog files.
21 package com.sun.org.apache.xml.internal.resolver;
22 
23 import com.sun.org.apache.xerces.internal.utils.SecuritySupport;
24 import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
25 import com.sun.org.apache.xml.internal.resolver.helpers.PublicId;
26 import com.sun.org.apache.xml.internal.resolver.readers.CatalogReader;
27 import com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader;
28 import com.sun.org.apache.xml.internal.resolver.readers.SAXCatalogReader;
29 import com.sun.org.apache.xml.internal.resolver.readers.TR9401CatalogReader;
30 
31 import java.io.DataInputStream;
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.UnsupportedEncodingException;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 import java.util.Enumeration;
39 import java.util.HashMap;
40 import java.util.Map;
41 import java.util.Vector;
42 import javax.xml.parsers.SAXParserFactory;
43 import jdk.xml.internal.JdkXmlUtils;
44 
45 /**
46  * Represents OASIS Open Catalog files.
47  *
48  * <p>This class implements the semantics of OASIS Open Catalog files
49  * (defined by
50  * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Technical
51  * Resolution 9401:1997 (Amendment 2 to TR 9401)</a>).</p>
52  *
53  * <p>The primary purpose of the Catalog is to associate resources in the
54  * document with local system identifiers. Some entities
55  * (document types, XML entities, and notations) have names and all of them
56  * can have either public or system identifiers or both. (In XML, only a
57  * notation can have a public identifier without a system identifier, but
58  * the methods implemented in this class obey the Catalog semantics
59  * from the SGML
60  * days when system identifiers were optional.)</p>
61  *
62  * <p>The system identifiers returned by the resolution methods in this
63  * class are valid, i.e. usable by, and in fact constructed by, the
64  * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in
65  * somewhat non-standard ways and the system identifiers returned may
66  * not be directly usable in a browser or filesystem context.
67  *
68  * <p>This class recognizes all of the Catalog entries defined in
69  * TR9401:1997:</p>
70  *
71  * <ul>
72  * <li><b>BASE</b>
73  * changes the base URI for resolving relative system identifiers. The
74  * initial base URI is the URI of the location of the catalog (which is,
75  * in turn, relative to the location of the current working directory
76  * at startup, as returned by the <tt>user.dir</tt> system property).</li>
77  * <li><b>CATALOG</b>
78  * processes other catalog files. An included catalog occurs logically
79  * at the end of the including catalog.</li>
80  * <li><b>DELEGATE_PUBLIC</b>
81  * specifies alternate catalogs for some public identifiers. The delegated
82  * catalogs are not loaded until they are needed, but they are cached
83  * once loaded.</li>
84  * <li><b>DELEGATE_SYSTEM</b>
85  * specifies alternate catalogs for some system identifiers. The delegated
86  * catalogs are not loaded until they are needed, but they are cached
87  * once loaded.</li>
88  * <li><b>DELEGATE_URI</b>
89  * specifies alternate catalogs for some URIs. The delegated
90  * catalogs are not loaded until they are needed, but they are cached
91  * once loaded.</li>
92  * <li><b>REWRITE_SYSTEM</b>
93  * specifies alternate prefix for a system identifier.</li>
94  * <li><b>REWRITE_URI</b>
95  * specifies alternate prefix for a URI.</li>
96  * <li><b>SYSTEM_SUFFIX</b>
97  * maps any system identifier that ends with a particular suffix to another
98  * system identifier.</li>
99  * <li><b>URI_SUFFIX</b>
100  * maps any URI that ends with a particular suffix to another URI.</li>
101  * <li><b>DOCTYPE</b>
102  * associates the names of root elements with URIs. (In other words, an XML
103  * processor might infer the doctype of an XML document that does not include
104  * a doctype declaration by looking for the DOCTYPE entry in the
105  * catalog which matches the name of the root element of the document.)</li>
106  * <li><b>DOCUMENT</b>
107  * provides a default document.</li>
108  * <li><b>DTDDECL</b>
109  * recognized and silently ignored. Not relevant for XML.</li>
110  * <li><b>ENTITY</b>
111  * associates entity names with URIs.</li>
112  * <li><b>LINKTYPE</b>
113  * recognized and silently ignored. Not relevant for XML.</li>
114  * <li><b>NOTATION</b>
115  * associates notation names with URIs.</li>
116  * <li><b>OVERRIDE</b>
117  * changes the override behavior. Initial behavior is set by the
118  * system property <tt>xml.catalog.override</tt>. The default initial
119  * behavior is 'YES', that is, entries in the catalog override
120  * system identifiers specified in the document.</li>
121  * <li><b>PUBLIC</b>
122  * maps a public identifier to a system identifier.</li>
123  * <li><b>SGMLDECL</b>
124  * recognized and silently ignored. Not relevant for XML.</li>
125  * <li><b>SYSTEM</b>
126  * maps a system identifier to another system identifier.</li>
127  * <li><b>URI</b>
128  * maps a URI to another URI.</li>
129  * </ul>
130  *
131  * <p>Note that BASE entries are treated as described by RFC2396. In
132  * particular, this has the counter-intuitive property that after a BASE
133  * entry identifing "http://example.com/a/b/c" as the base URI,
134  * the relative URI "foo" is resolved to the absolute URI
135  * "http://example.com/a/b/foo". You must provide the trailing slash if
136  * you do not want the final component of the path to be discarded as a
137  * filename would in a URI for a resource: "http://example.com/a/b/c/".
138  * </p>
139  *
140  * <p>Note that subordinate catalogs (all catalogs except the first,
141  * including CATALOG and DELEGATE* catalogs) are only loaded if and when
142  * they are required.</p>
143  *
144  * <p>This class relies on classes which implement the CatalogReader
145  * interface to actually load catalog files. This allows the catalog
146  * semantics to be implemented for TR9401 text-based catalogs, XML
147  * catalogs, or any number of other storage formats.</p>
148  *
149  * <p>Additional catalogs may also be loaded with the
150  * {@link #parseCatalog} method.</p>
151  * </dd>
152  * </dl>
153  *
154  * <p><b>Change Log:</b></p>
155  * <dl>
156  * <dt>2.0</dt>
157  * <dd><p>Rewrite to use CatalogReaders.</p></dd>
158  * <dt>1.1</dt>
159  * <dd><p>Allow quoted components in <tt>xml.catalog.files</tt>
160  * so that URLs containing colons can be used on Unix.
161  * The string passed to <tt>xml.catalog.files</tt> can now have the form:</p>
162  * <pre>
163  * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars'
164  * </pre>
165  * <p>(Where ":" is the separater character in this example.)</p>
166  * <p>If an unquoted path contains an embedded double or single quote
167  * character, no special processig is performed on that character. No
168  * path can contain separater characters, double, and single quotes
169  * simultaneously.</p>
170  * <p>Fix bug in calculation of BASE entries: if
171  * a catalog contains multiple BASE entries, each is relative to the preceding
172  * base, not the default base URI of the catalog.</p>
173  * </dd>
174  * <dt>1.0.1</dt>
175  * <dd><p>Fixed a bug in the calculation of the list of subordinate catalogs.
176  * This bug caused an infinite loop where parsing would alternately process
177  * two catalogs indefinitely.</p>
178  * </dd>
179  * </dl>
180  *
181  * @see CatalogReader
182  * @see CatalogEntry
183  *
184  * @author Norman Walsh
185  * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
186  *
187  * @version 1.0
188  *
189  * <p>Derived from public domain code originally published by Arbortext,
190  * Inc.</p>
191  */
192 public class Catalog {
193   /** The BASE Catalog Entry type. */
194   public static final int BASE     = CatalogEntry.addEntryType("BASE", 1);
195 
196   /** The CATALOG Catalog Entry type. */
197   public static final int CATALOG  = CatalogEntry.addEntryType("CATALOG", 1);
198 
199   /** The DOCUMENT Catalog Entry type. */
200   public static final int DOCUMENT = CatalogEntry.addEntryType("DOCUMENT", 1);
201 
202   /** The OVERRIDE Catalog Entry type. */
203   public static final int OVERRIDE = CatalogEntry.addEntryType("OVERRIDE", 1);
204 
205   /** The SGMLDECL Catalog Entry type. */
206   public static final int SGMLDECL = CatalogEntry.addEntryType("SGMLDECL", 1);
207 
208   /** The DELEGATE_PUBLIC Catalog Entry type. */
209   public static final int DELEGATE_PUBLIC = CatalogEntry.addEntryType("DELEGATE_PUBLIC", 2);
210 
211   /** The DELEGATE_SYSTEM Catalog Entry type. */
212   public static final int DELEGATE_SYSTEM = CatalogEntry.addEntryType("DELEGATE_SYSTEM", 2);
213 
214   /** The DELEGATE_URI Catalog Entry type. */
215   public static final int DELEGATE_URI = CatalogEntry.addEntryType("DELEGATE_URI", 2);
216 
217   /** The DOCTYPE Catalog Entry type. */
218   public static final int DOCTYPE  = CatalogEntry.addEntryType("DOCTYPE", 2);
219 
220   /** The DTDDECL Catalog Entry type. */
221   public static final int DTDDECL  = CatalogEntry.addEntryType("DTDDECL", 2);
222 
223   /** The ENTITY Catalog Entry type. */
224   public static final int ENTITY   = CatalogEntry.addEntryType("ENTITY", 2);
225 
226   /** The LINKTYPE Catalog Entry type. */
227   public static final int LINKTYPE = CatalogEntry.addEntryType("LINKTYPE", 2);
228 
229   /** The NOTATION Catalog Entry type. */
230   public static final int NOTATION = CatalogEntry.addEntryType("NOTATION", 2);
231 
232   /** The PUBLIC Catalog Entry type. */
233   public static final int PUBLIC   = CatalogEntry.addEntryType("PUBLIC", 2);
234 
235   /** The SYSTEM Catalog Entry type. */
236   public static final int SYSTEM   = CatalogEntry.addEntryType("SYSTEM", 2);
237 
238   /** The URI Catalog Entry type. */
239   public static final int URI      = CatalogEntry.addEntryType("URI", 2);
240 
241   /** The REWRITE_SYSTEM Catalog Entry type. */
242   public static final int REWRITE_SYSTEM = CatalogEntry.addEntryType("REWRITE_SYSTEM", 2);
243 
244   /** The REWRITE_URI Catalog Entry type. */
245   public static final int REWRITE_URI = CatalogEntry.addEntryType("REWRITE_URI", 2);
246   /** The SYSTEM_SUFFIX Catalog Entry type. */
247   public static final int SYSTEM_SUFFIX = CatalogEntry.addEntryType("SYSTEM_SUFFIX", 2);
248   /** The URI_SUFFIX Catalog Entry type. */
249   public static final int URI_SUFFIX = CatalogEntry.addEntryType("URI_SUFFIX", 2);
250 
251   /**
252    * The base URI for relative system identifiers in the catalog.
253    * This may be changed by BASE entries in the catalog.
254    */
255   protected URL base;
256 
257   /** The base URI of the Catalog file currently being parsed. */
258   protected URL catalogCwd;
259 
260   /** The catalog entries currently known to the system. */
261   protected Vector catalogEntries = new Vector();
262 
263   /** The default initial override setting. */
264   protected boolean default_override = true;
265 
266   /** The catalog manager in use for this instance. */
267   protected CatalogManager catalogManager = CatalogManager.getStaticManager();
268 
269   /**
270    * A vector of catalog files to be loaded.
271    *
272    * <p>This list is initially established by
273    * <code>loadSystemCatalogs</code> when
274    * it parses the system catalog list, but CATALOG entries may
275    * contribute to it during the course of parsing.</p>
276    *
277    * @see #loadSystemCatalogs
278    * @see #localCatalogFiles
279    */
280   protected Vector catalogFiles = new Vector();
281 
282   /**
283    * A vector of catalog files constructed during processing of
284    * CATALOG entries in the current catalog.
285    *
286    * <p>This two-level system is actually necessary to correctly implement
287    * the semantics of the CATALOG entry. If one catalog file includes
288    * another with a CATALOG entry, the included catalog logically
289    * occurs <i>at the end</i> of the including catalog, and after any
290    * preceding CATALOG entries. In other words, the CATALOG entry
291    * cannot insert anything into the middle of a catalog file.</p>
292    *
293    * <p>When processing reaches the end of each catalog files, any
294    * elements on this vector are added to the front of the
295    * <code>catalogFiles</code> vector.</p>
296    *
297    * @see #catalogFiles
298    */
299   protected Vector localCatalogFiles = new Vector();
300 
301   /**
302    * A vector of Catalogs.
303    *
304    * <p>The semantics of Catalog resolution are such that each
305    * catalog is effectively a list of Catalogs (in other words,
306    * a recursive list of Catalog instances).</p>
307    *
308    * <p>Catalogs that are processed as the result of CATALOG or
309    * DELEGATE* entries are subordinate to the catalog that contained
310    * them, but they may in turn have subordinate catalogs.</p>
311    *
312    * <p>Catalogs are only loaded when they are needed, so this vector
313    * initially contains a list of Catalog filenames (URLs). If, during
314    * processing, one of these catalogs has to be loaded, the resulting
315    * Catalog object is placed in the vector, effectively caching it
316    * for the next query.</p>
317    */
318   protected Vector catalogs = new Vector();
319 
320   /**
321    * A vector of DELEGATE* Catalog entries constructed during
322    * processing of the Catalog.
323    *
324    * <p>This two-level system has two purposes; first, it allows
325    * us to sort the DELEGATE* entries by the length of the partial
326    * public identifier so that a linear search encounters them in
327    * the correct order and second, it puts them all at the end of
328    * the Catalog.</p>
329    *
330    * <p>When processing reaches the end of each catalog file, any
331    * elements on this vector are added to the end of the
332    * <code>catalogEntries</code> vector. This assures that matching
333    * PUBLIC keywords are encountered before DELEGATE* entries.</p>
334    */
335   protected Vector localDelegate = new Vector();
336 
337   /**
338    * A hash of CatalogReaders.
339    *
340    * <p>This hash maps MIME types to elements in the readerArr
341    * vector. This allows the Catalog to quickly locate the reader
342    * for a particular MIME type.</p>
343    */
344   protected Map<String, Integer> readerMap = new HashMap<>();
345 
346   /**
347    * A vector of CatalogReaders.
348    *
349    * <p>This vector contains all of the readers in the order that they
350    * were added. In the event that a catalog is read from a file, where
351    * the MIME type is unknown, each reader is attempted in turn until
352    * one succeeds.</p>
353    */
354   protected Vector readerArr = new Vector();
355 
356   /**
357    * Constructs an empty Catalog.
358    *
359    * <p>The constructor interrogates the relevant system properties
360    * using the default (static) CatalogManager
361    * and initializes the catalog data structures.</p>
362    */
Catalog()363   public Catalog() {
364     // nop;
365   }
366 
367   /**
368    * Constructs an empty Catalog with a specific CatalogManager.
369    *
370    * <p>The constructor interrogates the relevant system properties
371    * using the specified Catalog Manager
372    * and initializes the catalog data structures.</p>
373    */
Catalog(CatalogManager manager)374   public Catalog(CatalogManager manager) {
375     catalogManager = manager;
376   }
377 
378   /**
379    * Return the CatalogManager used by this catalog.
380    *
381    */
getCatalogManager()382   public CatalogManager getCatalogManager() {
383     return catalogManager;
384   }
385 
386   /**
387    * Establish the CatalogManager used by this catalog.
388    *
389    */
setCatalogManager(CatalogManager manager)390   public void setCatalogManager(CatalogManager manager) {
391     catalogManager = manager;
392   }
393 
394   /**
395    * Setup readers.
396    */
setupReaders()397   public void setupReaders() {
398     SAXParserFactory spf = JdkXmlUtils.getSAXFactory(catalogManager.overrideDefaultParser());
399     spf.setValidating(false);
400 
401     SAXCatalogReader saxReader = new SAXCatalogReader(spf);
402 
403     saxReader.setCatalogParser(null, "XMLCatalog",
404                                "com.sun.org.apache.xml.internal.resolver.readers.XCatalogReader");
405 
406     saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName,
407                                "catalog",
408                                "com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader");
409 
410     addReader("application/xml", saxReader);
411 
412     TR9401CatalogReader textReader = new TR9401CatalogReader();
413     addReader("text/plain", textReader);
414   }
415 
416   /**
417    * Add a new CatalogReader to the Catalog.
418    *
419    * <p>This method allows you to add a new CatalogReader to the
420    * catalog. The reader will be associated with the specified mimeType.
421    * You can only have one reader per mimeType.</p>
422    *
423    * <p>In the absence of a mimeType (e.g., when reading a catalog
424    * directly from a file on the local system), the readers are attempted
425    * in the order that you add them to the Catalog.</p>
426    *
427    * <p>Note that subordinate catalogs (created by CATALOG or
428    * DELEGATE* entries) get a copy of the set of readers present in
429    * the primary catalog when they are created. Readers added subsequently
430    * will not be available. For this reason, it is best to add all
431    * of the readers before the first call to parse a catalog.</p>
432    *
433    * @param mimeType The MIME type associated with this reader.
434    * @param reader The CatalogReader to use.
435    */
addReader(String mimeType, CatalogReader reader)436   public void addReader(String mimeType, CatalogReader reader) {
437     if (readerMap.containsKey(mimeType)) {
438       Integer pos = readerMap.get(mimeType);
439       readerArr.set(pos, reader);
440     } else {
441       readerArr.add(reader);
442       Integer pos = readerArr.size()-1;
443       readerMap.put(mimeType, pos);
444     }
445   }
446 
447   /**
448    * Copies the reader list from the current Catalog to a new Catalog.
449    *
450    * <p>This method is used internally when constructing a new catalog.
451    * It copies the current reader associations over to the new catalog.
452    * </p>
453    *
454    * @param newCatalog The new Catalog.
455    */
copyReaders(Catalog newCatalog)456   protected void copyReaders(Catalog newCatalog) {
457     // Have to copy the readers in the right order...convert hash to arr
458     Vector mapArr = new Vector(readerMap.size());
459 
460     // Pad the mapArr out to the right length
461     for (int count = 0; count < readerMap.size(); count++) {
462       mapArr.add(null);
463     }
464 
465     for (Map.Entry<String, Integer> entry : readerMap.entrySet()) {
466         mapArr.set(entry.getValue(), entry.getKey());
467     }
468 
469     for (int count = 0; count < mapArr.size(); count++) {
470       String mimeType = (String) mapArr.get(count);
471       Integer pos = readerMap.get(mimeType);
472       newCatalog.addReader(mimeType,
473                            (CatalogReader)
474                            readerArr.get(pos));
475     }
476   }
477 
478   /**
479    * Create a new Catalog object.
480    *
481    * <p>This method constructs a new instance of the running Catalog
482    * class (which might be a subtype of com.sun.org.apache.xml.internal.resolver.Catalog).
483    * All new catalogs are managed by the same CatalogManager.
484    * </p>
485    *
486    * <p>N.B. All Catalog subtypes should call newCatalog() to construct
487    * a new Catalog. Do not simply use "new Subclass()" since that will
488    * confuse future subclasses.</p>
489    */
newCatalog()490   protected Catalog newCatalog() {
491     String catalogClass = this.getClass().getName();
492 
493     try {
494       Catalog c = (Catalog) (Class.forName(catalogClass).newInstance());
495       c.setCatalogManager(catalogManager);
496       copyReaders(c);
497       return c;
498     } catch (ClassNotFoundException cnfe) {
499       catalogManager.debug.message(1, "Class Not Found Exception: " + catalogClass);
500     } catch (IllegalAccessException iae) {
501       catalogManager.debug.message(1, "Illegal Access Exception: " + catalogClass);
502     } catch (InstantiationException ie) {
503       catalogManager.debug.message(1, "Instantiation Exception: " + catalogClass);
504     } catch (ClassCastException cce) {
505       catalogManager.debug.message(1, "Class Cast Exception: " + catalogClass);
506     } catch (Exception e) {
507       catalogManager.debug.message(1, "Other Exception: " + catalogClass);
508     }
509 
510     Catalog c = new Catalog();
511     c.setCatalogManager(catalogManager);
512     copyReaders(c);
513     return c;
514   }
515 
516   /**
517    * Returns the current base URI.
518    */
getCurrentBase()519   public String getCurrentBase() {
520     return base.toString();
521   }
522 
523   /**
524    * Returns the default override setting associated with this
525    * catalog.
526    *
527    * <p>All catalog files loaded by this catalog will have the
528    * initial override setting specified by this default.</p>
529    */
getDefaultOverride()530   public String getDefaultOverride() {
531     if (default_override) {
532       return "yes";
533     } else {
534       return "no";
535     }
536   }
537 
538   /**
539    * Load the system catalog files.
540    *
541    * <p>The method adds all of the
542    * catalogs specified in the <tt>xml.catalog.files</tt> property
543    * to the Catalog list.</p>
544    *
545    * @throws MalformedURLException  One of the system catalogs is
546    * identified with a filename that is not a valid URL.
547    * @throws IOException One of the system catalogs cannot be read.
548    */
loadSystemCatalogs()549   public void loadSystemCatalogs()
550     throws MalformedURLException, IOException {
551 
552     Vector catalogs = catalogManager.getCatalogFiles();
553     if (catalogs != null) {
554       for (int count = 0; count < catalogs.size(); count++) {
555         catalogFiles.addElement(catalogs.elementAt(count));
556       }
557     }
558 
559     if (catalogFiles.size() > 0) {
560       // This is a little odd. The parseCatalog() method expects
561       // a filename, but it adds that name to the end of the
562       // catalogFiles vector, and then processes that vector.
563       // This allows the system to handle CATALOG entries
564       // correctly.
565       //
566       // In this init case, we take the last element off the
567       // catalogFiles vector and pass it to parseCatalog. This
568       // will "do the right thing" in the init case, and allow
569       // parseCatalog() to do the right thing in the non-init
570       // case. Honest.
571       //
572       String catfile = (String) catalogFiles.lastElement();
573       catalogFiles.removeElement(catfile);
574       parseCatalog(catfile);
575     }
576   }
577 
578   /**
579    * Parse a catalog file, augmenting internal data structures.
580    *
581    * @param fileName The filename of the catalog file to process
582    *
583    * @throws MalformedURLException The fileName cannot be turned into
584    * a valid URL.
585    * @throws IOException Error reading catalog file.
586    */
parseCatalog(String fileName)587   public synchronized void parseCatalog(String fileName)
588     throws MalformedURLException, IOException {
589 
590     default_override = catalogManager.getPreferPublic();
591     catalogManager.debug.message(4, "Parse catalog: " + fileName);
592 
593     // Put the file into the list of catalogs to process...
594     // In all cases except the case when initCatalog() is the
595     // caller, this will be the only catalog initially in the list...
596     catalogFiles.addElement(fileName);
597 
598     // Now process all the pending catalogs...
599     parsePendingCatalogs();
600   }
601 
602   /**
603    * Parse a catalog file, augmenting internal data structures.
604    *
605    * <p>Catalogs retrieved over the net may have an associated MIME type.
606    * The MIME type can be used to select an appropriate reader.</p>
607    *
608    * @param mimeType The MIME type of the catalog file.
609    * @param is The InputStream from which the catalog should be read
610    *
611    * @throws CatalogException Failed to load catalog
612    * mimeType.
613    * @throws IOException Error reading catalog file.
614    */
parseCatalog(String mimeType, InputStream is)615   public synchronized void parseCatalog(String mimeType, InputStream is)
616     throws IOException, CatalogException {
617 
618     default_override = catalogManager.getPreferPublic();
619     catalogManager.debug.message(4, "Parse " + mimeType + " catalog on input stream");
620 
621     CatalogReader reader = null;
622 
623     if (readerMap.containsKey(mimeType)) {
624       int arrayPos = ((Integer) readerMap.get(mimeType)).intValue();
625       reader = (CatalogReader) readerArr.get(arrayPos);
626     }
627 
628     if (reader == null) {
629       String msg = "No CatalogReader for MIME type: " + mimeType;
630       catalogManager.debug.message(2, msg);
631       throw new CatalogException(CatalogException.UNPARSEABLE, msg);
632     }
633 
634     reader.readCatalog(this, is);
635 
636     // Now process all the pending catalogs...
637     parsePendingCatalogs();
638   }
639 
640   /**
641    * Parse a catalog document, augmenting internal data structures.
642    *
643    * <p>This method supports catalog files stored in jar files: e.g.,
644    * jar:file:///path/to/filename.jar!/path/to/catalog.xml". That URI
645    * doesn't survive transmogrification through the URI processing that
646    * the parseCatalog(String) performs and passing it as an input stream
647    * doesn't set the base URI appropriately.</p>
648    *
649    * <p>Written by Stefan Wachter (2002-09-26)</p>
650    *
651    * @param aUrl The URL of the catalog document to process
652    *
653    * @throws IOException Error reading catalog file.
654    */
parseCatalog(URL aUrl)655   public synchronized void parseCatalog(URL aUrl) throws IOException {
656     catalogCwd = aUrl;
657     base = aUrl;
658 
659     default_override = catalogManager.getPreferPublic();
660     catalogManager.debug.message(4, "Parse catalog: " + aUrl.toString());
661 
662     DataInputStream inStream = null;
663     boolean parsed = false;
664 
665     for (int count = 0; !parsed && count < readerArr.size(); count++) {
666       CatalogReader reader = (CatalogReader) readerArr.get(count);
667 
668       try {
669         inStream = new DataInputStream(aUrl.openStream());
670       } catch (FileNotFoundException fnfe) {
671         // No catalog; give up!
672         break;
673       }
674 
675       try {
676         reader.readCatalog(this, inStream);
677         parsed=true;
678       } catch (CatalogException ce) {
679         if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
680           // give up!
681           break;
682         } else {
683           // try again!
684         }
685       }
686 
687       try {
688         inStream.close();
689       } catch (IOException e) {
690         //nop
691       }
692     }
693 
694     if (parsed) parsePendingCatalogs();
695   }
696 
697   /**
698    * Parse all of the pending catalogs.
699    *
700    * <p>Catalogs may refer to other catalogs, this method parses
701    * all of the currently pending catalog files.</p>
702    */
parsePendingCatalogs()703   protected synchronized void parsePendingCatalogs()
704     throws MalformedURLException, IOException {
705 
706     if (!localCatalogFiles.isEmpty()) {
707       // Move all the localCatalogFiles into the front of
708       // the catalogFiles queue
709       Vector newQueue = new Vector();
710       Enumeration q = localCatalogFiles.elements();
711       while (q.hasMoreElements()) {
712         newQueue.addElement(q.nextElement());
713       }
714 
715       // Put the rest of the catalogs on the end of the new list
716       for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
717         String catfile = (String) catalogFiles.elementAt(curCat);
718         newQueue.addElement(catfile);
719       }
720 
721       catalogFiles = newQueue;
722       localCatalogFiles.clear();
723     }
724 
725     // Suppose there are no catalog files to process, but the
726     // single catalog already parsed included some delegate
727     // entries? Make sure they don't get lost.
728     if (catalogFiles.isEmpty() && !localDelegate.isEmpty()) {
729       Enumeration e = localDelegate.elements();
730       while (e.hasMoreElements()) {
731         catalogEntries.addElement(e.nextElement());
732       }
733       localDelegate.clear();
734     }
735 
736     // Now process all the files on the catalogFiles vector. This
737     // vector can grow during processing if CATALOG entries are
738     // encountered in the catalog
739     while (!catalogFiles.isEmpty()) {
740       String catfile = (String) catalogFiles.elementAt(0);
741       try {
742         catalogFiles.remove(0);
743       } catch (ArrayIndexOutOfBoundsException e) {
744         // can't happen
745       }
746 
747       if (catalogEntries.size() == 0 && catalogs.size() == 0) {
748         // We haven't parsed any catalogs yet, let this
749         // catalog be the first...
750         try {
751           parseCatalogFile(catfile);
752         } catch (CatalogException ce) {
753           System.out.println("FIXME: " + ce.toString());
754         }
755       } else {
756         // This is a subordinate catalog. We save its name,
757         // but don't bother to load it unless it's necessary.
758         catalogs.addElement(catfile);
759       }
760 
761       if (!localCatalogFiles.isEmpty()) {
762         // Move all the localCatalogFiles into the front of
763         // the catalogFiles queue
764         Vector newQueue = new Vector();
765         Enumeration q = localCatalogFiles.elements();
766         while (q.hasMoreElements()) {
767           newQueue.addElement(q.nextElement());
768         }
769 
770         // Put the rest of the catalogs on the end of the new list
771         for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
772           catfile = (String) catalogFiles.elementAt(curCat);
773           newQueue.addElement(catfile);
774         }
775 
776         catalogFiles = newQueue;
777         localCatalogFiles.clear();
778       }
779 
780       if (!localDelegate.isEmpty()) {
781         Enumeration e = localDelegate.elements();
782         while (e.hasMoreElements()) {
783           catalogEntries.addElement(e.nextElement());
784         }
785         localDelegate.clear();
786       }
787     }
788 
789     // We've parsed them all, reinit the vector...
790     catalogFiles.clear();
791   }
792 
793   /**
794    * Parse a single catalog file, augmenting internal data structures.
795    *
796    * @param fileName The filename of the catalog file to process
797    *
798    * @throws MalformedURLException The fileName cannot be turned into
799    * a valid URL.
800    * @throws IOException Error reading catalog file.
801    */
parseCatalogFile(String fileName)802   protected synchronized void parseCatalogFile(String fileName)
803     throws MalformedURLException, IOException, CatalogException {
804 
805     CatalogEntry entry;
806 
807     // The base-base is the cwd. If the catalog file is specified
808     // with a relative path, this assures that it gets resolved
809     // properly...
810     try {
811       // tack on a basename because URLs point to files not dirs
812       catalogCwd = FileURL.makeURL("basename");
813     } catch (MalformedURLException e) {
814       catalogManager.debug.message(1, "Malformed URL on cwd", "user.dir");
815       catalogCwd = null;
816     }
817 
818     // The initial base URI is the location of the catalog file
819     try {
820       base = new URL(catalogCwd, fixSlashes(fileName));
821     } catch (MalformedURLException e) {
822       try {
823         base = new URL("file:" + fixSlashes(fileName));
824       } catch (MalformedURLException e2) {
825         catalogManager.debug.message(1, "Malformed URL on catalog filename",
826                       fixSlashes(fileName));
827         base = null;
828       }
829     }
830 
831     catalogManager.debug.message(2, "Loading catalog", fileName);
832     catalogManager.debug.message(4, "Default BASE", base.toString());
833 
834     fileName = base.toString();
835 
836     DataInputStream inStream = null;
837     boolean parsed = false;
838     boolean notFound = false;
839 
840     for (int count = 0; !parsed && count < readerArr.size(); count++) {
841       CatalogReader reader = (CatalogReader) readerArr.get(count);
842 
843       try {
844         notFound = false;
845         inStream = new DataInputStream(base.openStream());
846       } catch (FileNotFoundException fnfe) {
847         // No catalog; give up!
848         notFound = true;
849         break;
850       }
851 
852       try {
853         reader.readCatalog(this, inStream);
854         parsed = true;
855       } catch (CatalogException ce) {
856         if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
857           // give up!
858           break;
859         } else {
860           // try again!
861         }
862       }
863 
864       try {
865         inStream.close();
866       } catch (IOException e) {
867         //nop
868       }
869     }
870 
871     if (!parsed) {
872       if (notFound) {
873         catalogManager.debug.message(3, "Catalog does not exist", fileName);
874       } else {
875         catalogManager.debug.message(1, "Failed to parse catalog", fileName);
876       }
877     }
878   }
879 
880   /**
881    * Cleanup and process a Catalog entry.
882    *
883    * <p>This method processes each Catalog entry, changing mapped
884    * relative system identifiers into absolute ones (based on the current
885    * base URI), and maintaining other information about the current
886    * catalog.</p>
887    *
888    * @param entry The CatalogEntry to process.
889    */
addEntry(CatalogEntry entry)890   public void addEntry(CatalogEntry entry) {
891     int type = entry.getEntryType();
892 
893     if (type == BASE) {
894       String value = entry.getEntryArg(0);
895       URL newbase = null;
896 
897       if (base == null) {
898         catalogManager.debug.message(5, "BASE CUR", "null");
899       } else {
900         catalogManager.debug.message(5, "BASE CUR", base.toString());
901       }
902       catalogManager.debug.message(4, "BASE STR", value);
903 
904       try {
905         value = fixSlashes(value);
906         newbase = new URL(base, value);
907       } catch (MalformedURLException e) {
908         try {
909           newbase = new URL("file:" + value);
910         } catch (MalformedURLException e2) {
911           catalogManager.debug.message(1, "Malformed URL on base", value);
912           newbase = null;
913         }
914       }
915 
916       if (newbase != null) {
917         base = newbase;
918       }
919 
920       catalogManager.debug.message(5, "BASE NEW", base.toString());
921     } else if (type == CATALOG) {
922       String fsi = makeAbsolute(entry.getEntryArg(0));
923 
924       catalogManager.debug.message(4, "CATALOG", fsi);
925 
926       localCatalogFiles.addElement(fsi);
927     } else if (type == PUBLIC) {
928       String publicid = PublicId.normalize(entry.getEntryArg(0));
929       String systemid = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
930 
931       entry.setEntryArg(0, publicid);
932       entry.setEntryArg(1, systemid);
933 
934       catalogManager.debug.message(4, "PUBLIC", publicid, systemid);
935 
936       catalogEntries.addElement(entry);
937     } else if (type == SYSTEM) {
938       String systemid = normalizeURI(entry.getEntryArg(0));
939       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
940 
941       entry.setEntryArg(1, fsi);
942 
943       catalogManager.debug.message(4, "SYSTEM", systemid, fsi);
944 
945       catalogEntries.addElement(entry);
946     } else if (type == URI) {
947       String uri = normalizeURI(entry.getEntryArg(0));
948       String altURI = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
949 
950       entry.setEntryArg(1, altURI);
951 
952       catalogManager.debug.message(4, "URI", uri, altURI);
953 
954       catalogEntries.addElement(entry);
955     } else if (type == DOCUMENT) {
956       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
957       entry.setEntryArg(0, fsi);
958 
959       catalogManager.debug.message(4, "DOCUMENT", fsi);
960 
961       catalogEntries.addElement(entry);
962     } else if (type == OVERRIDE) {
963       catalogManager.debug.message(4, "OVERRIDE", entry.getEntryArg(0));
964 
965       catalogEntries.addElement(entry);
966     } else if (type == SGMLDECL) {
967       // meaningless in XML
968       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
969       entry.setEntryArg(0, fsi);
970 
971       catalogManager.debug.message(4, "SGMLDECL", fsi);
972 
973       catalogEntries.addElement(entry);
974     } else if (type == DELEGATE_PUBLIC) {
975       String ppi = PublicId.normalize(entry.getEntryArg(0));
976       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
977 
978       entry.setEntryArg(0, ppi);
979       entry.setEntryArg(1, fsi);
980 
981       catalogManager.debug.message(4, "DELEGATE_PUBLIC", ppi, fsi);
982 
983       addDelegate(entry);
984     } else if (type == DELEGATE_SYSTEM) {
985       String psi = normalizeURI(entry.getEntryArg(0));
986       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
987 
988       entry.setEntryArg(0, psi);
989       entry.setEntryArg(1, fsi);
990 
991       catalogManager.debug.message(4, "DELEGATE_SYSTEM", psi, fsi);
992 
993       addDelegate(entry);
994     } else if (type == DELEGATE_URI) {
995       String pui = normalizeURI(entry.getEntryArg(0));
996       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
997 
998       entry.setEntryArg(0, pui);
999       entry.setEntryArg(1, fsi);
1000 
1001       catalogManager.debug.message(4, "DELEGATE_URI", pui, fsi);
1002 
1003       addDelegate(entry);
1004     } else if (type == REWRITE_SYSTEM) {
1005       String psi = normalizeURI(entry.getEntryArg(0));
1006       String rpx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1007 
1008       entry.setEntryArg(0, psi);
1009       entry.setEntryArg(1, rpx);
1010 
1011       catalogManager.debug.message(4, "REWRITE_SYSTEM", psi, rpx);
1012 
1013       catalogEntries.addElement(entry);
1014     } else if (type == REWRITE_URI) {
1015       String pui = normalizeURI(entry.getEntryArg(0));
1016       String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1017 
1018       entry.setEntryArg(0, pui);
1019       entry.setEntryArg(1, upx);
1020 
1021       catalogManager.debug.message(4, "REWRITE_URI", pui, upx);
1022 
1023       catalogEntries.addElement(entry);
1024     } else if (type == SYSTEM_SUFFIX) {
1025       String pui = normalizeURI(entry.getEntryArg(0));
1026       String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1027 
1028       entry.setEntryArg(0, pui);
1029       entry.setEntryArg(1, upx);
1030 
1031       catalogManager.debug.message(4, "SYSTEM_SUFFIX", pui, upx);
1032 
1033       catalogEntries.addElement(entry);
1034     } else if (type == URI_SUFFIX) {
1035       String pui = normalizeURI(entry.getEntryArg(0));
1036       String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1037 
1038       entry.setEntryArg(0, pui);
1039       entry.setEntryArg(1, upx);
1040 
1041       catalogManager.debug.message(4, "URI_SUFFIX", pui, upx);
1042 
1043       catalogEntries.addElement(entry);
1044     } else if (type == DOCTYPE) {
1045       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1046       entry.setEntryArg(1, fsi);
1047 
1048       catalogManager.debug.message(4, "DOCTYPE", entry.getEntryArg(0), fsi);
1049 
1050       catalogEntries.addElement(entry);
1051     } else if (type == DTDDECL) {
1052       // meaningless in XML
1053       String fpi = PublicId.normalize(entry.getEntryArg(0));
1054       entry.setEntryArg(0, fpi);
1055       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1056       entry.setEntryArg(1, fsi);
1057 
1058       catalogManager.debug.message(4, "DTDDECL", fpi, fsi);
1059 
1060       catalogEntries.addElement(entry);
1061     } else if (type == ENTITY) {
1062       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1063       entry.setEntryArg(1, fsi);
1064 
1065       catalogManager.debug.message(4, "ENTITY", entry.getEntryArg(0), fsi);
1066 
1067       catalogEntries.addElement(entry);
1068     } else if (type == LINKTYPE) {
1069       // meaningless in XML
1070       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1071       entry.setEntryArg(1, fsi);
1072 
1073       catalogManager.debug.message(4, "LINKTYPE", entry.getEntryArg(0), fsi);
1074 
1075       catalogEntries.addElement(entry);
1076     } else if (type == NOTATION) {
1077       String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1078       entry.setEntryArg(1, fsi);
1079 
1080       catalogManager.debug.message(4, "NOTATION", entry.getEntryArg(0), fsi);
1081 
1082       catalogEntries.addElement(entry);
1083     } else {
1084       catalogEntries.addElement(entry);
1085     }
1086   }
1087 
1088   /**
1089    * Handle unknown CatalogEntry types.
1090    *
1091    * <p>This method exists to allow subclasses to deal with unknown
1092    * entry types.</p>
1093    */
unknownEntry(Vector strings)1094   public void unknownEntry(Vector strings) {
1095     if (strings != null && strings.size() > 0) {
1096       String keyword = (String) strings.elementAt(0);
1097       catalogManager.debug.message(2, "Unrecognized token parsing catalog", keyword);
1098     }
1099   }
1100 
1101   /**
1102    * Parse all subordinate catalogs.
1103    *
1104    * <p>This method recursively parses all of the subordinate catalogs.
1105    * If this method does not throw an exception, you can be confident that
1106    * no subsequent call to any resolve*() method will either, with two
1107    * possible exceptions:</p>
1108    *
1109    * <ol>
1110    * <li><p>Delegated catalogs are re-parsed each time they are needed
1111    * (because a variable list of them may be needed in each case,
1112    * depending on the length of the matching partial public identifier).</p>
1113    * <p>But they are parsed by this method, so as long as they don't
1114    * change or disappear while the program is running, they shouldn't
1115    * generate errors later if they don't generate errors now.</p>
1116    * <li><p>If you add new catalogs with <code>parseCatalog</code>, they
1117    * won't be loaded until they are needed or until you call
1118    * <code>parseAllCatalogs</code> again.</p>
1119    * </ol>
1120    *
1121    * <p>On the other hand, if you don't call this method, you may
1122    * successfully parse documents without having to load all possible
1123    * catalogs.</p>
1124    *
1125    * @throws MalformedURLException The filename (URL) for a
1126    * subordinate or delegated catalog is not a valid URL.
1127    * @throws IOException Error reading some subordinate or delegated
1128    * catalog file.
1129    */
parseAllCatalogs()1130   public void parseAllCatalogs()
1131     throws MalformedURLException, IOException {
1132 
1133     // Parse all the subordinate catalogs
1134     for (int catPos = 0; catPos < catalogs.size(); catPos++) {
1135       Catalog c = null;
1136 
1137       try {
1138         c = (Catalog) catalogs.elementAt(catPos);
1139       } catch (ClassCastException e) {
1140         String catfile = (String) catalogs.elementAt(catPos);
1141         c = newCatalog();
1142 
1143         c.parseCatalog(catfile);
1144         catalogs.setElementAt(c, catPos);
1145         c.parseAllCatalogs();
1146       }
1147     }
1148 
1149     // Parse all the DELEGATE catalogs
1150     Enumeration en = catalogEntries.elements();
1151     while (en.hasMoreElements()) {
1152       CatalogEntry e = (CatalogEntry) en.nextElement();
1153       if (e.getEntryType() == DELEGATE_PUBLIC
1154           || e.getEntryType() == DELEGATE_SYSTEM
1155           || e.getEntryType() == DELEGATE_URI) {
1156         Catalog dcat = newCatalog();
1157         dcat.parseCatalog(e.getEntryArg(1));
1158       }
1159     }
1160   }
1161 
1162 
1163   /**
1164    * Return the applicable DOCTYPE system identifier.
1165    *
1166    * @param entityName The name of the entity (element) for which
1167    * a doctype is required.
1168    * @param publicId The nominal public identifier for the doctype
1169    * (as provided in the source document).
1170    * @param systemId The nominal system identifier for the doctype
1171    * (as provided in the source document).
1172    *
1173    * @return The system identifier to use for the doctype.
1174    *
1175    * @throws MalformedURLException The formal system identifier of a
1176    * subordinate catalog cannot be turned into a valid URL.
1177    * @throws IOException Error reading subordinate catalog file.
1178    */
resolveDoctype(String entityName, String publicId, String systemId)1179   public String resolveDoctype(String entityName,
1180                                String publicId,
1181                                String systemId)
1182     throws MalformedURLException, IOException {
1183     String resolved = null;
1184 
1185     catalogManager.debug.message(3, "resolveDoctype("
1186                   +entityName+","+publicId+","+systemId+")");
1187 
1188     systemId = normalizeURI(systemId);
1189 
1190     if (publicId != null && publicId.startsWith("urn:publicid:")) {
1191       publicId = PublicId.decodeURN(publicId);
1192     }
1193 
1194     if (systemId != null && systemId.startsWith("urn:publicid:")) {
1195       systemId = PublicId.decodeURN(systemId);
1196       if (publicId != null && !publicId.equals(systemId)) {
1197         catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1198         systemId = null;
1199       } else {
1200         publicId = systemId;
1201         systemId = null;
1202       }
1203     }
1204 
1205     if (systemId != null) {
1206       // If there's a SYSTEM entry in this catalog, use it
1207       resolved = resolveLocalSystem(systemId);
1208       if (resolved != null) {
1209         return resolved;
1210       }
1211     }
1212 
1213     if (publicId != null) {
1214       // If there's a PUBLIC entry in this catalog, use it
1215       resolved = resolveLocalPublic(DOCTYPE,
1216                                     entityName,
1217                                     publicId,
1218                                     systemId);
1219       if (resolved != null) {
1220         return resolved;
1221       }
1222     }
1223 
1224     // If there's a DOCTYPE entry in this catalog, use it
1225     boolean over = default_override;
1226     Enumeration en = catalogEntries.elements();
1227     while (en.hasMoreElements()) {
1228       CatalogEntry e = (CatalogEntry) en.nextElement();
1229       if (e.getEntryType() == OVERRIDE) {
1230         over = e.getEntryArg(0).equalsIgnoreCase("YES");
1231         continue;
1232       }
1233 
1234       if (e.getEntryType() == DOCTYPE
1235           && e.getEntryArg(0).equals(entityName)) {
1236         if (over || systemId == null) {
1237           return e.getEntryArg(1);
1238         }
1239       }
1240     }
1241 
1242     // Otherwise, look in the subordinate catalogs
1243     return resolveSubordinateCatalogs(DOCTYPE,
1244                                       entityName,
1245                                       publicId,
1246                                       systemId);
1247   }
1248 
1249   /**
1250    * Return the applicable DOCUMENT entry.
1251    *
1252    * @return The system identifier to use for the doctype.
1253    *
1254    * @throws MalformedURLException The formal system identifier of a
1255    * subordinate catalog cannot be turned into a valid URL.
1256    * @throws IOException Error reading subordinate catalog file.
1257    */
resolveDocument()1258   public String resolveDocument()
1259     throws MalformedURLException, IOException {
1260     // If there's a DOCUMENT entry, return it
1261 
1262     catalogManager.debug.message(3, "resolveDocument");
1263 
1264     Enumeration en = catalogEntries.elements();
1265     while (en.hasMoreElements()) {
1266       CatalogEntry e = (CatalogEntry) en.nextElement();
1267       if (e.getEntryType() == DOCUMENT) {
1268         return e.getEntryArg(0);
1269       }
1270     }
1271 
1272     return resolveSubordinateCatalogs(DOCUMENT,
1273                                       null, null, null);
1274   }
1275 
1276   /**
1277    * Return the applicable ENTITY system identifier.
1278    *
1279    * @param entityName The name of the entity for which
1280    * a system identifier is required.
1281    * @param publicId The nominal public identifier for the entity
1282    * (as provided in the source document).
1283    * @param systemId The nominal system identifier for the entity
1284    * (as provided in the source document).
1285    *
1286    * @return The system identifier to use for the entity.
1287    *
1288    * @throws MalformedURLException The formal system identifier of a
1289    * subordinate catalog cannot be turned into a valid URL.
1290    * @throws IOException Error reading subordinate catalog file.
1291    */
resolveEntity(String entityName, String publicId, String systemId)1292   public String resolveEntity(String entityName,
1293                               String publicId,
1294                               String systemId)
1295     throws MalformedURLException, IOException {
1296     String resolved = null;
1297 
1298     catalogManager.debug.message(3, "resolveEntity("
1299                   +entityName+","+publicId+","+systemId+")");
1300 
1301     systemId = normalizeURI(systemId);
1302 
1303     if (publicId != null && publicId.startsWith("urn:publicid:")) {
1304       publicId = PublicId.decodeURN(publicId);
1305     }
1306 
1307     if (systemId != null && systemId.startsWith("urn:publicid:")) {
1308       systemId = PublicId.decodeURN(systemId);
1309       if (publicId != null && !publicId.equals(systemId)) {
1310         catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1311         systemId = null;
1312       } else {
1313         publicId = systemId;
1314         systemId = null;
1315       }
1316     }
1317 
1318     if (systemId != null) {
1319       // If there's a SYSTEM entry in this catalog, use it
1320       resolved = resolveLocalSystem(systemId);
1321       if (resolved != null) {
1322         return resolved;
1323       }
1324     }
1325 
1326     if (publicId != null) {
1327       // If there's a PUBLIC entry in this catalog, use it
1328       resolved = resolveLocalPublic(ENTITY,
1329                                     entityName,
1330                                     publicId,
1331                                     systemId);
1332       if (resolved != null) {
1333         return resolved;
1334       }
1335     }
1336 
1337     // If there's a ENTITY entry in this catalog, use it
1338     boolean over = default_override;
1339     Enumeration en = catalogEntries.elements();
1340     while (en.hasMoreElements()) {
1341       CatalogEntry e = (CatalogEntry) en.nextElement();
1342       if (e.getEntryType() == OVERRIDE) {
1343         over = e.getEntryArg(0).equalsIgnoreCase("YES");
1344         continue;
1345       }
1346 
1347       if (e.getEntryType() == ENTITY
1348           && e.getEntryArg(0).equals(entityName)) {
1349         if (over || systemId == null) {
1350           return e.getEntryArg(1);
1351         }
1352       }
1353     }
1354 
1355     // Otherwise, look in the subordinate catalogs
1356     return resolveSubordinateCatalogs(ENTITY,
1357                                       entityName,
1358                                       publicId,
1359                                       systemId);
1360   }
1361 
1362   /**
1363    * Return the applicable NOTATION system identifier.
1364    *
1365    * @param notationName The name of the notation for which
1366    * a doctype is required.
1367    * @param publicId The nominal public identifier for the notation
1368    * (as provided in the source document).
1369    * @param systemId The nominal system identifier for the notation
1370    * (as provided in the source document).
1371    *
1372    * @return The system identifier to use for the notation.
1373    *
1374    * @throws MalformedURLException The formal system identifier of a
1375    * subordinate catalog cannot be turned into a valid URL.
1376    * @throws IOException Error reading subordinate catalog file.
1377    */
resolveNotation(String notationName, String publicId, String systemId)1378   public String resolveNotation(String notationName,
1379                                 String publicId,
1380                                 String systemId)
1381     throws MalformedURLException, IOException {
1382     String resolved = null;
1383 
1384     catalogManager.debug.message(3, "resolveNotation("
1385                   +notationName+","+publicId+","+systemId+")");
1386 
1387     systemId = normalizeURI(systemId);
1388 
1389     if (publicId != null && publicId.startsWith("urn:publicid:")) {
1390       publicId = PublicId.decodeURN(publicId);
1391     }
1392 
1393     if (systemId != null && systemId.startsWith("urn:publicid:")) {
1394       systemId = PublicId.decodeURN(systemId);
1395       if (publicId != null && !publicId.equals(systemId)) {
1396         catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1397         systemId = null;
1398       } else {
1399         publicId = systemId;
1400         systemId = null;
1401       }
1402     }
1403 
1404     if (systemId != null) {
1405       // If there's a SYSTEM entry in this catalog, use it
1406       resolved = resolveLocalSystem(systemId);
1407       if (resolved != null) {
1408         return resolved;
1409       }
1410     }
1411 
1412     if (publicId != null) {
1413       // If there's a PUBLIC entry in this catalog, use it
1414       resolved = resolveLocalPublic(NOTATION,
1415                                     notationName,
1416                                     publicId,
1417                                     systemId);
1418       if (resolved != null) {
1419         return resolved;
1420       }
1421     }
1422 
1423     // If there's a NOTATION entry in this catalog, use it
1424     boolean over = default_override;
1425     Enumeration en = catalogEntries.elements();
1426     while (en.hasMoreElements()) {
1427       CatalogEntry e = (CatalogEntry) en.nextElement();
1428       if (e.getEntryType() == OVERRIDE) {
1429         over = e.getEntryArg(0).equalsIgnoreCase("YES");
1430         continue;
1431       }
1432 
1433       if (e.getEntryType() == NOTATION
1434           && e.getEntryArg(0).equals(notationName)) {
1435         if (over || systemId == null) {
1436           return e.getEntryArg(1);
1437         }
1438       }
1439     }
1440 
1441     // Otherwise, look in the subordinate catalogs
1442     return resolveSubordinateCatalogs(NOTATION,
1443                                       notationName,
1444                                       publicId,
1445                                       systemId);
1446   }
1447 
1448   /**
1449    * Return the applicable PUBLIC or SYSTEM identifier.
1450    *
1451    * <p>This method searches the Catalog and returns the system
1452    * identifier specified for the given system or
1453    * public identifiers. If
1454    * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
1455    * null is returned.</p>
1456    *
1457    * @param publicId The public identifier to locate in the catalog.
1458    * Public identifiers are normalized before comparison.
1459    * @param systemId The nominal system identifier for the entity
1460    * in question (as provided in the source document).
1461    *
1462    * @throws MalformedURLException The formal system identifier of a
1463    * subordinate catalog cannot be turned into a valid URL.
1464    * @throws IOException Error reading subordinate catalog file.
1465    *
1466    * @return The system identifier to use.
1467    * Note that the nominal system identifier is not returned if a
1468    * match is not found in the catalog, instead null is returned
1469    * to indicate that no match was found.
1470    */
resolvePublic(String publicId, String systemId)1471   public String resolvePublic(String publicId, String systemId)
1472     throws MalformedURLException, IOException {
1473 
1474     catalogManager.debug.message(3, "resolvePublic("+publicId+","+systemId+")");
1475 
1476     systemId = normalizeURI(systemId);
1477 
1478     if (publicId != null && publicId.startsWith("urn:publicid:")) {
1479       publicId = PublicId.decodeURN(publicId);
1480     }
1481 
1482     if (systemId != null && systemId.startsWith("urn:publicid:")) {
1483       systemId = PublicId.decodeURN(systemId);
1484       if (publicId != null && !publicId.equals(systemId)) {
1485         catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1486         systemId = null;
1487       } else {
1488         publicId = systemId;
1489         systemId = null;
1490       }
1491     }
1492 
1493     // If there's a SYSTEM entry in this catalog, use it
1494     if (systemId != null) {
1495       String resolved = resolveLocalSystem(systemId);
1496       if (resolved != null) {
1497         return resolved;
1498       }
1499     }
1500 
1501     // If there's a PUBLIC entry in this catalog, use it
1502     String resolved = resolveLocalPublic(PUBLIC,
1503                                          null,
1504                                          publicId,
1505                                          systemId);
1506     if (resolved != null) {
1507       return resolved;
1508     }
1509 
1510     // Otherwise, look in the subordinate catalogs
1511     return resolveSubordinateCatalogs(PUBLIC,
1512                                       null,
1513                                       publicId,
1514                                       systemId);
1515   }
1516 
1517   /**
1518    * Return the applicable PUBLIC or SYSTEM identifier.
1519    *
1520    * <p>This method searches the Catalog and returns the system
1521    * identifier specified for the given system or public identifiers.
1522    * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
1523    * delegated Catalogs are interrogated.</p>
1524    *
1525    * <p>There are four possible cases:</p>
1526    *
1527    * <ul>
1528    * <li>If the system identifier provided matches a SYSTEM entry
1529    * in the current catalog, the SYSTEM entry is returned.
1530    * <li>If the system identifier is not null, the PUBLIC entries
1531    * that were encountered when OVERRIDE YES was in effect are
1532    * interrogated and the first matching entry is returned.</li>
1533    * <li>If the system identifier is null, then all of the PUBLIC
1534    * entries are interrogated and the first matching entry
1535    * is returned. This may not be the same as the preceding case, if
1536    * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
1537    * XML, the only place where a public identifier may occur without
1538    * a system identifier is in a notation declaration.</li>
1539    * <li>Finally, if the public identifier matches one of the partial
1540    * public identifiers specified in a DELEGATE* entry in
1541    * the Catalog, the delegated catalog is interrogated. The first
1542    * time that the delegated catalog is required, it will be
1543    * retrieved and parsed. It is subsequently cached.
1544    * </li>
1545    * </ul>
1546    *
1547    * @param entityType The CatalogEntry type for which this query is
1548    * being conducted. This is necessary in order to do the approprate
1549    * query on a delegated catalog.
1550    * @param entityName The name of the entity being searched for, if
1551    * appropriate.
1552    * @param publicId The public identifier of the entity in question.
1553    * @param systemId The nominal system identifier for the entity
1554    * in question (as provided in the source document).
1555    *
1556    * @throws MalformedURLException The formal system identifier of a
1557    * delegated catalog cannot be turned into a valid URL.
1558    * @throws IOException Error reading delegated catalog file.
1559    *
1560    * @return The system identifier to use.
1561    * Note that the nominal system identifier is not returned if a
1562    * match is not found in the catalog, instead null is returned
1563    * to indicate that no match was found.
1564    */
resolveLocalPublic(int entityType, String entityName, String publicId, String systemId)1565   protected synchronized String resolveLocalPublic(int entityType,
1566                                                    String entityName,
1567                                                    String publicId,
1568                                                    String systemId)
1569     throws MalformedURLException, IOException {
1570 
1571     // Always normalize the public identifier before attempting a match
1572     publicId = PublicId.normalize(publicId);
1573 
1574     // If there's a SYSTEM entry in this catalog, use it
1575     if (systemId != null) {
1576       String resolved = resolveLocalSystem(systemId);
1577       if (resolved != null) {
1578         return resolved;
1579       }
1580     }
1581 
1582     // If there's a PUBLIC entry in this catalog, use it
1583     boolean over = default_override;
1584     Enumeration en = catalogEntries.elements();
1585     while (en.hasMoreElements()) {
1586       CatalogEntry e = (CatalogEntry) en.nextElement();
1587       if (e.getEntryType() == OVERRIDE) {
1588         over = e.getEntryArg(0).equalsIgnoreCase("YES");
1589         continue;
1590       }
1591 
1592       if (e.getEntryType() == PUBLIC
1593           && e.getEntryArg(0).equals(publicId)) {
1594         if (over || systemId == null) {
1595           return e.getEntryArg(1);
1596         }
1597       }
1598     }
1599 
1600     // If there's a DELEGATE_PUBLIC entry in this catalog, use it
1601     over = default_override;
1602     en = catalogEntries.elements();
1603     Vector delCats = new Vector();
1604     while (en.hasMoreElements()) {
1605       CatalogEntry e = (CatalogEntry) en.nextElement();
1606       if (e.getEntryType() == OVERRIDE) {
1607         over = e.getEntryArg(0).equalsIgnoreCase("YES");
1608         continue;
1609       }
1610 
1611       if (e.getEntryType() == DELEGATE_PUBLIC
1612           && (over || systemId == null)) {
1613         String p = (String) e.getEntryArg(0);
1614         if (p.length() <= publicId.length()
1615             && p.equals(publicId.substring(0, p.length()))) {
1616           // delegate this match to the other catalog
1617 
1618           delCats.addElement(e.getEntryArg(1));
1619         }
1620       }
1621     }
1622 
1623     if (delCats.size() > 0) {
1624       Enumeration enCats = delCats.elements();
1625 
1626       if (catalogManager.debug.getDebug() > 1) {
1627         catalogManager.debug.message(2, "Switching to delegated catalog(s):");
1628         while (enCats.hasMoreElements()) {
1629           String delegatedCatalog = (String) enCats.nextElement();
1630           catalogManager.debug.message(2, "\t" + delegatedCatalog);
1631         }
1632       }
1633 
1634       Catalog dcat = newCatalog();
1635 
1636       enCats = delCats.elements();
1637       while (enCats.hasMoreElements()) {
1638         String delegatedCatalog = (String) enCats.nextElement();
1639         dcat.parseCatalog(delegatedCatalog);
1640       }
1641 
1642       return dcat.resolvePublic(publicId, null);
1643     }
1644 
1645     // Nada!
1646     return null;
1647   }
1648 
1649   /**
1650    * Return the applicable SYSTEM system identifier.
1651    *
1652    * <p>If a SYSTEM entry exists in the Catalog
1653    * for the system ID specified, return the mapped value.</p>
1654    *
1655    * <p>On Windows-based operating systems, the comparison between
1656    * the system identifier provided and the SYSTEM entries in the
1657    * Catalog is case-insensitive.</p>
1658    *
1659    * @param systemId The system ID to locate in the catalog.
1660    *
1661    * @return The resolved system identifier.
1662    *
1663    * @throws MalformedURLException The formal system identifier of a
1664    * subordinate catalog cannot be turned into a valid URL.
1665    * @throws IOException Error reading subordinate catalog file.
1666    */
resolveSystem(String systemId)1667   public String resolveSystem(String systemId)
1668     throws MalformedURLException, IOException {
1669 
1670     catalogManager.debug.message(3, "resolveSystem("+systemId+")");
1671 
1672     systemId = normalizeURI(systemId);
1673 
1674     if (systemId != null && systemId.startsWith("urn:publicid:")) {
1675       systemId = PublicId.decodeURN(systemId);
1676       return resolvePublic(systemId, null);
1677     }
1678 
1679     // If there's a SYSTEM entry in this catalog, use it
1680     if (systemId != null) {
1681       String resolved = resolveLocalSystem(systemId);
1682       if (resolved != null) {
1683         return resolved;
1684       }
1685     }
1686 
1687     // Otherwise, look in the subordinate catalogs
1688     return resolveSubordinateCatalogs(SYSTEM,
1689                                       null,
1690                                       null,
1691                                       systemId);
1692   }
1693 
1694   /**
1695    * Return the applicable SYSTEM system identifier in this
1696    * catalog.
1697    *
1698    * <p>If a SYSTEM entry exists in the catalog file
1699    * for the system ID specified, return the mapped value.</p>
1700    *
1701    * @param systemId The system ID to locate in the catalog
1702    *
1703    * @return The mapped system identifier or null
1704    */
resolveLocalSystem(String systemId)1705   protected String resolveLocalSystem(String systemId)
1706     throws MalformedURLException, IOException {
1707 
1708     String osname = SecuritySupport.getSystemProperty("os.name");
1709     boolean windows = (osname.indexOf("Windows") >= 0);
1710     Enumeration en = catalogEntries.elements();
1711     while (en.hasMoreElements()) {
1712       CatalogEntry e = (CatalogEntry) en.nextElement();
1713       if (e.getEntryType() == SYSTEM
1714           && (e.getEntryArg(0).equals(systemId)
1715               || (windows
1716                   && e.getEntryArg(0).equalsIgnoreCase(systemId)))) {
1717         return e.getEntryArg(1);
1718       }
1719     }
1720 
1721     // If there's a REWRITE_SYSTEM entry in this catalog, use it
1722     en = catalogEntries.elements();
1723     String startString = null;
1724     String prefix = null;
1725     while (en.hasMoreElements()) {
1726       CatalogEntry e = (CatalogEntry) en.nextElement();
1727 
1728       if (e.getEntryType() == REWRITE_SYSTEM) {
1729         String p = (String) e.getEntryArg(0);
1730         if (p.length() <= systemId.length()
1731             && p.equals(systemId.substring(0, p.length()))) {
1732           // Is this the longest prefix?
1733           if (startString == null
1734               || p.length() > startString.length()) {
1735             startString = p;
1736             prefix = e.getEntryArg(1);
1737           }
1738         }
1739       }
1740     }
1741 
1742     if (prefix != null) {
1743       // return the systemId with the new prefix
1744       return prefix + systemId.substring(startString.length());
1745     }
1746 
1747     // If there's a SYSTEM_SUFFIX entry in this catalog, use it
1748     en = catalogEntries.elements();
1749     String suffixString = null;
1750     String suffixURI = null;
1751     while (en.hasMoreElements()) {
1752       CatalogEntry e = (CatalogEntry) en.nextElement();
1753 
1754       if (e.getEntryType() == SYSTEM_SUFFIX) {
1755         String p = (String) e.getEntryArg(0);
1756         if (p.length() <= systemId.length()
1757             && systemId.endsWith(p)) {
1758           // Is this the longest prefix?
1759           if (suffixString == null
1760               || p.length() > suffixString.length()) {
1761             suffixString = p;
1762             suffixURI = e.getEntryArg(1);
1763           }
1764         }
1765       }
1766     }
1767 
1768     if (suffixURI != null) {
1769       // return the systemId for the suffix
1770       return suffixURI;
1771     }
1772 
1773     // If there's a DELEGATE_SYSTEM entry in this catalog, use it
1774     en = catalogEntries.elements();
1775     Vector delCats = new Vector();
1776     while (en.hasMoreElements()) {
1777       CatalogEntry e = (CatalogEntry) en.nextElement();
1778 
1779       if (e.getEntryType() == DELEGATE_SYSTEM) {
1780         String p = (String) e.getEntryArg(0);
1781         if (p.length() <= systemId.length()
1782             && p.equals(systemId.substring(0, p.length()))) {
1783           // delegate this match to the other catalog
1784 
1785           delCats.addElement(e.getEntryArg(1));
1786         }
1787       }
1788     }
1789 
1790     if (delCats.size() > 0) {
1791       Enumeration enCats = delCats.elements();
1792 
1793       if (catalogManager.debug.getDebug() > 1) {
1794         catalogManager.debug.message(2, "Switching to delegated catalog(s):");
1795         while (enCats.hasMoreElements()) {
1796           String delegatedCatalog = (String) enCats.nextElement();
1797           catalogManager.debug.message(2, "\t" + delegatedCatalog);
1798         }
1799       }
1800 
1801       Catalog dcat = newCatalog();
1802 
1803       enCats = delCats.elements();
1804       while (enCats.hasMoreElements()) {
1805         String delegatedCatalog = (String) enCats.nextElement();
1806         dcat.parseCatalog(delegatedCatalog);
1807       }
1808 
1809       return dcat.resolveSystem(systemId);
1810     }
1811 
1812     return null;
1813   }
1814 
1815   /**
1816    * Return the applicable URI.
1817    *
1818    * <p>If a URI entry exists in the Catalog
1819    * for the URI specified, return the mapped value.</p>
1820    *
1821    * <p>URI comparison is case sensitive.</p>
1822    *
1823    * @param uri The URI to locate in the catalog.
1824    *
1825    * @return The resolved URI.
1826    *
1827    * @throws MalformedURLException The system identifier of a
1828    * subordinate catalog cannot be turned into a valid URL.
1829    * @throws IOException Error reading subordinate catalog file.
1830    */
resolveURI(String uri)1831   public String resolveURI(String uri)
1832     throws MalformedURLException, IOException {
1833 
1834     catalogManager.debug.message(3, "resolveURI("+uri+")");
1835 
1836     uri = normalizeURI(uri);
1837 
1838     if (uri != null && uri.startsWith("urn:publicid:")) {
1839       uri = PublicId.decodeURN(uri);
1840       return resolvePublic(uri, null);
1841     }
1842 
1843     // If there's a URI entry in this catalog, use it
1844     if (uri != null) {
1845       String resolved = resolveLocalURI(uri);
1846       if (resolved != null) {
1847         return resolved;
1848       }
1849     }
1850 
1851     // Otherwise, look in the subordinate catalogs
1852     return resolveSubordinateCatalogs(URI,
1853                                       null,
1854                                       null,
1855                                       uri);
1856   }
1857 
1858   /**
1859    * Return the applicable URI in this catalog.
1860    *
1861    * <p>If a URI entry exists in the catalog file
1862    * for the URI specified, return the mapped value.</p>
1863    *
1864    * @param uri The URI to locate in the catalog
1865    *
1866    * @return The mapped URI or null
1867    */
resolveLocalURI(String uri)1868   protected String resolveLocalURI(String uri)
1869     throws MalformedURLException, IOException {
1870     Enumeration en = catalogEntries.elements();
1871     while (en.hasMoreElements()) {
1872       CatalogEntry e = (CatalogEntry) en.nextElement();
1873       if (e.getEntryType() == URI
1874           && (e.getEntryArg(0).equals(uri))) {
1875         return e.getEntryArg(1);
1876       }
1877     }
1878 
1879     // If there's a REWRITE_URI entry in this catalog, use it
1880     en = catalogEntries.elements();
1881     String startString = null;
1882     String prefix = null;
1883     while (en.hasMoreElements()) {
1884       CatalogEntry e = (CatalogEntry) en.nextElement();
1885 
1886       if (e.getEntryType() == REWRITE_URI) {
1887         String p = (String) e.getEntryArg(0);
1888         if (p.length() <= uri.length()
1889             && p.equals(uri.substring(0, p.length()))) {
1890           // Is this the longest prefix?
1891           if (startString == null
1892               || p.length() > startString.length()) {
1893             startString = p;
1894             prefix = e.getEntryArg(1);
1895           }
1896         }
1897       }
1898     }
1899 
1900     if (prefix != null) {
1901       // return the uri with the new prefix
1902       return prefix + uri.substring(startString.length());
1903     }
1904 
1905     // If there's a URI_SUFFIX entry in this catalog, use it
1906     en = catalogEntries.elements();
1907     String suffixString = null;
1908     String suffixURI = null;
1909     while (en.hasMoreElements()) {
1910       CatalogEntry e = (CatalogEntry) en.nextElement();
1911 
1912       if (e.getEntryType() == URI_SUFFIX) {
1913         String p = (String) e.getEntryArg(0);
1914         if (p.length() <= uri.length()
1915             && uri.endsWith(p)) {
1916           // Is this the longest prefix?
1917           if (suffixString == null
1918               || p.length() > suffixString.length()) {
1919             suffixString = p;
1920             suffixURI = e.getEntryArg(1);
1921           }
1922         }
1923       }
1924     }
1925 
1926     if (suffixURI != null) {
1927       // return the uri for the suffix
1928       return suffixURI;
1929     }
1930 
1931     // If there's a DELEGATE_URI entry in this catalog, use it
1932     en = catalogEntries.elements();
1933     Vector delCats = new Vector();
1934     while (en.hasMoreElements()) {
1935       CatalogEntry e = (CatalogEntry) en.nextElement();
1936 
1937       if (e.getEntryType() == DELEGATE_URI) {
1938         String p = (String) e.getEntryArg(0);
1939         if (p.length() <= uri.length()
1940             && p.equals(uri.substring(0, p.length()))) {
1941           // delegate this match to the other catalog
1942 
1943           delCats.addElement(e.getEntryArg(1));
1944         }
1945       }
1946     }
1947 
1948     if (delCats.size() > 0) {
1949       Enumeration enCats = delCats.elements();
1950 
1951       if (catalogManager.debug.getDebug() > 1) {
1952         catalogManager.debug.message(2, "Switching to delegated catalog(s):");
1953         while (enCats.hasMoreElements()) {
1954           String delegatedCatalog = (String) enCats.nextElement();
1955           catalogManager.debug.message(2, "\t" + delegatedCatalog);
1956         }
1957       }
1958 
1959       Catalog dcat = newCatalog();
1960 
1961       enCats = delCats.elements();
1962       while (enCats.hasMoreElements()) {
1963         String delegatedCatalog = (String) enCats.nextElement();
1964         dcat.parseCatalog(delegatedCatalog);
1965       }
1966 
1967       return dcat.resolveURI(uri);
1968     }
1969 
1970     return null;
1971   }
1972 
1973   /**
1974    * Search the subordinate catalogs, in order, looking for a match.
1975    *
1976    * <p>This method searches the Catalog and returns the system
1977    * identifier specified for the given entity type with the given
1978    * name, public, and system identifiers. In some contexts, these
1979    * may be null.</p>
1980    *
1981    * @param entityType The CatalogEntry type for which this query is
1982    * being conducted. This is necessary in order to do the approprate
1983    * query on a subordinate catalog.
1984    * @param entityName The name of the entity being searched for, if
1985    * appropriate.
1986    * @param publicId The public identifier of the entity in question
1987    * (as provided in the source document).
1988    * @param systemId The nominal system identifier for the entity
1989    * in question (as provided in the source document). This parameter is
1990    * overloaded for the URI entry type.
1991    *
1992    * @throws MalformedURLException The formal system identifier of a
1993    * delegated catalog cannot be turned into a valid URL.
1994    * @throws IOException Error reading delegated catalog file.
1995    *
1996    * @return The system identifier to use.
1997    * Note that the nominal system identifier is not returned if a
1998    * match is not found in the catalog, instead null is returned
1999    * to indicate that no match was found.
2000    */
resolveSubordinateCatalogs(int entityType, String entityName, String publicId, String systemId)2001   protected synchronized String resolveSubordinateCatalogs(int entityType,
2002                                                            String entityName,
2003                                                            String publicId,
2004                                                            String systemId)
2005     throws MalformedURLException, IOException {
2006 
2007     for (int catPos = 0; catPos < catalogs.size(); catPos++) {
2008       Catalog c = null;
2009 
2010       try {
2011         c = (Catalog) catalogs.elementAt(catPos);
2012       } catch (ClassCastException e) {
2013         String catfile = (String) catalogs.elementAt(catPos);
2014         c = newCatalog();
2015 
2016         try {
2017           c.parseCatalog(catfile);
2018         } catch (MalformedURLException mue) {
2019           catalogManager.debug.message(1, "Malformed Catalog URL", catfile);
2020         } catch (FileNotFoundException fnfe) {
2021           catalogManager.debug.message(1, "Failed to load catalog, file not found",
2022                         catfile);
2023         } catch (IOException ioe) {
2024           catalogManager.debug.message(1, "Failed to load catalog, I/O error", catfile);
2025         }
2026 
2027         catalogs.setElementAt(c, catPos);
2028       }
2029 
2030       String resolved = null;
2031 
2032       // Ok, now what are we supposed to call here?
2033       if (entityType == DOCTYPE) {
2034         resolved = c.resolveDoctype(entityName,
2035                                     publicId,
2036                                     systemId);
2037       } else if (entityType == DOCUMENT) {
2038         resolved = c.resolveDocument();
2039       } else if (entityType == ENTITY) {
2040         resolved = c.resolveEntity(entityName,
2041                                    publicId,
2042                                    systemId);
2043       } else if (entityType == NOTATION) {
2044         resolved = c.resolveNotation(entityName,
2045                                      publicId,
2046                                      systemId);
2047       } else if (entityType == PUBLIC) {
2048         resolved = c.resolvePublic(publicId, systemId);
2049       } else if (entityType == SYSTEM) {
2050         resolved = c.resolveSystem(systemId);
2051       } else if (entityType == URI) {
2052         resolved = c.resolveURI(systemId);
2053       }
2054 
2055       if (resolved != null) {
2056         return resolved;
2057       }
2058     }
2059 
2060     return null;
2061   }
2062 
2063   // -----------------------------------------------------------------
2064 
2065   /**
2066    * Replace backslashes with forward slashes. (URLs always use
2067    * forward slashes.)
2068    *
2069    * @param sysid The input system identifier.
2070    * @return The same system identifier with backslashes turned into
2071    * forward slashes.
2072    */
fixSlashes(String sysid)2073   protected String fixSlashes (String sysid) {
2074     return sysid.replace('\\', '/');
2075   }
2076 
2077   /**
2078    * Construct an absolute URI from a relative one, using the current
2079    * base URI.
2080    *
2081    * @param sysid The (possibly relative) system identifier
2082    * @return The system identifier made absolute with respect to the
2083    * current {@link #base}.
2084    */
makeAbsolute(String sysid)2085   protected String makeAbsolute(String sysid) {
2086     URL local = null;
2087 
2088     sysid = fixSlashes(sysid);
2089 
2090     try {
2091       local = new URL(base, sysid);
2092     } catch (MalformedURLException e) {
2093       catalogManager.debug.message(1, "Malformed URL on system identifier", sysid);
2094     }
2095 
2096     if (local != null) {
2097       return local.toString();
2098     } else {
2099       return sysid;
2100     }
2101   }
2102 
2103   /**
2104    * Perform character normalization on a URI reference.
2105    *
2106    * @param uriref The URI reference
2107    * @return The normalized URI reference.
2108    */
normalizeURI(String uriref)2109   protected String normalizeURI(String uriref) {
2110     if (uriref == null) {
2111       return null;
2112     }
2113 
2114     byte[] bytes;
2115     try {
2116       bytes = uriref.getBytes("UTF-8");
2117     } catch (UnsupportedEncodingException uee) {
2118       // this can't happen
2119       catalogManager.debug.message(1, "UTF-8 is an unsupported encoding!?");
2120       return uriref;
2121     }
2122 
2123     StringBuilder newRef = new StringBuilder(bytes.length);
2124     for (int count = 0; count < bytes.length; count++) {
2125       int ch = bytes[count] & 0xFF;
2126 
2127       if ((ch <= 0x20)    // ctrl
2128           || (ch > 0x7F)  // high ascii
2129           || (ch == 0x22) // "
2130           || (ch == 0x3C) // <
2131           || (ch == 0x3E) // >
2132           || (ch == 0x5C) // \
2133           || (ch == 0x5E) // ^
2134           || (ch == 0x60) // `
2135           || (ch == 0x7B) // {
2136           || (ch == 0x7C) // |
2137           || (ch == 0x7D) // }
2138           || (ch == 0x7F)) {
2139         newRef.append(encodedByte(ch));
2140       } else {
2141         newRef.append((char) bytes[count]);
2142       }
2143     }
2144 
2145     return newRef.toString();
2146   }
2147 
2148   /**
2149    * Perform %-encoding on a single byte.
2150    *
2151    * @param b The 8-bit integer that represents th byte. (Bytes are signed
2152               but encoding needs to look at the bytes unsigned.)
2153    * @return The %-encoded string for the byte in question.
2154    */
encodedByte(int b)2155   protected String encodedByte (int b) {
2156     String hex = Integer.toHexString(b).toUpperCase();
2157     if (hex.length() < 2) {
2158       return "%0" + hex;
2159     } else {
2160       return "%" + hex;
2161     }
2162   }
2163 
2164   // -----------------------------------------------------------------
2165 
2166   /**
2167    * Add to the current list of delegated catalogs.
2168    *
2169    * <p>This method always constructs the {@link #localDelegate}
2170    * vector so that it is ordered by length of partial
2171    * public identifier.</p>
2172    *
2173    * @param entry The DELEGATE catalog entry
2174    */
addDelegate(CatalogEntry entry)2175   protected void addDelegate(CatalogEntry entry) {
2176     int pos = 0;
2177     String partial = entry.getEntryArg(0);
2178 
2179     Enumeration local = localDelegate.elements();
2180     while (local.hasMoreElements()) {
2181       CatalogEntry dpe = (CatalogEntry) local.nextElement();
2182       String dp = dpe.getEntryArg(0);
2183       if (dp.equals(partial)) {
2184         // we already have this prefix
2185         return;
2186       }
2187       if (dp.length() > partial.length()) {
2188         pos++;
2189       }
2190       if (dp.length() < partial.length()) {
2191         break;
2192       }
2193     }
2194 
2195     // now insert partial into the vector at [pos]
2196     if (localDelegate.size() == 0) {
2197       localDelegate.addElement(entry);
2198     } else {
2199       localDelegate.insertElementAt(entry, pos);
2200     }
2201   }
2202 }
2203