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