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