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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.xml.catalog;
  26 
  27 import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.io.Reader;
  31 import java.io.StringReader;
  32 import java.net.URL;
  33 import javax.xml.parsers.ParserConfigurationException;
  34 import javax.xml.parsers.SAXParserFactory;
  35 import javax.xml.transform.Source;
  36 import javax.xml.transform.sax.SAXSource;
  37 import org.w3c.dom.ls.LSInput;
  38 import org.xml.sax.InputSource;
  39 import org.xml.sax.SAXException;
  40 import org.xml.sax.XMLReader;
  41 
  42 /**
  43  * Implements CatalogResolver.
  44  *
  45  * <p>
  46  * This class implements a SAX EntityResolver, StAX XMLResolver,
  47  * Schema Validation LSResourceResolver and Transform URIResolver.
  48  *
  49  *
  50  * @since 9
  51  */
  52 final class CatalogResolverImpl implements CatalogResolver {
  53     Catalog catalog;
  54 
  55     /**
  56      * Construct an instance of the CatalogResolver from a Catalog.
  57      *
  58      * @param catalog A Catalog.
  59      */
  60     public CatalogResolverImpl(Catalog catalog) {
  61         this.catalog = catalog;
  62     }
  63 
  64     /*
  65        Implements the EntityResolver interface
  66     */
  67     @Override
  68     public InputSource resolveEntity(String publicId, String systemId) {
  69         //8150187: NPE expected if the system identifier is null for CatalogResolver
  70         CatalogMessages.reportNPEOnNull("systemId", systemId);
  71 
  72         //Normalize publicId and systemId
  73         systemId = Normalizer.normalizeURI(Util.getNotNullOrEmpty(systemId));
  74         publicId = Normalizer.normalizePublicId(Normalizer.decodeURN(Util.getNotNullOrEmpty(publicId)));
  75 
  76         //check whether systemId is an urn
  77         if (systemId != null && systemId.startsWith(Util.URN)) {
  78             systemId = Normalizer.decodeURN(systemId);
  79             if (publicId != null && !publicId.equals(systemId)) {
  80                 systemId = null;
  81             } else {
  82                 publicId = systemId;
  83                 systemId = null;
  84             }
  85         }
  86 
  87         CatalogImpl c = (CatalogImpl)catalog;
  88         String resolvedSystemId = Util.resolve(c, publicId, systemId);
  89 
  90         if (resolvedSystemId != null) {
  91             return new InputSource(resolvedSystemId);
  92         }
  93 
  94         GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve();
  95         switch (resolveType) {
  96             case IGNORE:
  97                 return new InputSource(new StringReader(""));
  98             case STRICT:
  99                 CatalogMessages.reportError(CatalogMessages.ERR_NO_MATCH,
 100                         new Object[]{publicId, systemId});
 101         }
 102 
 103         //no action, allow the parser to continue
 104         return null;
 105     }
 106 
 107     /*
 108         Implements the URIResolver interface
 109     */
 110     CatalogResolverImpl entityResolver;
 111 
 112     @Override
 113     public Source resolve(String href, String base) {
 114         CatalogMessages.reportNPEOnNull("href", href);
 115 
 116         href = Util.getNotNullOrEmpty(href);
 117         base = Util.getNotNullOrEmpty(base);
 118 
 119         String result = null;
 120         CatalogImpl c = (CatalogImpl)catalog;
 121         String uri = Normalizer.normalizeURI(href);
 122         if (uri == null) {
 123             return null;
 124         }
 125 
 126         //check whether uri is an urn
 127         if (uri != null && uri.startsWith(Util.URN)) {
 128             String publicId = Normalizer.decodeURN(uri);
 129             if (publicId != null) {
 130                 result = Util.resolve(c, publicId, null);
 131             }
 132         }
 133 
 134         //if no match with a public id, continue search for an URI
 135         if (result == null) {
 136             //remove fragment if any.
 137             int hashPos = uri.indexOf("#");
 138             if (hashPos >= 0) {
 139                 uri = uri.substring(0, hashPos);
 140             }
 141 
 142             //search the current catalog
 143             result = Util.resolve(c, null, uri);
 144         }
 145 
 146         //Report error or return the URI as is when no match is found
 147         if (result == null) {
 148             GroupEntry.ResolveType resolveType = c.getResolve();
 149             switch (resolveType) {
 150                 case IGNORE:
 151                     return new SAXSource(new InputSource(new StringReader("")));
 152                 case STRICT:
 153                     CatalogMessages.reportError(CatalogMessages.ERR_NO_URI_MATCH,
 154                             new Object[]{href, base});
 155             }
 156             try {
 157                 URL url = null;
 158 
 159                 if (base == null) {
 160                     url = new URL(uri);
 161                     result = url.toString();
 162                 } else {
 163                     URL baseURL = new URL(base);
 164                     url = (href.length() == 0 ? baseURL : new URL(baseURL, uri));
 165                     result = url.toString();
 166                 }
 167             } catch (java.net.MalformedURLException mue) {
 168                     CatalogMessages.reportError(CatalogMessages.ERR_CREATING_URI,
 169                             new Object[]{href, base});
 170             }
 171         }
 172 
 173         SAXSource source = new SAXSource();
 174         source.setInputSource(new InputSource(result));
 175         setEntityResolver(source);
 176         return source;
 177     }
 178 
 179     /**
 180      * Establish an entityResolver for newly resolved URIs.
 181      * <p>
 182      * This is called from the URIResolver to set an EntityResolver on the SAX
 183      * parser to be used for new XML documents that are encountered as a result
 184      * of the document() function, xsl:import, or xsl:include. This is done
 185      * because the XSLT processor calls out to the SAXParserFactory itself to
 186      * create a new SAXParser to parse the new document. The new parser does not
 187      * automatically inherit the EntityResolver of the original (although
 188      * arguably it should). Quote from JAXP specification on Class
 189      * SAXTransformerFactory:
 190      * <p>
 191      * {@code If an application wants to set the ErrorHandler or EntityResolver
 192      * for an XMLReader used during a transformation, it should use a URIResolver
 193      * to return the SAXSource which provides (with getXMLReader) a reference to
 194      * the XMLReader}
 195      *
 196      */
 197     private void setEntityResolver(SAXSource source) {
 198         XMLReader reader = source.getXMLReader();
 199         if (reader == null) {
 200             SAXParserFactory spFactory = new SAXParserFactoryImpl();
 201             spFactory.setNamespaceAware(true);
 202             try {
 203                 reader = spFactory.newSAXParser().getXMLReader();
 204             } catch (ParserConfigurationException | SAXException ex) {
 205                 CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSER_CONF, ex);
 206             }
 207         }
 208         if (entityResolver != null) {
 209             entityResolver = new CatalogResolverImpl(catalog);
 210         }
 211         reader.setEntityResolver(entityResolver);
 212         source.setXMLReader(reader);
 213     }
 214 
 215     @Override
 216     public InputStream resolveEntity(String publicId, String systemId, String baseUri, String namespace) {
 217         InputSource is = resolveEntity(publicId, systemId);
 218 
 219         if (is != null && !is.isEmpty()) {
 220 
 221             try {
 222                 return new URL(is.getSystemId()).openStream();
 223             } catch (IOException ex) {
 224                 //considered as no mapping.
 225             }
 226 
 227         }
 228 
 229         GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve();
 230         switch (resolveType) {
 231             case IGNORE:
 232                 return null;
 233             case STRICT:
 234                 CatalogMessages.reportError(CatalogMessages.ERR_NO_MATCH,
 235                         new Object[]{publicId, systemId});
 236         }
 237 
 238         //no action, allow the parser to continue
 239         return null;
 240     }
 241 
 242     @Override
 243     public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
 244         InputSource is = resolveEntity(publicId, systemId);
 245 
 246         if (is != null && !is.isEmpty()) {
 247             return new LSInputImpl(is.getSystemId());
 248         }
 249 
 250         GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve();
 251         switch (resolveType) {
 252             case IGNORE:
 253                 return null;
 254             case STRICT:
 255                 CatalogMessages.reportError(CatalogMessages.ERR_NO_MATCH,
 256                         new Object[]{publicId, systemId});
 257         }
 258 
 259         //no action, allow the parser to continue
 260         return null;
 261     }
 262 
 263     /**
 264      * Implements LSInput. All that we need is the systemId since the Catalog
 265      * has already resolved it.
 266      */
 267     class LSInputImpl implements LSInput {
 268 
 269         private String systemId;
 270 
 271         public LSInputImpl(String systemId) {
 272             this.systemId = systemId;
 273         }
 274 
 275         @Override
 276         public Reader getCharacterStream() {
 277             return null;
 278         }
 279 
 280         @Override
 281         public void setCharacterStream(Reader characterStream) {
 282         }
 283 
 284         @Override
 285         public InputStream getByteStream() {
 286             return null;
 287         }
 288 
 289         @Override
 290         public void setByteStream(InputStream byteStream) {
 291         }
 292 
 293         @Override
 294         public String getStringData() {
 295             return null;
 296         }
 297 
 298         @Override
 299         public void setStringData(String stringData) {
 300         }
 301 
 302         @Override
 303         public String getSystemId() {
 304             return systemId;
 305         }
 306 
 307         @Override
 308         public void setSystemId(String systemId) {
 309             this.systemId = systemId;
 310         }
 311 
 312         @Override
 313         public String getPublicId() {
 314             return null;
 315         }
 316 
 317         @Override
 318         public void setPublicId(String publicId) {
 319         }
 320 
 321         @Override
 322         public String getBaseURI() {
 323             return null;
 324         }
 325 
 326         @Override
 327         public void setBaseURI(String baseURI) {
 328         }
 329 
 330         @Override
 331         public String getEncoding() {
 332             return null;
 333         }
 334 
 335         @Override
 336         public void setEncoding(String encoding) {
 337         }
 338 
 339         @Override
 340         public boolean getCertifiedText() {
 341             return false;
 342         }
 343 
 344         @Override
 345         public void setCertifiedText(boolean certifiedText) {
 346         }
 347     }
 348 
 349 }