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