1 /* 2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package catalog; 24 25 import java.io.IOException; 26 import java.nio.file.Paths; 27 28 import javax.xml.catalog.Catalog; 29 import javax.xml.catalog.CatalogException; 30 import javax.xml.catalog.CatalogFeatures; 31 import javax.xml.catalog.CatalogFeatures.Feature; 32 import javax.xml.catalog.CatalogManager; 33 import javax.xml.catalog.CatalogResolver; 34 import javax.xml.catalog.CatalogUriResolver; 35 import javax.xml.parsers.ParserConfigurationException; 36 import javax.xml.parsers.SAXParser; 37 import javax.xml.parsers.SAXParserFactory; 38 import javax.xml.transform.Source; 39 40 import jaxp.library.JAXPTestUtilities; 41 42 import org.testng.Assert; 43 import org.testng.annotations.BeforeClass; 44 import org.testng.annotations.DataProvider; 45 import org.testng.annotations.Listeners; 46 import org.testng.annotations.Test; 47 import org.xml.sax.Attributes; 48 import org.xml.sax.ErrorHandler; 49 import org.xml.sax.InputSource; 50 import org.xml.sax.SAXException; 51 import org.xml.sax.XMLReader; 52 import org.xml.sax.ext.DefaultHandler2; 53 54 /* 55 * @bug 8081248, 8144966, 8146606, 8146237, 8151154, 8150969, 8151162, 8152527, 8154220 56 * @summary Tests basic Catalog functions. 57 */ 58 @Listeners({jaxp.library.FilePolicy.class}) 59 public class CatalogTest { 60 static final String KEY_FILES = "javax.xml.catalog.files"; 61 62 public String filepath; 63 64 /* 65 * Initializing fields 66 */ 67 @BeforeClass 68 public void setUpClass() throws Exception { 69 String file1 = getClass().getResource("first_cat.xml").getFile(); 70 if (System.getProperty("os.name").contains("Windows")) { 71 filepath = file1.substring(1, file1.lastIndexOf("/") + 1); 72 } else { 73 filepath = file1.substring(0, file1.lastIndexOf("/") + 1); 74 } 75 } 76 77 /* 78 * @bug 8150187 79 * NPE is expected if the systemId is null. The specification for systemId 80 * is as follows: 81 * A system identifier is required on all external entities. XML 82 * requires a system identifier on all external entities, so this value is 83 * always specified. 84 */ 85 @Test(expectedExceptions = NullPointerException.class) 86 public void sysIdCantBeNull() { 87 CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults()); 88 InputSource is = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", null); 89 } 90 91 /* 92 * @bug 8156845 93 * Verifies that an URI reference with a urn:publicid is correctly resolved 94 * with an uri entry with a publicId. 95 * 96 * @param expectedFile is not used in this test, it's kept since we're 97 * copying the JCK test and its dataProvider. This test may be reused for 98 * other cases in that test. 99 */ 100 @Test(dataProvider = "resolveUri") 101 public void testMatch1(String cFile, String href, String expectedFile, String expectedUri, String msg) { 102 String catalogFile = getClass().getResource(cFile).getFile(); 103 CatalogUriResolver cur = CatalogManager.catalogUriResolver(CatalogFeatures.defaults(), catalogFile); 104 Source source = cur.resolve(href, null); 105 Assert.assertNotNull(source, "Source returned is null"); 106 Assert.assertEquals(expectedUri, source.getSystemId(), msg); 107 } 108 109 /* 110 * @bug 8154220 111 * Verifies that the file input is validated properly. Valid input includes 112 * multiple file paths separated by semicolon. 113 */ 114 @Test(dataProvider = "hierarchyOfCatFilesData") 115 public void hierarchyOfCatFiles2(String systemId, String expectedUri) { 116 String file1 = getClass().getResource("first_cat.xml").getFile(); 117 String file2 = getClass().getResource("second_cat.xml").getFile(); 118 String files = file1 + ";" + file2; 119 120 try { 121 System.setProperty(KEY_FILES, files); 122 CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults()); 123 String sysId = catalogResolver.resolveEntity(null, systemId).getSystemId(); 124 Assert.assertEquals(sysId, Paths.get(filepath + expectedUri).toUri().toString().replace("///", "/"), "System ID match not right"); 125 } finally { 126 System.clearProperty(KEY_FILES); 127 } 128 129 } 130 131 /* 132 * @bug 8152527 133 * This test is the same as the JDK test ResolveEntityTests:testMatch1. 134 * Verifies that the CatalogResolver resolves a publicId and/or systemId as 135 * expected. 136 */ 137 @Test(dataProvider = "resolveEntity") 138 public void testMatch1(String cfile, String prefer, String sysId, String pubId, String expectedUri, String expectedFile, String msg) { 139 String catalogFile = getClass().getResource(cfile).getFile(); 140 CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build(); 141 CatalogResolver catalogResolver = CatalogManager.catalogResolver(features, catalogFile); 142 InputSource is = catalogResolver.resolveEntity(pubId, sysId); 143 Assert.assertNotNull(is, msg); 144 String expected = (expectedUri == null) ? expectedFile : expectedUri; 145 Assert.assertEquals(expected, is.getSystemId(), msg); 146 } 147 148 /* 149 * @bug 8151162 150 * Verifies that the Catalog matches specified publicId or systemId and returns 151 * results as expected. 152 */ 153 @Test(dataProvider = "matchWithPrefer") 154 public void matchWithPrefer(String prefer, String cfile, String publicId, String systemId, String expected) { 155 String catalogFile = getClass().getResource(cfile).getFile(); 156 Catalog c = CatalogManager.catalog(CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build(), catalogFile); 157 String result; 158 if (publicId != null && publicId.length() > 0) { 159 result = c.matchPublic(publicId); 160 } else { 161 result = c.matchSystem(systemId); 162 } 163 Assert.assertEquals(expected, result); 164 } 165 166 /* 167 * @bug 8151162 168 * Verifies that the CatalogResolver resolves specified publicId or systemId 169 * in accordance with the prefer setting. 170 * prefer "system": resolves with a system entry. 171 * Exception: use the public entry when the catalog contains 172 * only public entry and only publicId is specified. 173 * prefer "public": attempts to resolve with a system entry; 174 * attempts to resolve with a public entry if no matching 175 * system entry is found. 176 */ 177 @Test(dataProvider = "resolveWithPrefer") 178 public void resolveWithPrefer(String prefer, String cfile, String publicId, String systemId, String expected) { 179 String catalogFile = getClass().getResource(cfile).getFile(); 180 CatalogFeatures f = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).with(CatalogFeatures.Feature.RESOLVE, "ignore").build(); 181 CatalogResolver catalogResolver = CatalogManager.catalogResolver(f, catalogFile); 182 String result = catalogResolver.resolveEntity(publicId, systemId).getSystemId(); 183 Assert.assertEquals(expected, result); 184 } 185 186 /** 187 * @bug 8150969 188 * Verifies that the defer attribute set in the catalog file takes precedence 189 * over other settings, in which case, whether next and delegate Catalogs will 190 * be loaded is determined by the defer attribute. 191 */ 192 @Test(dataProvider = "invalidAltCatalogs", expectedExceptions = CatalogException.class) 193 public void testDeferAltCatalogs(String file) { 194 String catalogFile = getClass().getResource(file).getFile(); 195 CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "true").build(); 196 /* 197 Since the defer attribute is set to false in the specified catalog file, 198 the parent catalog will try to load the alt catalog, which will fail 199 since it points to an invalid catalog. 200 */ 201 Catalog catalog = CatalogManager.catalog(features, catalogFile); 202 } 203 204 /** 205 * @bug 8151154 206 * Verifies that the CatalogFeatures' builder throws IllegalArgumentException 207 * on invalid file inputs. 208 * @param file the file path 209 */ 210 @Test(dataProvider = "invalidPaths", expectedExceptions = IllegalArgumentException.class) 211 public void testFileInput(String file) { 212 CatalogFeatures features = CatalogFeatures.builder() 213 .with(CatalogFeatures.Feature.FILES, file) 214 .build(); 215 } 216 217 /** 218 * @bug 8146237 219 * PREFER from Features API taking precedence over catalog file 220 */ 221 @Test 222 public void testJDK8146237() { 223 String catalogFile = getClass().getResource("JDK8146237_catalog.xml").getFile(); 224 225 try { 226 CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, "system").build(); 227 Catalog catalog = CatalogManager.catalog(features, catalogFile); 228 CatalogResolver catalogResolver = CatalogManager.catalogResolver(catalog); 229 String actualSystemId = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", "http://www.oracle.com/alt1sys.dtd").getSystemId(); 230 Assert.assertTrue(actualSystemId.contains("dummy.dtd"), "Resulting id should contain dummy.dtd, indicating a match by publicId"); 231 232 } catch (Exception e) { 233 Assert.fail(e.getMessage()); 234 } 235 } 236 237 /* 238 @bug 8146606 239 Verifies that the resulting systemId does not contain duplicate slashes 240 */ 241 @Test 242 public void testRewriteSystem() { 243 String catalog = getClass().getResource("rewriteCatalog.xml").getFile(); 244 245 try { 246 CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog); 247 String actualSystemId = resolver.resolveEntity(null, "http://remote.com/dtd/book.dtd").getSystemId(); 248 Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes"); 249 } catch (Exception e) { 250 Assert.fail(e.getMessage()); 251 } 252 253 } 254 255 /* 256 @bug 8146606 257 Verifies that the resulting systemId does not contain duplicate slashes 258 */ 259 @Test 260 public void testRewriteUri() { 261 String catalog = getClass().getResource("rewriteCatalog.xml").getFile(); 262 263 try { 264 265 CatalogUriResolver resolver = CatalogManager.catalogUriResolver(CatalogFeatures.defaults(), catalog); 266 String actualSystemId = resolver.resolve("http://remote.com/import/import.xsl", null).getSystemId(); 267 Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes"); 268 } catch (Exception e) { 269 Assert.fail(e.getMessage()); 270 } 271 } 272 273 /* 274 @bug 8144966 275 Verifies that passing null as CatalogFeatures will result in a NPE. 276 */ 277 @Test(expectedExceptions = NullPointerException.class) 278 public void testFeatureNull() { 279 CatalogResolver resolver = CatalogManager.catalogResolver(null, ""); 280 281 } 282 283 /* 284 @bug 8144966 285 Verifies that passing null as the path will result in a NPE. 286 */ 287 @Test(expectedExceptions = NullPointerException.class) 288 public void testPathNull() { 289 String path = null; 290 CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), path); 291 } 292 293 /* 294 Tests basic catalog feature by using a CatalogResolver instance to 295 resolve a DTD reference to a locally specified DTD file. If the resolution 296 is successful, the Handler shall return the value of the entity reference 297 that matches the expected value. 298 */ 299 @Test(dataProvider = "catalog") 300 public void testCatalogResolver(String test, String expected, String catalogFile, String xml, SAXParser saxParser) { 301 String catalog = null; 302 if (catalogFile != null) { 303 catalog = getClass().getResource(catalogFile).getFile(); 304 } 305 String url = getClass().getResource(xml).getFile(); 306 try { 307 CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog); 308 XMLReader reader = saxParser.getXMLReader(); 309 reader.setEntityResolver(cr); 310 MyHandler handler = new MyHandler(saxParser); 311 reader.setContentHandler(handler); 312 reader.parse(url); 313 System.out.println(test + ": expected [" + expected + "] <> actual [" + handler.getResult() + "]"); 314 Assert.assertEquals(handler.getResult(), expected); 315 } catch (SAXException | IOException e) { 316 Assert.fail(e.getMessage()); 317 } 318 } 319 320 /* 321 Verifies that when there's no match, in this case only an invalid 322 catalog is provided, the resolver will throw an exception by default. 323 */ 324 @Test 325 public void testInvalidCatalog() { 326 String catalog = getClass().getResource("catalog_invalid.xml").getFile(); 327 328 String test = "testInvalidCatalog"; 329 try { 330 CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog); 331 String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId(); 332 } catch (Exception e) { 333 String msg = e.getMessage(); 334 if (msg != null) { 335 if (msg.contains("No match found for publicId")) { 336 Assert.assertEquals(msg, "No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'."); 337 System.out.println(test + ": expected [No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.]"); 338 System.out.println("actual [" + msg + "]"); 339 } 340 } 341 } 342 } 343 344 /* 345 Verifies that if resolve is "ignore", an empty InputSource will be returned 346 when there's no match. The systemId is then null. 347 */ 348 @Test 349 public void testIgnoreInvalidCatalog() { 350 String catalog = getClass().getResource("catalog_invalid.xml").getFile(); 351 CatalogFeatures f = CatalogFeatures.builder() 352 .with(Feature.FILES, catalog) 353 .with(Feature.PREFER, "public") 354 .with(Feature.DEFER, "true") 355 .with(Feature.RESOLVE, "ignore") 356 .build(); 357 358 String test = "testInvalidCatalog"; 359 try { 360 CatalogResolver resolver = CatalogManager.catalogResolver(f, ""); 361 String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId(); 362 System.out.println("testIgnoreInvalidCatalog: expected [null]"); 363 System.out.println("testIgnoreInvalidCatalog: expected [null]"); 364 System.out.println("actual [" + actualSystemId + "]"); 365 Assert.assertEquals(actualSystemId, null); 366 } catch (Exception e) { 367 Assert.fail(e.getMessage()); 368 } 369 } 370 371 372 /* 373 DataProvider: used to verify CatalogUriResolver's resolve function. 374 Data columns: 375 catalog, uri or publicId, expectedFile, expectedUri, msg 376 377 This DataProvider is copied from JCK ResolveTests' dataMatch1 378 */ 379 @DataProvider(name = "resolveUri") 380 Object[][] getDataForUriResolver() { 381 return new Object[][]{ 382 {"uri.xml", "urn:publicid:-:Acme,+Inc.:DTD+Book+Version+1.0", null, "http://local/base/dtd/book.dtd", "Uri in publicId namespace is incorrectly unwrapped"}, 383 }; 384 } 385 386 /* 387 DataProvider: used to verify hierarchical catalogs. Refer to JCK test 388 hierarchyOfCatFiles2. 389 */ 390 @DataProvider(name = "hierarchyOfCatFilesData") 391 Object[][] getHierarchyOfCatFilesData() { 392 return new Object[][]{ 393 {"http://www.oracle.com/sequence.dtd", "first.dtd"}, 394 {"http://www.oracle.com/sequence_next.dtd", "next.dtd"}, 395 {"http://www.oracle.com/sequence_second.dtd", "second.dtd"} 396 }; 397 } 398 399 /* 400 DataProvider: used to verify CatalogResolver's resolveEntity function. 401 Data columns: 402 catalog, prefer, systemId, publicId, expectedUri, expectedFile, msg 403 */ 404 @DataProvider(name = "resolveEntity") 405 Object[][] getDataForMatchingBothIds() { 406 String expected = "http://www.groupxmlbase.com/dtds/rewrite.dtd"; 407 return new Object[][]{ 408 {"rewriteSystem_id.xml", "system", "http://www.sys00test.com/rewrite.dtd", "PUB-404", expected, expected, "Relative rewriteSystem with xml:base at group level failed"}, 409 }; 410 } 411 412 static String id = "http://openjdk.java.net/xml/catalog/dtd/system.dtd"; 413 /* 414 DataProvider: used to verify how prefer settings affect the result of the 415 Catalog's matching operation. 416 Data columns: 417 prefer, catalog, publicId, systemId, expected result 418 */ 419 @DataProvider(name = "matchWithPrefer") 420 Object[][] getDataForMatch() { 421 return new Object[][]{ 422 {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"}, 423 {"public", "sysOnly.xml", id, "", null}, 424 {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"}, 425 {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"}, 426 {"system", "sysOnly.xml", id, "", null}, 427 {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"}, 428 {"public", "pubOnly.xml", "", id, null}, 429 {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"}, 430 {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"}, 431 {"system", "pubOnly.xml", "", id, null}, 432 {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"}, 433 {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"}, 434 }; 435 } 436 437 /* 438 DataProvider: used to verify how prefer settings affect the result of the 439 CatalogResolver's resolution operation. 440 Data columns: 441 prefer, catalog, publicId, systemId, expected result 442 */ 443 @DataProvider(name = "resolveWithPrefer") 444 Object[][] getDataForResolve() { 445 return new Object[][]{ 446 {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"}, 447 {"system", "pubOnly.xml", "", id, null}, 448 {"system", "pubOnly.xml", id, id, null}, 449 {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"}, 450 {"public", "pubOnly.xml", "", id, null}, 451 {"public", "pubOnly.xml", id, id, "http://local/base/dtd/public.dtd"}, 452 {"system", "sysOnly.xml", id, "", null}, 453 {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"}, 454 {"system", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"}, 455 {"public", "sysOnly.xml", id, "", null}, 456 {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"}, 457 {"public", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"}, 458 {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"}, 459 {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"}, 460 {"system", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"}, 461 {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"}, 462 {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"}, 463 {"public", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"}, 464 }; 465 } 466 /* 467 DataProvider: catalogs that contain invalid next or delegate catalogs. 468 The defer attribute is set to false. 469 */ 470 @DataProvider(name = "invalidAltCatalogs") 471 Object[][] getCatalogs() { 472 return new Object[][]{ 473 {"defer_false_2.xml"}, 474 {"defer_del_false.xml"} 475 }; 476 } 477 478 /* 479 DataProvider: for testing the verification of file paths by 480 the CatalogFeatures builder 481 */ 482 @DataProvider(name = "invalidPaths") 483 Object[][] getFiles() { 484 return new Object[][]{ 485 {null}, 486 {""}, 487 {"file:a/b\\c"}, 488 {"file:/../../.."}, 489 {"c:/te:t"}, 490 {"c:/te?t"}, 491 {"c/te*t"}, 492 {"in|valid.txt"}, 493 {"shema:invalid.txt"}, 494 }; 495 } 496 497 /* 498 DataProvider: provides test name, expected string, the catalog, and XML 499 document. 500 */ 501 @DataProvider(name = "catalog") 502 Object[][] getCatalog() { 503 return new Object[][]{ 504 {"testSystem", "Test system entry", "catalog.xml", "system.xml", getParser()}, 505 {"testRewriteSystem", "Test rewritesystem entry", "catalog.xml", "rewritesystem.xml", getParser()}, 506 {"testRewriteSystem1", "Test rewritesystem entry", "catalog.xml", "rewritesystem1.xml", getParser()}, 507 {"testSystemSuffix", "Test systemsuffix entry", "catalog.xml", "systemsuffix.xml", getParser()}, 508 {"testDelegateSystem", "Test delegatesystem entry", "catalog.xml", "delegatesystem.xml", getParser()}, 509 {"testPublic", "Test public entry", "catalog.xml", "public.xml", getParser()}, 510 {"testDelegatePublic", "Test delegatepublic entry", "catalog.xml", "delegatepublic.xml", getParser()}, 511 }; 512 } 513 514 SAXParser getParser() { 515 SAXParser saxParser = null; 516 try { 517 SAXParserFactory factory = SAXParserFactory.newInstance(); 518 factory.setNamespaceAware(true); 519 saxParser = factory.newSAXParser(); 520 } catch (ParserConfigurationException | SAXException e) { 521 } 522 523 return saxParser; 524 } 525 526 527 /** 528 * SAX handler 529 */ 530 @Listeners({jaxp.library.BasePolicy.class}) 531 public class MyHandler extends DefaultHandler2 implements ErrorHandler { 532 533 StringBuilder textContent = new StringBuilder(); 534 SAXParser saxParser; 535 536 MyHandler(SAXParser saxParser) { 537 textContent.setLength(0); 538 this.saxParser = saxParser; 539 } 540 541 String getResult() { 542 return textContent.toString(); 543 } 544 545 @Override 546 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 547 textContent.delete(0, textContent.length()); 548 try { 549 System.out.println("Element: " + uri + ":" + localName + " " + qName); 550 } catch (Exception e) { 551 throw new SAXException(e); 552 } 553 554 } 555 556 @Override 557 public void characters(char ch[], int start, int length) throws SAXException { 558 textContent.append(ch, start, length); 559 } 560 } 561 }