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 static jaxp.library.JAXPTestUtilities.clearSystemProperty;
  26 import static jaxp.library.JAXPTestUtilities.getSystemProperty;
  27 import static jaxp.library.JAXPTestUtilities.setSystemProperty;
  28 
  29 import java.io.File;
  30 import java.io.FileInputStream;
  31 import java.io.FilePermission;
  32 import java.io.IOException;
  33 import java.io.InputStream;
  34 import java.io.StringReader;
  35 import java.io.StringWriter;
  36 import java.nio.file.Paths;
  37 import java.util.PropertyPermission;
  38 import javax.xml.XMLConstants;
  39 import javax.xml.catalog.Catalog;
  40 import javax.xml.catalog.CatalogException;
  41 import javax.xml.catalog.CatalogFeatures;
  42 import javax.xml.catalog.CatalogFeatures.Feature;
  43 import javax.xml.catalog.CatalogManager;
  44 import javax.xml.catalog.CatalogResolver;
  45 import javax.xml.parsers.ParserConfigurationException;
  46 import javax.xml.parsers.SAXParser;
  47 import javax.xml.parsers.SAXParserFactory;
  48 import javax.xml.stream.XMLInputFactory;
  49 import javax.xml.stream.XMLStreamConstants;
  50 import javax.xml.stream.XMLStreamReader;
  51 import javax.xml.transform.Source;
  52 import javax.xml.transform.Transformer;
  53 import javax.xml.transform.TransformerFactory;
  54 import javax.xml.transform.sax.SAXSource;
  55 import javax.xml.transform.stream.StreamResult;
  56 import javax.xml.transform.stream.StreamSource;
  57 import javax.xml.validation.Schema;
  58 import javax.xml.validation.SchemaFactory;
  59 import javax.xml.validation.Validator;
  60 import jaxp.library.JAXPTestUtilities;
  61 
  62 import org.testng.Assert;
  63 import org.testng.annotations.BeforeClass;
  64 import org.testng.annotations.DataProvider;
  65 import org.testng.annotations.Listeners;
  66 import org.testng.annotations.Test;
  67 import org.xml.sax.Attributes;
  68 import org.xml.sax.ErrorHandler;
  69 import org.xml.sax.InputSource;
  70 import org.xml.sax.SAXException;
  71 import org.xml.sax.XMLReader;
  72 import org.xml.sax.ext.DefaultHandler2;
  73 
  74 /*
  75  * @test
  76  * @bug 8081248 8144966 8146606 8146237 8151154 8150969 8151162 8152527 8154220 8163232
  77  * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest
  78  * @run testng/othervm -DrunSecMngr=true catalog.CatalogTest
  79  * @run testng/othervm catalog.CatalogTest
  80  * @summary Tests basic Catalog functions.
  81  */
  82 @Listeners({jaxp.library.FilePolicy.class})
  83 public class CatalogTest extends CatalogSupportBase {
  84     static final String KEY_FILES = "javax.xml.catalog.files";
  85 
  86 
  87     /*
  88      * Initializing fields
  89      */
  90     @BeforeClass
  91     public void setUpClass() throws Exception {
  92         super.setUp();
  93     }
  94 
  95     /*
  96      * @bug 8162431
  97      * Verifies that circular references are caught and
  98      * CatalogException is thrown.
  99      */
 100     @Test(dataProvider = "getFeatures", expectedExceptions = CatalogException.class)
 101     public void testCircularRef(CatalogFeatures cf, String xml) {
 102         CatalogResolver catalogResolver = CatalogManager.catalogResolver(
 103                 cf,
 104                 getClass().getResource(xml).getFile());
 105         catalogResolver.resolve("anyuri", "");
 106     }
 107 
 108     /*
 109        DataProvider: used to verify circular reference
 110         Data columns: CatalogFeatures, catalog
 111      */
 112     @DataProvider(name = "getFeatures")
 113     public Object[][] getFeatures() {
 114 
 115         return new Object[][]{
 116             {CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "false").build(),
 117                 "catalogReferCircle-itself.xml"},
 118             {CatalogFeatures.defaults(), "catalogReferCircle-itself.xml"},
 119             {CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "false").build(),
 120                 "catalogReferCircle-left.xml"},
 121             {CatalogFeatures.defaults(), "catalogReferCircle-left.xml"},};
 122     }
 123 
 124     /*
 125      * @bug 8163232
 126      * Verifies that the CatalogResolver supports the following XML Resolvers:
 127           javax.xml.stream.XMLResolver
 128           javax.xml.transform.URIResolver
 129           org.w3c.dom.ls.LSResourceResolver
 130           org.xml.sax.EntityResolver
 131      *
 132      * Plus, system and uri entries can equally be used.
 133      */
 134 
 135     /*
 136      * Verifies the support for org.xml.sax.EntityResolver.
 137      * Expected: the parser returns the expected string.
 138     */
 139     @Test(dataProvider = "supportXMLResolver")
 140     public void supportEntityResolver(String catalogFile, String xml, String expected) throws Exception {
 141         String xmlSource = getClass().getResource(xml).getFile();
 142 
 143         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 144         MyCatalogHandler handler = new MyCatalogHandler(cr, elementInSystem);
 145         SAXParser parser = getSAXParser(false, true, null);
 146         parser.parse(xmlSource, handler);
 147 
 148         Assert.assertEquals(handler.getResult().trim(), expected);
 149     }
 150 
 151     /*
 152      * Verifies the support for javax.xml.stream.XMLResolver.
 153      * Expected: the parser returns the expected string.
 154     */
 155     @Test(dataProvider = "supportXMLResolver")
 156     public void supportXMLResolver(String catalogFile, String xml, String expected) throws Exception {
 157         String xmlSource = getClass().getResource(xml).getFile();
 158 
 159         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 160 
 161         XMLInputFactory xifactory = XMLInputFactory.newInstance();
 162         xifactory.setProperty(XMLInputFactory.IS_COALESCING, true);
 163         xifactory.setProperty(XMLInputFactory.RESOLVER, cr);
 164         File file = new File(xmlSource);
 165         String systemId = file.toURI().toString();
 166         InputStream entityxml = new FileInputStream(file);
 167         XMLStreamReader streamReader = xifactory.createXMLStreamReader(systemId, entityxml);
 168         String result = null;
 169         while (streamReader.hasNext()) {
 170             int eventType = streamReader.next();
 171             if (eventType == XMLStreamConstants.START_ELEMENT) {
 172                 eventType = streamReader.next();
 173                 if (eventType == XMLStreamConstants.CHARACTERS) {
 174                     result = streamReader.getText();
 175                 }
 176             }
 177         }
 178         System.out.println(": expected [" + expected + "] <> actual [" + result.trim() + "]");
 179 
 180         Assert.assertEquals(result.trim(), expected);
 181     }
 182 
 183     /*
 184      * Verifies the support for org.w3c.dom.ls.LSResourceResolver by ShemaFactory.
 185      * Success: parsing goes through with no error
 186      * Fail: throws Exception if references are not resolved (by the CatalogResolver)
 187     */
 188     @Test(dataProvider = "supportLSResourceResolver")
 189     public void supportLSResourceResolver(String catalogFile, Source schemaSource) throws SAXException {
 190 
 191         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 192 
 193         SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 194         factory.setResourceResolver(cr);
 195         Schema schema = factory.newSchema(schemaSource);
 196 
 197     }
 198 
 199     /*
 200      * Verifies the support for org.w3c.dom.ls.LSResourceResolver by Validator.
 201      * Success: parsing goes through with no error
 202      * Fail: throws Exception if references are not resolved (by the CatalogResolver)
 203     */
 204     @Test(dataProvider = "supportLSResourceResolver1")
 205     public void supportLSResourceResolver1(String catalogFile, Source source) throws Exception {
 206 
 207         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 208 
 209         SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 210         Validator validator = factory.newSchema().newValidator();
 211         validator.setResourceResolver(cr);
 212         validator.validate(source);
 213     }
 214 
 215     /*
 216      * Verifies the support for javax.xml.transform.URIResolver.
 217      * Success: parsing goes through with no error
 218      * Fail: throws Exception if references are not resolved (by the CatalogResolver)
 219     */
 220     @Test(dataProvider = "supportURIResolver")
 221     public void supportURIResolver(String catalogFile, Source xsl, Source xml, String expected) throws Exception {
 222 
 223         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 224 
 225             TransformerFactory factory = TransformerFactory.newInstance();
 226             factory.setURIResolver(cr);
 227             Transformer transformer = factory.newTransformer(xsl);
 228             StringWriter out = new StringWriter();
 229             transformer.transform(xml, new StreamResult(out));
 230             if (expected != null) {
 231                 Assert.assertTrue(out.toString().contains(expected), "supportURIResolver");
 232             }
 233     }
 234 
 235     /*
 236        DataProvider: used to verify the support of XML Resolvers.
 237         Data columns:
 238         catalog filepath, xml source file, expected result
 239      */
 240     @DataProvider(name = "supportXMLResolver")
 241     public Object[][] supportXMLResolver() {
 242         String catalogFile = getClass().getResource("catalog.xml").getFile();
 243         String catalogFileUri = getClass().getResource("catalog_uri.xml").getFile();
 244 
 245         return new Object[][]{
 246             {catalogFile, "system.xml", "Test system entry"},
 247             {catalogFile, "rewritesystem.xml", "Test rewritesystem entry"},
 248             {catalogFile, "rewritesystem1.xml", "Test rewritesystem entry"},
 249             {catalogFile, "systemsuffix.xml", "Test systemsuffix entry"},
 250             {catalogFile, "delegatesystem.xml", "Test delegatesystem entry"},
 251             {catalogFile, "public.xml", "Test public entry"},
 252             {catalogFile, "delegatepublic.xml", "Test delegatepublic entry"},
 253             // using uri entries
 254             {catalogFileUri, "system.xml", "Test system entry"},
 255             {catalogFileUri, "rewritesystem.xml", "Test rewritesystem entry"},
 256             {catalogFileUri, "rewritesystem1.xml", "Test rewritesystem entry"},
 257             {catalogFileUri, "systemsuffix.xml", "Test systemsuffix entry"},
 258             {catalogFileUri, "delegateuri.xml", "Test delegateuri entry"},
 259             {catalogFileUri, "public.xml", "Test public entry"},
 260          };
 261     }
 262 
 263     /*
 264        DataProvider: used to verify the support of LSResourceResolver by SchemaFactory.
 265         Data columns:
 266         catalog filepath, schema source file
 267      */
 268     @DataProvider(name = "supportLSResourceResolver")
 269     public Object[][] supportLSResourceResolver() {
 270         String catalogFile = getClass().getResource("CatalogSupport.xml").getFile();
 271         String catalogFileUri = getClass().getResource("CatalogSupport_uri.xml").getFile();
 272 
 273         /*
 274          * XMLSchema.xsd has a reference to XMLSchema.dtd which in turn refers to
 275          * datatypes.dtd
 276         */
 277         return new Object[][]{
 278             {catalogFile, new StreamSource(new StringReader(xsd_xmlSchema))},
 279             {catalogFile, new StreamSource(new StringReader(xsd_xmlSchema_import))},
 280             {catalogFile, new StreamSource(new StringReader(xsd_include_company))},
 281             {catalogFileUri, new StreamSource(new StringReader(xsd_xmlSchema))},
 282             {catalogFileUri, new StreamSource(new StringReader(xsd_xmlSchema_import))},
 283             {catalogFileUri, new StreamSource(new StringReader(xsd_include_company))},
 284          };
 285     }
 286 
 287     /*
 288        DataProvider: used to verify the support of LSResourceResolver by Validator.
 289         Data columns:
 290         catalog filepath, source file
 291      */
 292     @DataProvider(name = "supportLSResourceResolver1")
 293     public Object[][] supportLSResourceResolver1() {
 294         String catalogFile = getClass().getResource("CatalogSupport.xml").getFile();
 295         String catalogFileUri = getClass().getResource("CatalogSupport_uri.xml").getFile();
 296 
 297         /*
 298          * val_test.xml has a reference to system.dtd and val_test.xsd
 299         */
 300         SAXSource ss = new SAXSource(new InputSource(xml_val_test));
 301         ss.setSystemId(xml_val_test_id);
 302 
 303         return new Object[][]{
 304             {catalogFile, ss},
 305             {catalogFileUri, ss},
 306          };
 307     }
 308 
 309 
 310     /*
 311        DataProvider: used to verify the support of LSResourceResolver by Validator.
 312         Data columns:
 313         catalog filepath, xsl source, xml source file
 314      */
 315     @DataProvider(name = "supportURIResolver")
 316     public Object[][] supportURIResolver() {
 317         String catalogFile = getClass().getResource("CatalogSupport.xml").getFile();
 318         String catalogFileUri = getClass().getResource("CatalogSupport_uri.xml").getFile();
 319         SAXSource xslSource = new SAXSource(new InputSource(new File(xsl_doc).toURI().toASCIIString()));
 320 
 321         /*
 322          * val_test.xml has a reference to system.dtd and val_test.xsd
 323         */
 324         SAXSource ss = new SAXSource(new InputSource(xml_val_test));
 325         ss.setSystemId(xml_val_test_id);
 326 
 327         return new Object[][]{
 328             {catalogFile, new SAXSource(new InputSource(new File(xsl_doc).toURI().toASCIIString())),
 329                 new StreamSource(new File(xml_doc)), "Resolved by a catalog"},
 330             {catalogFileUri, new SAXSource(new InputSource(new StringReader(xsl_include))),
 331                 new StreamSource(new StringReader(xml_xsl)), null},
 332          };
 333     }
 334 
 335     /*
 336      * @bug 8150187
 337      * NPE is expected if the systemId is null. The specification for systemId
 338      * is as follows:
 339      * A system identifier is required on all external entities. XML
 340      * requires a system identifier on all external entities, so this value is
 341      * always specified.
 342      */
 343     @Test(expectedExceptions = NullPointerException.class)
 344     public void sysIdCantBeNull() {
 345         CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults());
 346         InputSource is = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", null);
 347     }
 348 
 349     /*
 350      * @bug 8156845
 351      * Verifies that an URI reference with a urn:publicid is correctly resolved
 352      * with an uri entry with a publicId.
 353      *
 354      * @param expectedFile is not used in this test, it's kept since we're
 355      * copying the JCK test and its dataProvider. This test may be reused for
 356      * other cases in that test.
 357      */
 358     @Test(dataProvider = "resolveUri")
 359     public void testMatch1(String cFile, String href, String expectedFile, String expectedUri, String msg) {
 360         String catalogFile = getClass().getResource(cFile).getFile();
 361         CatalogResolver cur = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 362         Source source = cur.resolve(href, null);
 363         Assert.assertNotNull(source, "Source returned is null");
 364         Assert.assertEquals(expectedUri, source.getSystemId(), msg);
 365     }
 366 
 367     /*
 368      * @bug 8154220
 369      * Verifies that the file input is validated properly. Valid input includes
 370      * multiple file paths separated by semicolon.
 371      */
 372     @Test(dataProvider = "hierarchyOfCatFilesData")
 373     public void hierarchyOfCatFiles2(String systemId, String expectedUri) {
 374         String file1 = getClass().getResource("first_cat.xml").getFile();
 375         String file2 = getClass().getResource("second_cat.xml").getFile();
 376         String files = file1 + ";" + file2;
 377 
 378         try {
 379             setSystemProperty(KEY_FILES, files);
 380             CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults());
 381             String sysId = catalogResolver.resolveEntity(null, systemId).getSystemId();
 382             Assert.assertEquals(sysId, Paths.get(filepath + expectedUri).toUri().toString().replace("///", "/"), "System ID match not right");
 383         } finally {
 384             clearSystemProperty(KEY_FILES);
 385         }
 386 
 387     }
 388 
 389     /*
 390      * @bug 8152527
 391      * This test is the same as the JDK test ResolveEntityTests:testMatch1.
 392      * Verifies that the CatalogResolver resolves a publicId and/or systemId as
 393      * expected.
 394      */
 395     @Test(dataProvider = "resolveEntity")
 396     public void testMatch1(String cfile, String prefer, String sysId, String pubId, String expectedUri, String expectedFile, String msg) {
 397         String catalogFile = getClass().getResource(cfile).getFile();
 398         CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build();
 399         CatalogResolver catalogResolver = CatalogManager.catalogResolver(features, catalogFile);
 400         InputSource is = catalogResolver.resolveEntity(pubId, sysId);
 401         Assert.assertNotNull(is, msg);
 402         String expected = (expectedUri == null) ? expectedFile : expectedUri;
 403         Assert.assertEquals(expected, is.getSystemId(), msg);
 404     }
 405 
 406     /*
 407      * @bug 8151162
 408      * Verifies that the Catalog matches specified publicId or systemId and returns
 409      * results as expected.
 410      */
 411     @Test(dataProvider = "matchWithPrefer")
 412     public void matchWithPrefer(String prefer, String cfile, String publicId, String systemId, String expected) {
 413         String catalogFile = getClass().getResource(cfile).getFile();
 414         Catalog c = CatalogManager.catalog(CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build(), catalogFile);
 415         String result;
 416         if (publicId != null && publicId.length() > 0) {
 417             result = c.matchPublic(publicId);
 418         } else {
 419             result = c.matchSystem(systemId);
 420         }
 421         Assert.assertEquals(expected, result);
 422     }
 423 
 424     /*
 425      * @bug 8151162
 426      * Verifies that the CatalogResolver resolves specified publicId or systemId
 427      * in accordance with the prefer setting.
 428      * prefer "system": resolves with a system entry.
 429      *                  Exception: use the public entry when the catalog contains
 430      *                  only public entry and only publicId is specified.
 431      * prefer "public": attempts to resolve with a system entry;
 432      *                  attempts to resolve with a public entry if no matching
 433      *                  system entry is found.
 434      */
 435     @Test(dataProvider = "resolveWithPrefer")
 436     public void resolveWithPrefer(String prefer, String cfile, String publicId, String systemId, String expected) {
 437         String catalogFile = getClass().getResource(cfile).getFile();
 438         CatalogFeatures f = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).with(CatalogFeatures.Feature.RESOLVE, "ignore").build();
 439         CatalogResolver catalogResolver = CatalogManager.catalogResolver(f, catalogFile);
 440         String result = catalogResolver.resolveEntity(publicId, systemId).getSystemId();
 441         Assert.assertEquals(expected, result);
 442     }
 443 
 444     /**
 445      * @bug 8150969
 446      * Verifies that the defer attribute set in the catalog file takes precedence
 447      * over other settings, in which case, whether next and delegate Catalogs will
 448      * be loaded is determined by the defer attribute.
 449      */
 450     @Test(dataProvider = "invalidAltCatalogs", expectedExceptions = CatalogException.class)
 451     public void testDeferAltCatalogs(String file) {
 452         String catalogFile = getClass().getResource(file).getFile();
 453         CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "true").build();
 454         /*
 455           Since the defer attribute is set to false in the specified catalog file,
 456           the parent catalog will try to load the alt catalog, which will fail
 457           since it points to an invalid catalog.
 458         */
 459         Catalog catalog = CatalogManager.catalog(features, catalogFile);
 460     }
 461 
 462     /**
 463      * @bug 8151154
 464      * Verifies that the CatalogFeatures' builder throws IllegalArgumentException
 465      * on invalid file inputs.
 466      * @param file the file path
 467      */
 468     @Test(dataProvider = "invalidPaths", expectedExceptions = IllegalArgumentException.class)
 469     public void testFileInput(String file) {
 470         JAXPTestUtilities.runWithTmpPermission(() -> {
 471             CatalogFeatures features = CatalogFeatures.builder()
 472                 .with(CatalogFeatures.Feature.FILES, file)
 473                 .build();
 474         }, new FilePermission("/../../..", "read"), new FilePermission("c:\\te:t", "read"),
 475                 new FilePermission("c:\\te?t", "read"), new PropertyPermission("user.dir", "read"));
 476     }
 477 
 478     /**
 479      * @bug 8146237
 480      * PREFER from Features API taking precedence over catalog file
 481      */
 482     @Test
 483     public void testJDK8146237() {
 484         String catalogFile = getClass().getResource("JDK8146237_catalog.xml").getFile();
 485 
 486         try {
 487             CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, "system").build();
 488             Catalog catalog = CatalogManager.catalog(features, catalogFile);
 489             CatalogResolver catalogResolver = CatalogManager.catalogResolver(catalog);
 490             String actualSystemId = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", "http://www.oracle.com/alt1sys.dtd").getSystemId();
 491             Assert.assertTrue(actualSystemId.contains("dummy.dtd"), "Resulting id should contain dummy.dtd, indicating a match by publicId");
 492 
 493         } catch (Exception e) {
 494             Assert.fail(e.getMessage());
 495         }
 496     }
 497 
 498     /*
 499        @bug 8146606
 500        Verifies that the resulting systemId does not contain duplicate slashes
 501     */
 502     @Test
 503     public void testRewriteSystem() {
 504         String catalog = getClass().getResource("rewriteCatalog.xml").getFile();
 505 
 506         try {
 507             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 508             String actualSystemId = resolver.resolveEntity(null, "http://remote.com/dtd/book.dtd").getSystemId();
 509             Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes");
 510         } catch (Exception e) {
 511             Assert.fail(e.getMessage());
 512         }
 513 
 514     }
 515 
 516     /*
 517        @bug 8146606
 518        Verifies that the resulting systemId does not contain duplicate slashes
 519     */
 520     @Test
 521     public void testRewriteUri() {
 522         String catalog = getClass().getResource("rewriteCatalog.xml").getFile();
 523 
 524         try {
 525 
 526             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 527             String actualSystemId = resolver.resolve("http://remote.com/import/import.xsl", null).getSystemId();
 528             Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes");
 529         } catch (Exception e) {
 530             Assert.fail(e.getMessage());
 531         }
 532     }
 533 
 534     /*
 535        @bug 8144966
 536        Verifies that passing null as CatalogFeatures will result in a NPE.
 537     */
 538     @Test(expectedExceptions = NullPointerException.class)
 539     public void testFeatureNull() {
 540         CatalogResolver resolver = CatalogManager.catalogResolver(null, "");
 541 
 542     }
 543 
 544     /*
 545        @bug 8144966
 546        Verifies that passing null as the path will result in a NPE.
 547     */
 548     @Test(expectedExceptions = NullPointerException.class)
 549     public void testPathNull() {
 550         String path = null;
 551         CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), path);
 552     }
 553 
 554     /*
 555        Tests basic catalog feature by using a CatalogResolver instance to
 556     resolve a DTD reference to a locally specified DTD file. If the resolution
 557     is successful, the Handler shall return the value of the entity reference
 558     that matches the expected value.
 559      */
 560     @Test(dataProvider = "catalog")
 561     public void testCatalogResolver(String test, String expected, String catalogFile, String xml, SAXParser saxParser) {
 562         String catalog = null;
 563         if (catalogFile != null) {
 564             catalog = getClass().getResource(catalogFile).getFile();
 565         }
 566         String url = getClass().getResource(xml).getFile();
 567         try {
 568             CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 569             XMLReader reader = saxParser.getXMLReader();
 570             reader.setEntityResolver(cr);
 571             MyHandler handler = new MyHandler(saxParser);
 572             reader.setContentHandler(handler);
 573             reader.parse(url);
 574             System.out.println(test + ": expected [" + expected + "] <> actual [" + handler.getResult() + "]");
 575             Assert.assertEquals(handler.getResult(), expected);
 576         } catch (SAXException | IOException e) {
 577             Assert.fail(e.getMessage());
 578         }
 579     }
 580 
 581     /*
 582        Verifies that when there's no match, in this case only an invalid
 583     catalog is provided, the resolver will throw an exception by default.
 584     */
 585     @Test
 586     public void testInvalidCatalog() {
 587         String catalog = getClass().getResource("catalog_invalid.xml").getFile();
 588 
 589         String test = "testInvalidCatalog";
 590         try {
 591             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 592             String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId();
 593         } catch (Exception e) {
 594             String msg = e.getMessage();
 595             if (msg != null) {
 596                 if (msg.contains("No match found for publicId")) {
 597                     Assert.assertEquals(msg, "No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.");
 598                     System.out.println(test + ": expected [No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.]");
 599                     System.out.println("actual [" + msg + "]");
 600                 }
 601             }
 602         }
 603     }
 604 
 605     /*
 606        Verifies that if resolve is "ignore", an empty InputSource will be returned
 607     when there's no match. The systemId is then null.
 608     */
 609     @Test
 610     public void testIgnoreInvalidCatalog() {
 611         String catalog = getClass().getResource("catalog_invalid.xml").getFile();
 612         CatalogFeatures f = CatalogFeatures.builder()
 613                 .with(Feature.FILES, catalog)
 614                 .with(Feature.PREFER, "public")
 615                 .with(Feature.DEFER, "true")
 616                 .with(Feature.RESOLVE, "ignore")
 617                 .build();
 618 
 619         String test = "testInvalidCatalog";
 620         try {
 621             CatalogResolver resolver = CatalogManager.catalogResolver(f, "");
 622             String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId();
 623             System.out.println("testIgnoreInvalidCatalog: expected [null]");
 624             System.out.println("testIgnoreInvalidCatalog: expected [null]");
 625             System.out.println("actual [" + actualSystemId + "]");
 626             Assert.assertEquals(actualSystemId, null);
 627         } catch (Exception e) {
 628             Assert.fail(e.getMessage());
 629         }
 630     }
 631 
 632 
 633     /*
 634         DataProvider: used to verify CatalogResolver's resolve function.
 635         Data columns:
 636         catalog, uri or publicId, expectedFile, expectedUri, msg
 637 
 638         This DataProvider is copied from JCK ResolveTests' dataMatch1
 639      */
 640     @DataProvider(name = "resolveUri")
 641     public Object[][] getDataForUriResolver() {
 642         return new Object[][]{
 643             {"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"},
 644         };
 645     }
 646 
 647     /*
 648         DataProvider: used to verify hierarchical catalogs. Refer to JCK test
 649     hierarchyOfCatFiles2.
 650      */
 651     @DataProvider(name = "hierarchyOfCatFilesData")
 652     public Object[][] getHierarchyOfCatFilesData() {
 653         return new Object[][]{
 654             {"http://www.oracle.com/sequence.dtd", "first.dtd"},
 655             {"http://www.oracle.com/sequence_next.dtd", "next.dtd"},
 656             {"http://www.oracle.com/sequence_second.dtd", "second.dtd"}
 657         };
 658     }
 659 
 660     /*
 661         DataProvider: used to verify CatalogResolver's resolveEntity function.
 662         Data columns:
 663         catalog, prefer, systemId, publicId, expectedUri, expectedFile, msg
 664      */
 665     @DataProvider(name = "resolveEntity")
 666     public Object[][] getDataForMatchingBothIds() {
 667         String expected = "http://www.groupxmlbase.com/dtds/rewrite.dtd";
 668         return new Object[][]{
 669             {"rewriteSystem_id.xml", "system", "http://www.sys00test.com/rewrite.dtd", "PUB-404", expected, expected, "Relative rewriteSystem with xml:base at group level failed"},
 670         };
 671     }
 672 
 673     static String id = "http://openjdk.java.net/xml/catalog/dtd/system.dtd";
 674     /*
 675        DataProvider: used to verify how prefer settings affect the result of the
 676         Catalog's matching operation.
 677         Data columns:
 678         prefer, catalog, publicId, systemId, expected result
 679      */
 680     @DataProvider(name = "matchWithPrefer")
 681     public Object[][] getDataForMatch() {
 682         return new Object[][]{
 683             {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 684             {"public", "sysOnly.xml", id, "", null},
 685             {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 686             {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 687             {"system", "sysOnly.xml", id, "", null},
 688             {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 689             {"public", "pubOnly.xml", "", id, null},
 690             {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 691             {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 692             {"system", "pubOnly.xml", "", id, null},
 693             {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 694             {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 695         };
 696     }
 697 
 698     /*
 699        DataProvider: used to verify how prefer settings affect the result of the
 700         CatalogResolver's resolution operation.
 701         Data columns:
 702         prefer, catalog, publicId, systemId, expected result
 703      */
 704     @DataProvider(name = "resolveWithPrefer")
 705     public Object[][] getDataForResolve() {
 706         return new Object[][]{
 707             {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 708             {"system", "pubOnly.xml", "", id, null},
 709             {"system", "pubOnly.xml", id, id, null},
 710             {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 711             {"public", "pubOnly.xml", "", id, null},
 712             {"public", "pubOnly.xml", id, id, "http://local/base/dtd/public.dtd"},
 713             {"system", "sysOnly.xml", id, "", null},
 714             {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 715             {"system", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"},
 716             {"public", "sysOnly.xml", id, "", null},
 717             {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 718             {"public", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"},
 719             {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 720             {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 721             {"system", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"},
 722             {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 723             {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 724             {"public", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"},
 725         };
 726     }
 727     /*
 728        DataProvider: catalogs that contain invalid next or delegate catalogs.
 729                      The defer attribute is set to false.
 730      */
 731     @DataProvider(name = "invalidAltCatalogs")
 732     public Object[][] getCatalogs() {
 733         return new Object[][]{
 734             {"defer_false_2.xml"},
 735             {"defer_del_false.xml"}
 736         };
 737     }
 738 
 739     /*
 740        DataProvider: for testing the verification of file paths by
 741                      the CatalogFeatures builder
 742      */
 743     @DataProvider(name = "invalidPaths")
 744     public Object[][] getFiles() {
 745         return new Object[][]{
 746             {null},
 747             {""},
 748             {"file:a/b\\c"},
 749             {"file:/../../.."},
 750             {"c:/te:t"},
 751             {"c:/te?t"},
 752             {"c/te*t"},
 753             {"in|valid.txt"},
 754             {"shema:invalid.txt"},
 755         };
 756     }
 757 
 758     /*
 759        DataProvider: provides test name, expected string, the catalog, and XML
 760        document.
 761      */
 762     @DataProvider(name = "catalog")
 763     public Object[][] getCatalog() {
 764         return new Object[][]{
 765             {"testSystem", "Test system entry", "catalog.xml", "system.xml", getParser()},
 766             {"testRewriteSystem", "Test rewritesystem entry", "catalog.xml", "rewritesystem.xml", getParser()},
 767             {"testRewriteSystem1", "Test rewritesystem entry", "catalog.xml", "rewritesystem1.xml", getParser()},
 768             {"testSystemSuffix", "Test systemsuffix entry", "catalog.xml", "systemsuffix.xml", getParser()},
 769             {"testDelegateSystem", "Test delegatesystem entry", "catalog.xml", "delegatesystem.xml", getParser()},
 770             {"testPublic", "Test public entry", "catalog.xml", "public.xml", getParser()},
 771             {"testDelegatePublic", "Test delegatepublic entry", "catalog.xml", "delegatepublic.xml", getParser()},
 772         };
 773     }
 774 
 775     SAXParser getParser() {
 776         SAXParser saxParser = null;
 777         try {
 778             SAXParserFactory factory = SAXParserFactory.newInstance();
 779             factory.setNamespaceAware(true);
 780             saxParser = factory.newSAXParser();
 781         } catch (ParserConfigurationException | SAXException e) {
 782         }
 783 
 784         return saxParser;
 785     }
 786 
 787     /**
 788      * SAX handler
 789      */
 790     public class MyHandler extends DefaultHandler2 implements ErrorHandler {
 791 
 792         StringBuilder textContent = new StringBuilder();
 793         SAXParser saxParser;
 794 
 795         MyHandler(SAXParser saxParser) {
 796             textContent.setLength(0);
 797             this.saxParser = saxParser;
 798         }
 799 
 800         String getResult() {
 801             return textContent.toString();
 802         }
 803 
 804         @Override
 805         public void startElement(String uri, String localName, String qName, Attributes attributes)
 806                 throws SAXException {
 807             textContent.delete(0, textContent.length());
 808             try {
 809                 System.out.println("Element: " + uri + ":" + localName + " " + qName);
 810             } catch (Exception e) {
 811                 throw new SAXException(e);
 812             }
 813 
 814         }
 815 
 816         @Override
 817         public void characters(char ch[], int start, int length) throws SAXException {
 818             textContent.append(ch, start, length);
 819         }
 820     }
 821 }