1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.sun.org.apache.xml.internal.resolver;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.FileNotFoundException;
23import java.util.Enumeration;
24import java.util.Vector;
25import java.net.URL;
26import java.net.URLConnection;
27import java.net.MalformedURLException;
28import javax.xml.parsers.SAXParserFactory;
29import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
30import com.sun.org.apache.xerces.internal.utils.SecuritySupport;
31import com.sun.org.apache.xml.internal.resolver.readers.SAXCatalogReader;
32import com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader;
33import com.sun.org.apache.xml.internal.resolver.readers.TR9401CatalogReader;
34
35/**
36 * An extension to OASIS Open Catalog files, this class supports
37 * suffix-based matching and an external RFC2483 resolver.
38 *
39 * @see Catalog
40 * @deprecated The JDK internal Catalog API in package
41 * {@code com.sun.org.apache.xml.internal.resolver}
42 * is encapsulated in JDK 9. The entire implementation under the package is now
43 * deprecated and subject to removal in a future release. Users of the API
44 * should migrate to the {@linkplain javax.xml.catalog new public API}.
45 * <p>
46 * The new Catalog API is supported throughout the JDK XML Processors, which allows
47 * the use of Catalog by simply setting a path to a Catalog file as a property.
48 *
49 * @author Norman Walsh
50 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
51 *
52 * @version 1.0
53 */
54@Deprecated(since="9", forRemoval=true)
55public class Resolver extends Catalog {
56  /**
57   * The URISUFFIX Catalog Entry type.
58   *
59   * <p>URI suffix entries match URIs that end in a specified suffix.</p>
60   */
61  public static final int URISUFFIX = CatalogEntry.addEntryType("URISUFFIX", 2);
62
63  /**
64   * The SYSTEMSUFFIX Catalog Entry type.
65   *
66   * <p>System suffix entries match system identifiers that end in a
67   * specified suffix.</p>
68   */
69  public static final int SYSTEMSUFFIX = CatalogEntry.addEntryType("SYSTEMSUFFIX", 2);
70
71  /**
72   * The RESOLVER Catalog Entry type.
73   *
74   * <p>A hook for providing support for web-based backup resolvers.</p>
75   */
76  public static final int RESOLVER = CatalogEntry.addEntryType("RESOLVER", 1);
77
78  /**
79   * The SYSTEMREVERSE Catalog Entry type.
80   *
81   * <p>This is a bit of a hack. There's no actual SYSTEMREVERSE entry,
82   * but this entry type is used to indicate that a reverse lookup is
83   * being performed. (This allows the Resolver to implement
84   * RFC2483 I2N and I2NS.)
85   */
86  public static final int SYSTEMREVERSE
87    = CatalogEntry.addEntryType("SYSTEMREVERSE", 1);
88
89  /**
90   * Setup readers.
91   */
92  public void setupReaders() {
93    SAXParserFactory spf = catalogManager.useServicesMechanism() ?
94                    SAXParserFactory.newInstance() : new SAXParserFactoryImpl();
95    spf.setNamespaceAware(true);
96    spf.setValidating(false);
97
98    SAXCatalogReader saxReader = new SAXCatalogReader(spf);
99
100    saxReader.setCatalogParser(null, "XCatalog",
101                               "com.sun.org.apache.xml.internal.resolver.readers.XCatalogReader");
102
103    saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName,
104                               "catalog",
105                               "com.sun.org.apache.xml.internal.resolver.readers.ExtendedXMLCatalogReader");
106
107    addReader("application/xml", saxReader);
108
109    TR9401CatalogReader textReader = new TR9401CatalogReader();
110    addReader("text/plain", textReader);
111  }
112
113  /**
114   * Cleanup and process a Catalog entry.
115   *
116   * <p>This method processes each Catalog entry, changing mapped
117   * relative system identifiers into absolute ones (based on the current
118   * base URI), and maintaining other information about the current
119   * catalog.</p>
120   *
121   * @param entry The CatalogEntry to process.
122   */
123  public void addEntry(CatalogEntry entry) {
124    int type = entry.getEntryType();
125
126    if (type == URISUFFIX) {
127      String suffix = normalizeURI(entry.getEntryArg(0));
128      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
129
130      entry.setEntryArg(1, fsi);
131
132      catalogManager.debug.message(4, "URISUFFIX", suffix, fsi);
133    } else if (type == SYSTEMSUFFIX) {
134      String suffix = normalizeURI(entry.getEntryArg(0));
135      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
136
137      entry.setEntryArg(1, fsi);
138
139      catalogManager.debug.message(4, "SYSTEMSUFFIX", suffix, fsi);
140    }
141
142    super.addEntry(entry);
143  }
144
145  /**
146   * Return the applicable URI.
147   *
148   * <p>If a URI entry exists in the Catalog
149   * for the URI specified, return the mapped value.</p>
150   *
151   * <p>In the Resolver (as opposed to the Catalog) class, if the
152   * URI isn't found by the usual algorithm, URISUFFIX entries are
153   * considered.</p>
154   *
155   * <p>URI comparison is case sensitive.</p>
156   *
157   * @param uri The URI to locate in the catalog.
158   *
159   * @return The resolved URI.
160   *
161   * @throws MalformedURLException The system identifier of a
162   * subordinate catalog cannot be turned into a valid URL.
163   * @throws IOException Error reading subordinate catalog file.
164   */
165  public String resolveURI(String uri)
166    throws MalformedURLException, IOException {
167
168    String resolved = super.resolveURI(uri);
169    if (resolved != null) {
170      return resolved;
171    }
172
173    Enumeration en = catalogEntries.elements();
174    while (en.hasMoreElements()) {
175      CatalogEntry e = (CatalogEntry) en.nextElement();
176      if (e.getEntryType() == RESOLVER) {
177        resolved = resolveExternalSystem(uri, e.getEntryArg(0));
178        if (resolved != null) {
179          return resolved;
180        }
181      } else if (e.getEntryType() == URISUFFIX) {
182        String suffix = e.getEntryArg(0);
183        String result = e.getEntryArg(1);
184
185        if (suffix.length() <= uri.length()
186            && uri.substring(uri.length()-suffix.length()).equals(suffix)) {
187          return result;
188        }
189      }
190    }
191
192    // Otherwise, look in the subordinate catalogs
193    return resolveSubordinateCatalogs(Catalog.URI,
194                                      null,
195                                      null,
196                                      uri);
197  }
198
199  /**
200   * Return the applicable SYSTEM system identifier, resorting
201   * to external RESOLVERs if necessary.
202   *
203   * <p>If a SYSTEM entry exists in the Catalog
204   * for the system ID specified, return the mapped value.</p>
205   *
206   * <p>In the Resolver (as opposed to the Catalog) class, if the
207   * URI isn't found by the usual algorithm, SYSTEMSUFFIX entries are
208   * considered.</p>
209   *
210   * <p>On Windows-based operating systems, the comparison between
211   * the system identifier provided and the SYSTEM entries in the
212   * Catalog is case-insensitive.</p>
213   *
214   * @param systemId The system ID to locate in the catalog.
215   *
216   * @return The system identifier to use for systemId.
217   *
218   * @throws MalformedURLException The formal system identifier of a
219   * subordinate catalog cannot be turned into a valid URL.
220   * @throws IOException Error reading subordinate catalog file.
221   */
222  public String resolveSystem(String systemId)
223    throws MalformedURLException, IOException {
224
225    String resolved = super.resolveSystem(systemId);
226    if (resolved != null) {
227      return resolved;
228    }
229
230    Enumeration en = catalogEntries.elements();
231    while (en.hasMoreElements()) {
232      CatalogEntry e = (CatalogEntry) en.nextElement();
233      if (e.getEntryType() == RESOLVER) {
234        resolved = resolveExternalSystem(systemId, e.getEntryArg(0));
235        if (resolved != null) {
236          return resolved;
237        }
238      } else if (e.getEntryType() == SYSTEMSUFFIX) {
239        String suffix = e.getEntryArg(0);
240        String result = e.getEntryArg(1);
241
242        if (suffix.length() <= systemId.length()
243            && systemId.substring(systemId.length()-suffix.length()).equals(suffix)) {
244          return result;
245        }
246      }
247    }
248
249    return resolveSubordinateCatalogs(Catalog.SYSTEM,
250                                      null,
251                                      null,
252                                      systemId);
253  }
254
255  /**
256   * Return the applicable PUBLIC or SYSTEM identifier, resorting
257   * to external resolvers if necessary.
258   *
259   * <p>This method searches the Catalog and returns the system
260   * identifier specified for the given system or
261   * public identifiers. If
262   * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
263   * null is returned.</p>
264   *
265   * <p>Note that a system or public identifier in the current catalog
266   * (or subordinate catalogs) will be used in preference to an
267   * external resolver. Further, if a systemId is present, the external
268   * resolver(s) will be queried for that before the publicId.</p>
269   *
270   * @param publicId The public identifier to locate in the catalog.
271   * Public identifiers are normalized before comparison.
272   * @param systemId The nominal system identifier for the entity
273   * in question (as provided in the source document).
274   *
275   * @throws MalformedURLException The formal system identifier of a
276   * subordinate catalog cannot be turned into a valid URL.
277   * @throws IOException Error reading subordinate catalog file.
278   *
279   * @return The system identifier to use.
280   * Note that the nominal system identifier is not returned if a
281   * match is not found in the catalog, instead null is returned
282   * to indicate that no match was found.
283   */
284  public String resolvePublic(String publicId, String systemId)
285    throws MalformedURLException, IOException {
286
287    String resolved = super.resolvePublic(publicId, systemId);
288    if (resolved != null) {
289      return resolved;
290    }
291
292    Enumeration en = catalogEntries.elements();
293    while (en.hasMoreElements()) {
294      CatalogEntry e = (CatalogEntry) en.nextElement();
295      if (e.getEntryType() == RESOLVER) {
296        if (systemId != null) {
297          resolved = resolveExternalSystem(systemId,
298                                           e.getEntryArg(0));
299          if (resolved != null) {
300            return resolved;
301          }
302        }
303        resolved = resolveExternalPublic(publicId, e.getEntryArg(0));
304        if (resolved != null) {
305          return resolved;
306        }
307      }
308    }
309
310    return resolveSubordinateCatalogs(Catalog.PUBLIC,
311                                      null,
312                                      publicId,
313                                      systemId);
314  }
315
316    /**
317     * Query an external RFC2483 resolver for a system identifier.
318     *
319     * @param systemId The system ID to locate.
320     * @param resolver The name of the resolver to use.
321     *
322     * @return The system identifier to use for the systemId.
323     */
324    protected String resolveExternalSystem(String systemId, String resolver)
325        throws MalformedURLException, IOException {
326        Resolver r = queryResolver(resolver, "i2l", systemId, null);
327        if (r != null) {
328            return r.resolveSystem(systemId);
329        } else {
330            return null;
331        }
332    }
333
334    /**
335     * Query an external RFC2483 resolver for a public identifier.
336     *
337     * @param publicId The system ID to locate.
338     * @param resolver The name of the resolver to use.
339     *
340     * @return The system identifier to use for the systemId.
341     */
342    protected String resolveExternalPublic(String publicId, String resolver)
343        throws MalformedURLException, IOException {
344        Resolver r = queryResolver(resolver, "fpi2l", publicId, null);
345        if (r != null) {
346            return r.resolvePublic(publicId, null);
347        } else {
348            return null;
349        }
350    }
351
352    /**
353     * Query an external RFC2483 resolver.
354     *
355     * @param resolver The URL of the RFC2483 resolver.
356     * @param command The command to send the resolver.
357     * @param arg1 The first argument to the resolver.
358     * @param arg2 The second argument to the resolver, usually null.
359     *
360     * @return The Resolver constructed.
361     */
362    protected Resolver queryResolver(String resolver,
363                                     String command,
364                                     String arg1,
365                                     String arg2) {
366        InputStream iStream = null;
367        String RFC2483 = resolver + "?command=" + command
368            + "&format=tr9401&uri=" + arg1
369            + "&uri2=" + arg2;
370        String line = null;
371
372        try {
373            URL url = new URL(RFC2483);
374
375            URLConnection urlCon = url.openConnection();
376
377            urlCon.setUseCaches(false);
378
379            Resolver r = (Resolver) newCatalog();
380
381            String cType = urlCon.getContentType();
382
383            // I don't care about the character set or subtype
384            if (cType.indexOf(";") > 0) {
385                cType = cType.substring(0, cType.indexOf(";"));
386            }
387
388            r.parseCatalog(cType, urlCon.getInputStream());
389
390            return r;
391        } catch (CatalogException cex) {
392          if (cex.getExceptionType() == CatalogException.UNPARSEABLE) {
393            catalogManager.debug.message(1, "Unparseable catalog: " + RFC2483);
394          } else if (cex.getExceptionType()
395                     == CatalogException.UNKNOWN_FORMAT) {
396            catalogManager.debug.message(1, "Unknown catalog format: " + RFC2483);
397          }
398          return null;
399        } catch (MalformedURLException mue) {
400            catalogManager.debug.message(1, "Malformed resolver URL: " + RFC2483);
401            return null;
402        } catch (IOException ie) {
403            catalogManager.debug.message(1, "I/O Exception opening resolver: " + RFC2483);
404            return null;
405        }
406    }
407
408    /**
409     * Append two vectors, returning the result.
410     *
411     * @param vec The first vector
412     * @param appvec The vector to be appended
413     * @return The vector vec, with appvec's elements appended to it
414     */
415    private Vector appendVector(Vector vec, Vector appvec) {
416        if (appvec != null) {
417            for (int count = 0; count < appvec.size(); count++) {
418                vec.addElement(appvec.elementAt(count));
419            }
420        }
421        return vec;
422    }
423
424    /**
425     * Find the URNs for a given system identifier in all catalogs.
426     *
427     * @param systemId The system ID to locate.
428     *
429     * @return A vector of URNs that map to the systemId.
430     */
431    public Vector resolveAllSystemReverse(String systemId)
432        throws MalformedURLException, IOException {
433        Vector resolved = new Vector();
434
435        // If there's a SYSTEM entry in this catalog, use it
436        if (systemId != null) {
437            Vector localResolved = resolveLocalSystemReverse(systemId);
438            resolved = appendVector(resolved, localResolved);
439        }
440
441        // Otherwise, look in the subordinate catalogs
442        Vector subResolved = resolveAllSubordinateCatalogs(SYSTEMREVERSE,
443                                                           null,
444                                                           null,
445                                                           systemId);
446
447        return appendVector(resolved, subResolved);
448    }
449
450    /**
451     * Find the URN for a given system identifier.
452     *
453     * @param systemId The system ID to locate.
454     *
455     * @return A (single) URN that maps to the systemId.
456     */
457    public String resolveSystemReverse(String systemId)
458        throws MalformedURLException, IOException {
459        Vector resolved = resolveAllSystemReverse(systemId);
460        if (resolved != null && resolved.size() > 0) {
461            return (String) resolved.elementAt(0);
462        } else {
463            return null;
464        }
465    }
466
467    /**
468     * Return the applicable SYSTEM system identifiers.
469     *
470     * <p>If one or more SYSTEM entries exists in the Catalog
471     * for the system ID specified, return the mapped values.</p>
472     *
473     * <p>The caller is responsible for doing any necessary
474     * normalization of the system identifier before calling
475     * this method. For example, a relative system identifier in
476     * a document might be converted to an absolute system identifier
477     * before attempting to resolve it.</p>
478     *
479     * <p>Note that this function will force all subordinate catalogs
480     * to be loaded.</p>
481     *
482     * <p>On Windows-based operating systems, the comparison between
483     * the system identifier provided and the SYSTEM entries in the
484     * Catalog is case-insensitive.</p>
485     *
486     * @param systemId The system ID to locate in the catalog.
487     *
488     * @return The system identifier to use for the notation.
489     *
490     * @throws MalformedURLException The formal system identifier of a
491     * subordinate catalog cannot be turned into a valid URL.
492     * @throws IOException Error reading subordinate catalog file.
493     */
494    public Vector resolveAllSystem(String systemId)
495        throws MalformedURLException, IOException {
496        Vector resolutions = new Vector();
497
498        // If there are SYSTEM entries in this catalog, start with them
499        if (systemId != null) {
500            Vector localResolutions = resolveAllLocalSystem(systemId);
501            resolutions = appendVector(resolutions, localResolutions);
502        }
503
504        // Then look in the subordinate catalogs
505        Vector subResolutions = resolveAllSubordinateCatalogs(SYSTEM,
506                                                              null,
507                                                              null,
508                                                              systemId);
509        resolutions = appendVector(resolutions, subResolutions);
510
511        if (resolutions.size() > 0) {
512            return resolutions;
513        } else {
514            return null;
515        }
516    }
517
518    /**
519     * Return all applicable SYSTEM system identifiers in this
520     * catalog.
521     *
522     * <p>If one or more SYSTEM entries exists in the catalog file
523     * for the system ID specified, return the mapped values.</p>
524     *
525     * @param systemId The system ID to locate in the catalog
526     *
527     * @return A vector of the mapped system identifiers or null
528     */
529    private Vector resolveAllLocalSystem(String systemId) {
530        Vector map = new Vector();
531        String osname = SecuritySupport.getSystemProperty("os.name");
532        boolean windows = (osname.indexOf("Windows") >= 0);
533        Enumeration en = catalogEntries.elements();
534        while (en.hasMoreElements()) {
535            CatalogEntry e = (CatalogEntry) en.nextElement();
536            if (e.getEntryType() == SYSTEM
537                && (e.getEntryArg(0).equals(systemId)
538                    || (windows
539                        && e.getEntryArg(0).equalsIgnoreCase(systemId)))) {
540                map.addElement(e.getEntryArg(1));
541            }
542        }
543        if (map.size() == 0) {
544            return null;
545        } else {
546            return map;
547        }
548    }
549
550    /**
551     * Find the URNs for a given system identifier in the current catalog.
552     *
553     * @param systemId The system ID to locate.
554     *
555     * @return A vector of URNs that map to the systemId.
556     */
557    private Vector resolveLocalSystemReverse(String systemId) {
558        Vector map = new Vector();
559        String osname = SecuritySupport.getSystemProperty("os.name");
560        boolean windows = (osname.indexOf("Windows") >= 0);
561        Enumeration en = catalogEntries.elements();
562        while (en.hasMoreElements()) {
563            CatalogEntry e = (CatalogEntry) en.nextElement();
564            if (e.getEntryType() == SYSTEM
565                && (e.getEntryArg(1).equals(systemId)
566                    || (windows
567                        && e.getEntryArg(1).equalsIgnoreCase(systemId)))) {
568                map.addElement(e.getEntryArg(0));
569            }
570        }
571        if (map.size() == 0) {
572            return null;
573        } else {
574            return map;
575        }
576    }
577
578    /**
579     * Search the subordinate catalogs, in order, looking for all
580     * match.
581     *
582     * <p>This method searches the Catalog and returns all of the system
583     * identifiers specified for the given entity type with the given
584     * name, public, and system identifiers. In some contexts, these
585     * may be null.</p>
586     *
587     * @param entityType The CatalogEntry type for which this query is
588     * being conducted. This is necessary in order to do the approprate
589     * query on a subordinate catalog.
590     * @param entityName The name of the entity being searched for, if
591     * appropriate.
592     * @param publicId The public identifier of the entity in question
593     * (as provided in the source document).
594     * @param systemId The nominal system identifier for the entity
595     * in question (as provided in the source document).
596     *
597     * @throws MalformedURLException The formal system identifier of a
598     * delegated catalog cannot be turned into a valid URL.
599     * @throws IOException Error reading delegated catalog file.
600     *
601     * @return The system identifier to use.
602     * Note that the nominal system identifier is not returned if a
603     * match is not found in the catalog, instead null is returned
604     * to indicate that no match was found.
605     */
606    private synchronized Vector resolveAllSubordinateCatalogs(int entityType,
607                                              String entityName,
608                                              String publicId,
609                                              String systemId)
610        throws MalformedURLException, IOException {
611
612        Vector resolutions = new Vector();
613
614        for (int catPos = 0; catPos < catalogs.size(); catPos++) {
615            Resolver c = null;
616
617            try {
618                c = (Resolver) catalogs.elementAt(catPos);
619            } catch (ClassCastException e) {
620                String catfile = (String) catalogs.elementAt(catPos);
621                c = (Resolver) newCatalog();
622
623                try {
624                    c.parseCatalog(catfile);
625                } catch (MalformedURLException mue) {
626                    catalogManager.debug.message(1, "Malformed Catalog URL", catfile);
627                } catch (FileNotFoundException fnfe) {
628                    catalogManager.debug.message(1, "Failed to load catalog, file not found",
629                          catfile);
630                } catch (IOException ioe) {
631                    catalogManager.debug.message(1, "Failed to load catalog, I/O error", catfile);
632                }
633
634                catalogs.setElementAt(c, catPos);
635            }
636
637            String resolved = null;
638
639            // Ok, now what are we supposed to call here?
640            if (entityType == DOCTYPE) {
641                resolved = c.resolveDoctype(entityName,
642                                            publicId,
643                                            systemId);
644                if (resolved != null) {
645                    // Only find one DOCTYPE resolution
646                    resolutions.addElement(resolved);
647                    return resolutions;
648                }
649            } else if (entityType == DOCUMENT) {
650                resolved = c.resolveDocument();
651                if (resolved != null) {
652                    // Only find one DOCUMENT resolution
653                    resolutions.addElement(resolved);
654                    return resolutions;
655                }
656            } else if (entityType == ENTITY) {
657                resolved = c.resolveEntity(entityName,
658                                           publicId,
659                                           systemId);
660                if (resolved != null) {
661                    // Only find one ENTITY resolution
662                    resolutions.addElement(resolved);
663                    return resolutions;
664                }
665            } else if (entityType == NOTATION) {
666                resolved = c.resolveNotation(entityName,
667                                             publicId,
668                                             systemId);
669                if (resolved != null) {
670                    // Only find one NOTATION resolution
671                    resolutions.addElement(resolved);
672                    return resolutions;
673                }
674            } else if (entityType == PUBLIC) {
675                resolved = c.resolvePublic(publicId, systemId);
676                if (resolved != null) {
677                    // Only find one PUBLIC resolution
678                    resolutions.addElement(resolved);
679                    return resolutions;
680                }
681            } else if (entityType == SYSTEM) {
682                Vector localResolutions = c.resolveAllSystem(systemId);
683                resolutions = appendVector(resolutions, localResolutions);
684                break;
685            } else if (entityType == SYSTEMREVERSE) {
686                Vector localResolutions = c.resolveAllSystemReverse(systemId);
687                resolutions = appendVector(resolutions, localResolutions);
688            }
689        }
690
691        if (resolutions != null) {
692            return resolutions;
693        } else {
694            return null;
695        }
696    }
697}
698