1 /*
   2  * Copyright (c) 1997, 2013, 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 
  26 package com.sun.xml.internal.ws.util.xml;
  27 
  28 import com.sun.istack.internal.Nullable;
  29 import com.sun.org.apache.xml.internal.resolver.Catalog;
  30 import com.sun.org.apache.xml.internal.resolver.CatalogManager;
  31 import com.sun.org.apache.xml.internal.resolver.tools.CatalogResolver;
  32 import com.sun.xml.internal.ws.server.ServerRtException;
  33 import com.sun.xml.internal.ws.util.ByteArrayBuffer;
  34 import org.w3c.dom.Attr;
  35 import org.w3c.dom.Element;
  36 import org.w3c.dom.EntityReference;
  37 import org.w3c.dom.Node;
  38 import org.w3c.dom.NodeList;
  39 import org.w3c.dom.Text;
  40 import org.xml.sax.*;
  41 
  42 import javax.xml.XMLConstants;
  43 import javax.xml.namespace.QName;
  44 import javax.xml.parsers.DocumentBuilderFactory;
  45 import javax.xml.parsers.ParserConfigurationException;
  46 import javax.xml.parsers.SAXParserFactory;
  47 import javax.xml.stream.XMLInputFactory;
  48 import javax.xml.transform.Result;
  49 import javax.xml.transform.Source;
  50 import javax.xml.transform.Transformer;
  51 import javax.xml.transform.TransformerConfigurationException;
  52 import javax.xml.transform.TransformerException;
  53 import javax.xml.transform.TransformerFactory;
  54 import javax.xml.transform.sax.SAXTransformerFactory;
  55 import javax.xml.transform.sax.TransformerHandler;
  56 import javax.xml.transform.stream.StreamSource;
  57 import javax.xml.validation.SchemaFactory;
  58 import javax.xml.ws.WebServiceException;
  59 import javax.xml.xpath.XPathFactory;
  60 import javax.xml.xpath.XPathFactoryConfigurationException;
  61 import java.io.IOException;
  62 import java.io.InputStream;
  63 import java.io.OutputStreamWriter;
  64 import java.io.Writer;
  65 import java.net.URL;
  66 import java.util.ArrayList;
  67 import java.util.Enumeration;
  68 import java.util.Iterator;
  69 import java.util.List;
  70 import java.util.StringTokenizer;
  71 import java.util.logging.Level;
  72 import java.util.logging.Logger;
  73 
  74 /**
  75  * @author WS Development Team
  76  */
  77 public class XmlUtil {
  78 
  79     // not in older JDK, so must be duplicated here, otherwise javax.xml.XMLConstants should be used
  80     private static final String ACCESS_EXTERNAL_SCHEMA = "http://javax.xml.XMLConstants/property/accessExternalSchema";
  81 
  82     private final static String LEXICAL_HANDLER_PROPERTY =
  83         "http://xml.org/sax/properties/lexical-handler";
  84 
  85     private static final Logger LOGGER = Logger.getLogger(XmlUtil.class.getName());
  86 
  87     private static boolean globalSecureXmlProcessingEnabled;
  88 
  89     static {
  90         String disableSecureXmlProcessing = System.getProperty("disableSecureXmlProcessing");
  91         globalSecureXmlProcessingEnabled = disableSecureXmlProcessing == null || !Boolean.valueOf(disableSecureXmlProcessing);
  92     }
  93 
  94     public static String getPrefix(String s) {
  95         int i = s.indexOf(':');
  96         if (i == -1)
  97             return null;
  98         return s.substring(0, i);
  99     }
 100 
 101     public static String getLocalPart(String s) {
 102         int i = s.indexOf(':');
 103         if (i == -1)
 104             return s;
 105         return s.substring(i + 1);
 106     }
 107 
 108 
 109 
 110     public static String getAttributeOrNull(Element e, String name) {
 111         Attr a = e.getAttributeNode(name);
 112         if (a == null)
 113             return null;
 114         return a.getValue();
 115     }
 116 
 117     public static String getAttributeNSOrNull(
 118         Element e,
 119         String name,
 120         String nsURI) {
 121         Attr a = e.getAttributeNodeNS(nsURI, name);
 122         if (a == null)
 123             return null;
 124         return a.getValue();
 125     }
 126 
 127     public static String getAttributeNSOrNull(
 128         Element e,
 129         QName name) {
 130         Attr a = e.getAttributeNodeNS(name.getNamespaceURI(), name.getLocalPart());
 131         if (a == null)
 132             return null;
 133         return a.getValue();
 134     }
 135 
 136 /*    public static boolean matchesTagNS(Element e, String tag, String nsURI) {
 137         try {
 138             return e.getLocalName().equals(tag)
 139                 && e.getNamespaceURI().equals(nsURI);
 140         } catch (NullPointerException npe) {
 141 
 142             // localname not null since parsing would fail before here
 143             throw new WSDLParseException(
 144                 "null.namespace.found",
 145                 e.getLocalName());
 146         }
 147     }
 148 
 149     public static boolean matchesTagNS(
 150         Element e,
 151         javax.xml.namespace.QName name) {
 152         try {
 153             return e.getLocalName().equals(name.getLocalPart())
 154                 && e.getNamespaceURI().equals(name.getNamespaceURI());
 155         } catch (NullPointerException npe) {
 156 
 157             // localname not null since parsing would fail before here
 158             throw new WSDLParseException(
 159                 "null.namespace.found",
 160                 e.getLocalName());
 161         }
 162     }*/
 163 
 164     public static Iterator getAllChildren(Element element) {
 165         return new NodeListIterator(element.getChildNodes());
 166     }
 167 
 168     public static Iterator getAllAttributes(Element element) {
 169         return new NamedNodeMapIterator(element.getAttributes());
 170     }
 171 
 172     public static List<String> parseTokenList(String tokenList) {
 173         List<String> result = new ArrayList<String>();
 174         StringTokenizer tokenizer = new StringTokenizer(tokenList, " ");
 175         while (tokenizer.hasMoreTokens()) {
 176             result.add(tokenizer.nextToken());
 177         }
 178         return result;
 179     }
 180 
 181     public static String getTextForNode(Node node) {
 182         StringBuilder sb = new StringBuilder();
 183 
 184         NodeList children = node.getChildNodes();
 185         if (children.getLength() == 0)
 186             return null;
 187 
 188         for (int i = 0; i < children.getLength(); ++i) {
 189             Node n = children.item(i);
 190 
 191             if (n instanceof Text)
 192                 sb.append(n.getNodeValue());
 193             else if (n instanceof EntityReference) {
 194                 String s = getTextForNode(n);
 195                 if (s == null)
 196                     return null;
 197                 else
 198                     sb.append(s);
 199             } else
 200                 return null;
 201         }
 202 
 203         return sb.toString();
 204     }
 205 
 206     public static InputStream getUTF8Stream(String s) {
 207         try {
 208             ByteArrayBuffer bab = new ByteArrayBuffer();
 209             Writer w = new OutputStreamWriter(bab, "utf-8");
 210             w.write(s);
 211             w.close();
 212             return bab.newInputStream();
 213         } catch (IOException e) {
 214             throw new RuntimeException("should not happen");
 215         }
 216     }
 217 
 218     static final TransformerFactory transformerFactory = newTransformerFactory();
 219 
 220     static final SAXParserFactory saxParserFactory = newSAXParserFactory(true);
 221 
 222     static {
 223         saxParserFactory.setNamespaceAware(true);
 224     }
 225 
 226     /**
 227      * Creates a new identity transformer.
 228      */
 229     public static Transformer newTransformer() {
 230         try {
 231             return transformerFactory.newTransformer();
 232         } catch (TransformerConfigurationException tex) {
 233             throw new IllegalStateException("Unable to create a JAXP transformer");
 234         }
 235     }
 236 
 237     /**
 238      * Performs identity transformation.
 239      */
 240     public static <T extends Result>
 241     T identityTransform(Source src, T result) throws TransformerException, SAXException, ParserConfigurationException, IOException {
 242         if (src instanceof StreamSource) {
 243             // work around a bug in JAXP in JDK6u4 and earlier where the namespace processing
 244             // is not turned on by default
 245             StreamSource ssrc = (StreamSource) src;
 246             TransformerHandler th = ((SAXTransformerFactory) transformerFactory).newTransformerHandler();
 247             th.setResult(result);
 248             XMLReader reader = saxParserFactory.newSAXParser().getXMLReader();
 249             reader.setContentHandler(th);
 250             reader.setProperty(LEXICAL_HANDLER_PROPERTY, th);
 251             reader.parse(toInputSource(ssrc));
 252         } else {
 253             newTransformer().transform(src, result);
 254         }
 255         return result;
 256     }
 257 
 258     private static InputSource toInputSource(StreamSource src) {
 259         InputSource is = new InputSource();
 260         is.setByteStream(src.getInputStream());
 261         is.setCharacterStream(src.getReader());
 262         is.setPublicId(src.getPublicId());
 263         is.setSystemId(src.getSystemId());
 264         return is;
 265     }
 266 
 267     /*
 268     * Gets an EntityResolver using XML catalog
 269     */
 270      public static EntityResolver createEntityResolver(@Nullable URL catalogUrl) {
 271         // set up a manager
 272         CatalogManager manager = new CatalogManager();
 273         manager.setIgnoreMissingProperties(true);
 274         // Using static catalog may  result in to sharing of the catalog by multiple apps running in a container
 275         manager.setUseStaticCatalog(false);
 276         Catalog catalog = manager.getCatalog();
 277         try {
 278             if (catalogUrl != null) {
 279                 catalog.parseCatalog(catalogUrl);
 280             }
 281         } catch (IOException e) {
 282             throw new ServerRtException("server.rt.err",e);
 283         }
 284         return workaroundCatalogResolver(catalog);
 285     }
 286 
 287     /**
 288      * Gets a default EntityResolver for catalog at META-INF/jaxws-catalog.xml
 289      */
 290     public static EntityResolver createDefaultCatalogResolver() {
 291 
 292         // set up a manager
 293         CatalogManager manager = new CatalogManager();
 294         manager.setIgnoreMissingProperties(true);
 295         // Using static catalog may  result in to sharing of the catalog by multiple apps running in a container
 296         manager.setUseStaticCatalog(false);
 297         // parse the catalog
 298         ClassLoader cl = Thread.currentThread().getContextClassLoader();
 299         Enumeration<URL> catalogEnum;
 300         Catalog catalog = manager.getCatalog();
 301         try {
 302             if (cl == null) {
 303                 catalogEnum = ClassLoader.getSystemResources("META-INF/jax-ws-catalog.xml");
 304             } else {
 305                 catalogEnum = cl.getResources("META-INF/jax-ws-catalog.xml");
 306             }
 307 
 308             while(catalogEnum.hasMoreElements()) {
 309                 URL url = catalogEnum.nextElement();
 310                 catalog.parseCatalog(url);
 311             }
 312         } catch (IOException e) {
 313             throw new WebServiceException(e);
 314         }
 315 
 316         return workaroundCatalogResolver(catalog);
 317     }
 318 
 319     /**
 320      *  Default CatalogResolver implementation is broken as it depends on CatalogManager.getCatalog() which will always create a new one when
 321      *  useStaticCatalog is false.
 322      *  This returns a CatalogResolver that uses the catalog passed as parameter.
 323      * @param catalog
 324      * @return  CatalogResolver
 325      */
 326     private static CatalogResolver workaroundCatalogResolver(final Catalog catalog) {
 327         // set up a manager
 328         CatalogManager manager = new CatalogManager() {
 329             @Override
 330             public Catalog getCatalog() {
 331                 return catalog;
 332             }
 333         };
 334         manager.setIgnoreMissingProperties(true);
 335         // Using static catalog may  result in to sharing of the catalog by multiple apps running in a container
 336         manager.setUseStaticCatalog(false);
 337 
 338         return new CatalogResolver(manager);
 339     }
 340 
 341     /**
 342      * {@link ErrorHandler} that always treat the error as fatal.
 343      */
 344     public static final ErrorHandler DRACONIAN_ERROR_HANDLER = new ErrorHandler() {
 345         @Override
 346         public void warning(SAXParseException exception) {
 347         }
 348 
 349         @Override
 350         public void error(SAXParseException exception) throws SAXException {
 351             throw exception;
 352         }
 353 
 354         @Override
 355         public void fatalError(SAXParseException exception) throws SAXException {
 356             throw exception;
 357         }
 358     };
 359 
 360     public static DocumentBuilderFactory newDocumentBuilderFactory() {
 361         return newDocumentBuilderFactory(true);
 362     }
 363 
 364     public static DocumentBuilderFactory newDocumentBuilderFactory(boolean secureXmlProcessing) {
 365         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 366         try {
 367             factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, checkGlobalOverride(secureXmlProcessing));
 368         } catch (ParserConfigurationException e) {
 369             LOGGER.log(Level.WARNING, "Factory [{}] doesn't support secure xml processing!", new Object[] { factory.getClass().getName() } );
 370         }
 371         return factory;
 372     }
 373 
 374     public static TransformerFactory newTransformerFactory(boolean secureXmlProcessingEnabled) {
 375         TransformerFactory factory = TransformerFactory.newInstance();
 376         try {
 377             factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, checkGlobalOverride(secureXmlProcessingEnabled));
 378         } catch (TransformerConfigurationException e) {
 379             LOGGER.log(Level.WARNING, "Factory [{}] doesn't support secure xml processing!", new Object[]{factory.getClass().getName()});
 380         }
 381         return factory;
 382     }
 383 
 384     public static TransformerFactory newTransformerFactory() {
 385         return newTransformerFactory(true);
 386     }
 387 
 388     public static SAXParserFactory newSAXParserFactory(boolean secureXmlProcessingEnabled) {
 389         SAXParserFactory factory = SAXParserFactory.newInstance();
 390         try {
 391             factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, checkGlobalOverride(secureXmlProcessingEnabled));
 392         } catch (Exception e) {
 393             LOGGER.log(Level.WARNING, "Factory [{}] doesn't support secure xml processing!", new Object[]{factory.getClass().getName()});
 394         }
 395         return factory;
 396     }
 397 
 398     public static XPathFactory newXPathFactory(boolean secureXmlProcessingEnabled) {
 399         XPathFactory factory = XPathFactory.newInstance();
 400         try {
 401             factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, checkGlobalOverride(secureXmlProcessingEnabled));
 402         } catch (XPathFactoryConfigurationException e) {
 403             LOGGER.log(Level.WARNING, "Factory [{}] doesn't support secure xml processing!", new Object[] { factory.getClass().getName() } );
 404         }
 405         return factory;
 406     }
 407 
 408     public static XMLInputFactory newXMLInputFactory(boolean secureXmlProcessingEnabled)  {
 409         XMLInputFactory factory = XMLInputFactory.newInstance();
 410         if (checkGlobalOverride(secureXmlProcessingEnabled)) {
 411             // TODO-Miran: are those apppropriate defaults?
 412             factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
 413             factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
 414         }
 415         return factory;
 416     }
 417 
 418     private static boolean checkGlobalOverride(boolean localSecureXmlProcessingEnabled) {
 419         return globalSecureXmlProcessingEnabled && localSecureXmlProcessingEnabled;
 420     }
 421 
 422     public static SchemaFactory allowFileAccess(SchemaFactory sf, boolean disableSecureProcessing) {
 423 
 424         // if feature secure processing enabled, nothing to do, file is allowed,
 425         // or user is able to control access by standard JAXP mechanisms
 426         if (checkGlobalOverride(disableSecureProcessing)) {
 427             return sf;
 428         }
 429 
 430         try {
 431             sf.setProperty(ACCESS_EXTERNAL_SCHEMA, "file");
 432             LOGGER.log(Level.FINE, "Property \"{}\" is supported and has been successfully set by used JAXP implementation.", new Object[]{ACCESS_EXTERNAL_SCHEMA});
 433         } catch (SAXException ignored) {
 434             // depending on JDK/SAX implementation used
 435             LOGGER.log(Level.CONFIG, "Property \"{}\" is not supported by used JAXP implementation.", new Object[]{ACCESS_EXTERNAL_SCHEMA});
 436         }
 437         return sf;
 438     }
 439 }