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