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