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