1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 // ResolvingXMLFilter.java - An XMLFilter that performs catalog resolution
   6 
   7 /*
   8  * Copyright 2001-2004 The Apache Software Foundation or its licensors,
   9  * as applicable.
  10  *
  11  * Licensed under the Apache License, Version 2.0 (the "License");
  12  * you may not use this file except in compliance with the License.
  13  * You may obtain a copy of the License at
  14  *
  15  *      http://www.apache.org/licenses/LICENSE-2.0
  16  *
  17  * Unless required by applicable law or agreed to in writing, software
  18  * distributed under the License is distributed on an "AS IS" BASIS,
  19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20  * See the License for the specific language governing permissions and
  21  * limitations under the License.
  22  */
  23 
  24 package com.sun.org.apache.xml.internal.resolver.tools;
  25 
  26 import java.io.IOException;
  27 import java.io.InputStream;
  28 import java.net.MalformedURLException;
  29 import java.net.URL;
  30 
  31 import org.xml.sax.InputSource;
  32 import org.xml.sax.SAXException;
  33 import org.xml.sax.XMLReader;
  34 import org.xml.sax.Attributes;
  35 import org.xml.sax.helpers.XMLFilterImpl;
  36 
  37 import com.sun.org.apache.xml.internal.resolver.Catalog;
  38 import com.sun.org.apache.xml.internal.resolver.CatalogManager;
  39 
  40 import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
  41 
  42 /**
  43  * A SAX XMLFilter that performs catalog-based entity resolution.
  44  *
  45  * <p>This class implements a SAX XMLFilter that performs entity resolution
  46  * using the CatalogResolver. The actual, underlying parser is obtained
  47  * from a SAXParserFactory.</p>
  48  * </p>
  49  *
  50  * @see CatalogResolver
  51  * @see org.xml.sax.XMLFilter
  52  *
  53  * @author Norman Walsh
  54  * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
  55  *
  56  * @version 1.0
  57  */
  58 public class ResolvingXMLFilter extends XMLFilterImpl {
  59   /**
  60    * Suppress explanatory message?
  61    *
  62    * @see #parse(InputSource)
  63    */
  64   public static boolean suppressExplanation = false;
  65 
  66   /** The manager for the underlying resolver. */
  67   CatalogManager catalogManager = CatalogManager.getStaticManager();
  68 
  69   /** The underlying catalog resolver. */
  70   private CatalogResolver catalogResolver = null;
  71 
  72   /** A separate resolver for oasis-xml-pi catalogs. */
  73   private CatalogResolver piCatalogResolver = null;
  74 
  75   /** Are we in the prolog? Is an oasis-xml-catalog PI valid now? */
  76   private boolean allowXMLCatalogPI = false;
  77 
  78   /** Has an oasis-xml-catalog PI been seen? */
  79   private boolean oasisXMLCatalogPI = false;
  80 
  81   /** The base URI of the input document, if known. */
  82   private URL baseURL = null;
  83 
  84   /** Construct an empty XML Filter with no parent. */
  85   public ResolvingXMLFilter() {
  86     super();
  87     catalogResolver = new CatalogResolver(catalogManager);
  88   }
  89 
  90   /** Construct an XML filter with the specified parent. */
  91   public ResolvingXMLFilter(XMLReader parent) {
  92     super(parent);
  93     catalogResolver = new CatalogResolver(catalogManager);
  94   }
  95 
  96   /** Construct an XML filter with the specified parent. */
  97   public ResolvingXMLFilter(CatalogManager manager) {
  98     super();
  99     catalogManager = manager;
 100     catalogResolver = new CatalogResolver(catalogManager);
 101   }
 102 
 103   /** Construct an XML filter with the specified parent. */
 104   public ResolvingXMLFilter(XMLReader parent, CatalogManager manager) {
 105     super(parent);
 106     catalogManager = manager;
 107     catalogResolver = new CatalogResolver(catalogManager);
 108   }
 109 
 110   /**
 111    * Provide accessto the underlying Catalog.
 112    */
 113   public Catalog getCatalog() {
 114     return catalogResolver.getCatalog();
 115   }
 116 
 117   /**
 118    * SAX XMLReader API.
 119    *
 120    * <p>Note that the JAXP 1.1ea2 parser crashes with an InternalError if
 121    * it encounters a system identifier that appears to be a relative URI
 122    * that begins with a slash. For example, the declaration:</p>
 123    *
 124    * <pre>
 125    * &lt;!DOCTYPE book SYSTEM "/path/to/dtd/on/my/system/docbookx.dtd">
 126    * </pre>
 127    *
 128    * <p>would cause such an error. As a convenience, this method catches
 129    * that error and prints an explanation. (Unfortunately, it's not possible
 130    * to identify the particular system identifier that causes the problem.)
 131    * </p>
 132    *
 133    * <p>The underlying error is forwarded after printing the explanatory
 134    * message. The message is only every printed once and if
 135    * <code>suppressExplanation</code> is set to <code>false</code> before
 136    * parsing, it will never be printed.</p>
 137    */
 138   public void parse(InputSource input)
 139     throws IOException, SAXException {
 140     allowXMLCatalogPI = true;
 141 
 142     setupBaseURI(input.getSystemId());
 143 
 144     try {
 145       super.parse(input);
 146     } catch (InternalError ie) {
 147       explain(input.getSystemId());
 148       throw ie;
 149     }
 150   }
 151 
 152   /** SAX XMLReader API.
 153    *
 154    * @see #parse(InputSource)
 155    */
 156   public void parse(String systemId)
 157     throws IOException, SAXException {
 158     allowXMLCatalogPI = true;
 159 
 160     setupBaseURI(systemId);
 161 
 162     try {
 163       super.parse(systemId);
 164     } catch (InternalError ie) {
 165       explain(systemId);
 166       throw ie;
 167     }
 168   }
 169 
 170   /**
 171    * Implements the <code>resolveEntity</code> method
 172    * for the SAX interface, using an underlying CatalogResolver
 173    * to do the real work.
 174    */
 175   public InputSource resolveEntity (String publicId, String systemId) {
 176     allowXMLCatalogPI = false;
 177     String resolved = catalogResolver.getResolvedEntity(publicId, systemId);
 178 
 179     if (resolved == null && piCatalogResolver != null) {
 180       resolved = piCatalogResolver.getResolvedEntity(publicId, systemId);
 181     }
 182 
 183     if (resolved != null) {
 184       try {
 185         InputSource iSource = new InputSource(resolved);
 186         iSource.setPublicId(publicId);
 187 
 188         // Ideally this method would not attempt to open the
 189         // InputStream, but there is a bug (in Xerces, at least)
 190         // that causes the parser to mistakenly open the wrong
 191         // system identifier if the returned InputSource does
 192         // not have a byteStream.
 193         //
 194         // It could be argued that we still shouldn't do this here,
 195         // but since the purpose of calling the entityResolver is
 196         // almost certainly to open the input stream, it seems to
 197         // do little harm.
 198         //
 199         URL url = new URL(resolved);
 200         InputStream iStream = url.openStream();
 201         iSource.setByteStream(iStream);
 202 
 203         return iSource;
 204       } catch (Exception e) {
 205         catalogManager.debug.message(1, "Failed to create InputSource", resolved);
 206         return null;
 207       }
 208     } else {
 209       return null;
 210     }
 211   }
 212 
 213   /** SAX DTDHandler API.
 214    *
 215    * <p>Captured here only to detect the end of the prolog so that
 216    * we can ignore subsequent oasis-xml-catalog PIs. Otherwise
 217    * the events are just passed through.</p>
 218    */
 219   public void notationDecl (String name, String publicId, String systemId)
 220     throws SAXException {
 221     allowXMLCatalogPI = false;
 222     super.notationDecl(name,publicId,systemId);
 223   }
 224 
 225   /** SAX DTDHandler API.
 226    *
 227    * <p>Captured here only to detect the end of the prolog so that
 228    * we can ignore subsequent oasis-xml-catalog PIs. Otherwise
 229    * the events are just passed through.</p>
 230    */
 231   public void unparsedEntityDecl (String name,
 232                                   String publicId,
 233                                   String systemId,
 234                                   String notationName)
 235     throws SAXException {
 236     allowXMLCatalogPI = false;
 237     super.unparsedEntityDecl (name, publicId, systemId, notationName);
 238   }
 239 
 240   /** SAX ContentHandler API.
 241    *
 242    * <p>Captured here only to detect the end of the prolog so that
 243    * we can ignore subsequent oasis-xml-catalog PIs. Otherwise
 244    * the events are just passed through.</p>
 245    */
 246   public void startElement (String uri, String localName, String qName,
 247                             Attributes atts)
 248     throws SAXException {
 249     allowXMLCatalogPI = false;
 250     super.startElement(uri,localName,qName,atts);
 251   }
 252 
 253   /** SAX ContentHandler API.
 254    *
 255    * <p>Detect and use the oasis-xml-catalog PI if it occurs.</p>
 256    */
 257   public void processingInstruction(String target, String pidata)
 258     throws SAXException {
 259     if (target.equals("oasis-xml-catalog")) {
 260       URL catalog = null;
 261       String data = pidata;
 262 
 263       int pos = data.indexOf("catalog=");
 264       if (pos >= 0) {
 265         data = data.substring(pos+8);
 266         if (data.length() > 1) {
 267           String quote = data.substring(0,1);
 268           data = data.substring(1);
 269           pos = data.indexOf(quote);
 270           if (pos >= 0) {
 271             data = data.substring(0, pos);
 272             try {
 273               if (baseURL != null) {
 274                 catalog = new URL(baseURL, data);
 275               } else {
 276                 catalog = new URL(data);
 277               }
 278             } catch (MalformedURLException mue) {
 279               // nevermind
 280             }
 281           }
 282         }
 283       }
 284 
 285       if (allowXMLCatalogPI) {
 286         if (catalogManager.getAllowOasisXMLCatalogPI()) {
 287           catalogManager.debug.message(4,"oasis-xml-catalog PI", pidata);
 288 
 289           if (catalog != null) {
 290             try {
 291               catalogManager.debug.message(4,"oasis-xml-catalog", catalog.toString());
 292               oasisXMLCatalogPI = true;
 293 
 294               if (piCatalogResolver == null) {
 295                 piCatalogResolver = new CatalogResolver(true);
 296               }
 297 
 298               piCatalogResolver.getCatalog().parseCatalog(catalog.toString());
 299             } catch (Exception e) {
 300               catalogManager.debug.message(3, "Exception parsing oasis-xml-catalog: "
 301                             + catalog.toString());
 302             }
 303           } else {
 304             catalogManager.debug.message(3, "PI oasis-xml-catalog unparseable: " + pidata);
 305           }
 306         } else {
 307           catalogManager.debug.message(4,"PI oasis-xml-catalog ignored: " + pidata);
 308         }
 309       } else {
 310         catalogManager.debug.message(3, "PI oasis-xml-catalog occurred in an invalid place: "
 311                       + pidata);
 312       }
 313     } else {
 314       super.processingInstruction(target, pidata);
 315     }
 316   }
 317 
 318   /** Save the base URI of the document being parsed. */
 319   private void setupBaseURI(String systemId) {
 320     URL cwd = null;
 321 
 322     try {
 323       cwd = FileURL.makeURL("basename");
 324     } catch (MalformedURLException mue) {
 325       cwd = null;
 326     }
 327 
 328     try {
 329       baseURL = new URL(systemId);
 330     } catch (MalformedURLException mue) {
 331       if (cwd != null) {
 332         try {
 333           baseURL = new URL(cwd, systemId);
 334         } catch (MalformedURLException mue2) {
 335           // give up
 336           baseURL = null;
 337         }
 338       } else {
 339         // give up
 340         baseURL = null;
 341       }
 342     }
 343   }
 344 
 345   /** Provide one possible explanation for an InternalError. */
 346   private void explain(String systemId) {
 347     if (!suppressExplanation) {
 348       System.out.println("XMLReader probably encountered bad URI in " + systemId);
 349       System.out.println("For example, replace '/some/uri' with 'file:/some/uri'.");
 350     }
 351     suppressExplanation = true;
 352   }
 353 }