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