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