1 /*
   2  * Licensed to the Apache Software Foundation (ASF) under one or more
   3  * contributor license agreements.  See the NOTICE file distributed with
   4  * this work for additional information regarding copyright ownership.
   5  * The ASF licenses this file to You under the Apache License, Version 2.0
   6  * (the "License"); you may not use this file except in compliance with
   7  * the License.  You may obtain a copy of the License at
   8  *
   9  *      http://www.apache.org/licenses/LICENSE-2.0
  10  *
  11  * Unless required by applicable law or agreed to in writing, software
  12  * distributed under the License is distributed on an "AS IS" BASIS,
  13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14  * See the License for the specific language governing permissions and
  15  * limitations under the License.
  16  */
  17 
  18 package com.sun.org.apache.xml.internal.resolver.tools;
  19 
  20 import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
  21 import java.io.IOException;
  22 import java.io.InputStream;
  23 import java.net.URL;
  24 import java.net.MalformedURLException;
  25 
  26 import org.xml.sax.SAXException;
  27 import org.xml.sax.XMLReader;
  28 import org.xml.sax.InputSource;
  29 import org.xml.sax.EntityResolver;
  30 
  31 import javax.xml.transform.sax.SAXSource;
  32 import javax.xml.transform.Source;
  33 import javax.xml.transform.URIResolver;
  34 import javax.xml.transform.TransformerException;
  35 import javax.xml.parsers.ParserConfigurationException;
  36 import javax.xml.parsers.SAXParserFactory;
  37 
  38 import com.sun.org.apache.xml.internal.resolver.Catalog;
  39 import com.sun.org.apache.xml.internal.resolver.CatalogManager;
  40 import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
  41 
  42 /**
  43  * A SAX EntityResolver/JAXP URIResolver that uses catalogs.
  44  *
  45  * <p>This class implements both a SAX EntityResolver and a JAXP URIResolver.
  46  * </p>
  47  *
  48  * <p>This resolver understands OASIS TR9401 catalogs, XCatalogs, and the
  49  * current working draft of the OASIS Entity Resolution Technical
  50  * Committee specification.</p>
  51  *
  52  * @see Catalog
  53  * @see org.xml.sax.EntityResolver
  54  * @see javax.xml.transform.URIResolver
  55  *
  56  * @author Norman Walsh
  57  * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
  58  *
  59  * @version 1.0
  60  */
  61 public class CatalogResolver implements EntityResolver, URIResolver {
  62   /** Make the parser Namespace aware? */
  63   public boolean namespaceAware = true;
  64 
  65   /** Make the parser validating? */
  66   public boolean validating = false;
  67 
  68   /** The underlying catalog */
  69   private Catalog catalog = null;
  70 
  71   /** The catalog manager */
  72   private CatalogManager catalogManager = CatalogManager.getStaticManager();
  73 
  74   /** Constructor */
  75   public CatalogResolver() {
  76     initializeCatalogs(false);
  77   }
  78 
  79   /** Constructor */
  80   public CatalogResolver(boolean privateCatalog) {
  81     initializeCatalogs(privateCatalog);
  82   }
  83 
  84   /** Constructor */
  85   public CatalogResolver(CatalogManager manager) {
  86     catalogManager = manager;
  87     initializeCatalogs(!catalogManager.getUseStaticCatalog());
  88   }
  89 
  90   /** Initialize catalog */
  91   private void initializeCatalogs(boolean privateCatalog) {
  92     catalog = catalogManager.getCatalog();
  93   }
  94 
  95   /** Return the underlying catalog */
  96   public Catalog getCatalog() {
  97     return catalog;
  98   }
  99 
 100   /**
 101    * Implements the guts of the <code>resolveEntity</code> method
 102    * for the SAX interface.
 103    *
 104    * <p>Presented with an optional public identifier and a system
 105    * identifier, this function attempts to locate a mapping in the
 106    * catalogs.</p>
 107    *
 108    * <p>If such a mapping is found, it is returned.  If no mapping is
 109    * found, null is returned.</p>
 110    *
 111    * @param publicId  The public identifier for the entity in question.
 112    * This may be null.
 113    *
 114    * @param systemId  The system identifier for the entity in question.
 115    * XML requires a system identifier on all external entities, so this
 116    * value is always specified.
 117    *
 118    * @return The resolved identifier (a URI reference).
 119    */
 120   public String getResolvedEntity (String publicId, String systemId) {
 121     String resolved = null;
 122 
 123     if (catalog == null) {
 124       catalogManager.debug.message(1, "Catalog resolution attempted with null catalog; ignored");
 125       return null;
 126     }
 127 
 128     if (systemId != null) {
 129       try {
 130         resolved = catalog.resolveSystem(systemId);
 131       } catch (MalformedURLException me) {
 132         catalogManager.debug.message(1, "Malformed URL exception trying to resolve",
 133                       publicId);
 134         resolved = null;
 135       } catch (IOException ie) {
 136         catalogManager.debug.message(1, "I/O exception trying to resolve", publicId);
 137         resolved = null;
 138       }
 139     }
 140 
 141     if (resolved == null) {
 142       if (publicId != null) {
 143         try {
 144           resolved = catalog.resolvePublic(publicId, systemId);
 145         } catch (MalformedURLException me) {
 146           catalogManager.debug.message(1, "Malformed URL exception trying to resolve",
 147                         publicId);
 148         } catch (IOException ie) {
 149           catalogManager.debug.message(1, "I/O exception trying to resolve", publicId);
 150         }
 151       }
 152 
 153       if (resolved != null) {
 154         catalogManager.debug.message(2, "Resolved public", publicId, resolved);
 155       }
 156     } else {
 157       catalogManager.debug.message(2, "Resolved system", systemId, resolved);
 158     }
 159 
 160     return resolved;
 161   }
 162 
 163   /**
 164    * Implements the <code>resolveEntity</code> method
 165    * for the SAX interface.
 166    *
 167    * <p>Presented with an optional public identifier and a system
 168    * identifier, this function attempts to locate a mapping in the
 169    * catalogs.</p>
 170    *
 171    * <p>If such a mapping is found, the resolver attempts to open
 172    * the mapped value as an InputSource and return it. Exceptions are
 173    * ignored and null is returned if the mapped value cannot be opened
 174    * as an input source.</p>
 175    *
 176    * <p>If no mapping is found (or an error occurs attempting to open
 177    * the mapped value as an input source), null is returned and the system
 178    * will use the specified system identifier as if no entityResolver
 179    * was specified.</p>
 180    *
 181    * @param publicId  The public identifier for the entity in question.
 182    * This may be null.
 183    *
 184    * @param systemId  The system identifier for the entity in question.
 185    * XML requires a system identifier on all external entities, so this
 186    * value is always specified.
 187    *
 188    * @return An InputSource for the mapped identifier, or null.
 189    */
 190   public InputSource resolveEntity (String publicId, String systemId) {
 191     String resolved = getResolvedEntity(publicId, systemId);
 192 
 193     if (resolved != null) {
 194       try {
 195         InputSource iSource = new InputSource(resolved);
 196         iSource.setPublicId(publicId);
 197 
 198         // Ideally this method would not attempt to open the
 199         // InputStream, but there is a bug (in Xerces, at least)
 200         // that causes the parser to mistakenly open the wrong
 201         // system identifier if the returned InputSource does
 202         // not have a byteStream.
 203         //
 204         // It could be argued that we still shouldn't do this here,
 205         // but since the purpose of calling the entityResolver is
 206         // almost certainly to open the input stream, it seems to
 207         // do little harm.
 208         //
 209         URL url = new URL(resolved);
 210         InputStream iStream = url.openStream();
 211         iSource.setByteStream(iStream);
 212 
 213         return iSource;
 214       } catch (Exception e) {
 215         catalogManager.debug.message(1,
 216                                      "Failed to create InputSource ("
 217                                      + e.toString()
 218                                      + ")", resolved);
 219         return null;
 220       }
 221     }
 222 
 223     return null;
 224   }
 225 
 226   /** JAXP URIResolver API */
 227   public Source resolve(String href, String base)
 228     throws TransformerException {
 229 
 230     String uri = href;
 231     String fragment = null;
 232     int hashPos = href.indexOf("#");
 233     if (hashPos >= 0) {
 234       uri = href.substring(0, hashPos);
 235       fragment = href.substring(hashPos+1);
 236     }
 237 
 238     String result = null;
 239 
 240     try {
 241       result = catalog.resolveURI(href);
 242     } catch (Exception e) {
 243       // nop;
 244     }
 245 
 246     if (result == null) {
 247       try {
 248         URL url = null;
 249 
 250         if (base==null) {
 251           url = new URL(uri);
 252           result = url.toString();
 253         } else {
 254           URL baseURL = new URL(base);
 255           url = (href.length()==0 ? baseURL : new URL(baseURL, uri));
 256           result = url.toString();
 257         }
 258       } catch (java.net.MalformedURLException mue) {
 259         // try to make an absolute URI from the current base
 260         String absBase = makeAbsolute(base);
 261         if (!absBase.equals(base)) {
 262           // don't bother if the absBase isn't different!
 263           return resolve(href, absBase);
 264         } else {
 265           throw new TransformerException("Malformed URL "
 266                                          + href + "(base " + base + ")",
 267                                          mue);
 268         }
 269       }
 270     }
 271 
 272     catalogManager.debug.message(2, "Resolved URI", href, result);
 273 
 274     SAXSource source = new SAXSource();
 275     source.setInputSource(new InputSource(result));
 276     setEntityResolver(source);
 277     return source;
 278   }
 279 
 280   /**
 281    * <p>Establish an entityResolver for newly resolved URIs.</p>
 282    *
 283    * <p>This is called from the URIResolver to set an EntityResolver
 284    * on the SAX parser to be used for new XML documents that are
 285    * encountered as a result of the document() function, xsl:import,
 286    * or xsl:include.  This is done because the XSLT processor calls
 287    * out to the SAXParserFactory itself to create a new SAXParser to
 288    * parse the new document.  The new parser does not automatically
 289    * inherit the EntityResolver of the original (although arguably
 290    * it should).  See below:</p>
 291    *
 292    * <tt>"If an application wants to set the ErrorHandler or
 293    * EntityResolver for an XMLReader used during a transformation,
 294    * it should use a URIResolver to return the SAXSource which
 295    * provides (with getXMLReader) a reference to the XMLReader"</tt>
 296    *
 297    * <p>...quoted from page 118 of the Java API for XML
 298    * Processing 1.1 specification</p>
 299    *
 300    */
 301   private void setEntityResolver(SAXSource source) throws TransformerException {
 302     XMLReader reader = source.getXMLReader();
 303     if (reader == null) {
 304       SAXParserFactory spFactory = catalogManager.useServicesMechanism() ?
 305                     SAXParserFactory.newInstance() : new SAXParserFactoryImpl();
 306       spFactory.setNamespaceAware(true);
 307       try {
 308         reader = spFactory.newSAXParser().getXMLReader();
 309       }
 310       catch (ParserConfigurationException ex) {
 311         throw new TransformerException(ex);
 312       }
 313       catch (SAXException ex) {
 314         throw new TransformerException(ex);
 315       }
 316     }
 317     reader.setEntityResolver(this);
 318     source.setXMLReader(reader);
 319   }
 320 
 321   /** Attempt to construct an absolute URI */
 322   private String makeAbsolute(String uri) {
 323     if (uri == null) {
 324       uri = "";
 325     }
 326 
 327     try {
 328       URL url = new URL(uri);
 329       return url.toString();
 330     } catch (MalformedURLException mue) {
 331       try {
 332         URL fileURL = FileURL.makeURL(uri);
 333         return fileURL.toString();
 334       } catch (MalformedURLException mue2) {
 335         // bail
 336         return uri;
 337       }
 338     }
 339   }
 340 }