001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.io.IOException; 005import java.io.InputStream; 006import java.util.stream.IntStream; 007 008import javax.xml.XMLConstants; 009import javax.xml.parsers.DocumentBuilder; 010import javax.xml.parsers.DocumentBuilderFactory; 011import javax.xml.parsers.ParserConfigurationException; 012import javax.xml.parsers.SAXParser; 013import javax.xml.parsers.SAXParserFactory; 014import javax.xml.stream.XMLInputFactory; 015import javax.xml.transform.TransformerConfigurationException; 016import javax.xml.transform.TransformerFactory; 017import javax.xml.validation.Schema; 018import javax.xml.validation.SchemaFactory; 019import javax.xml.validation.SchemaFactoryConfigurationError; 020import javax.xml.validation.Validator; 021 022import org.w3c.dom.Document; 023import org.w3c.dom.Element; 024import org.w3c.dom.Node; 025import org.w3c.dom.NodeList; 026import org.xml.sax.InputSource; 027import org.xml.sax.SAXException; 028import org.xml.sax.SAXNotRecognizedException; 029import org.xml.sax.SAXNotSupportedException; 030import org.xml.sax.helpers.DefaultHandler; 031 032/** 033 * XML utils, mainly used to construct safe factories. 034 * @since 13901 035 */ 036public final class XmlUtils { 037 038 private static final String FEATURE_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; 039 040 private XmlUtils() { 041 // Hide default constructor for utils classes 042 } 043 044 /** 045 * Returns the W3C XML Schema factory implementation. Robust method dealing with ContextClassLoader problems. 046 * @return the W3C XML Schema factory implementation 047 */ 048 public static SchemaFactory newXmlSchemaFactory() { 049 try { 050 return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 051 } catch (SchemaFactoryConfigurationError e) { 052 Logging.debug(e); 053 // Can happen with icedtea-web. Use workaround from https://issues.apache.org/jira/browse/GERONIMO-6185 054 Thread currentThread = Thread.currentThread(); 055 ClassLoader old = currentThread.getContextClassLoader(); 056 currentThread.setContextClassLoader(null); 057 try { 058 return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 059 } finally { 060 currentThread.setContextClassLoader(old); 061 } 062 } 063 } 064 065 /** 066 * Returns a new secure DOM builder, supporting XML namespaces. 067 * @return a new secure DOM builder, supporting XML namespaces 068 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration. 069 */ 070 public static DocumentBuilder newSafeDOMBuilder() throws ParserConfigurationException { 071 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 072 builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 073 builderFactory.setNamespaceAware(true); 074 builderFactory.setValidating(false); 075 return builderFactory.newDocumentBuilder(); 076 } 077 078 /** 079 * Parse the content given {@link InputStream} as XML. 080 * This method uses a secure DOM builder, supporting XML namespaces. 081 * 082 * @param is The InputStream containing the content to be parsed. 083 * @return the result DOM document 084 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration. 085 * @throws IOException if any IO errors occur. 086 * @throws SAXException for SAX errors. 087 */ 088 public static Document parseSafeDOM(InputStream is) throws ParserConfigurationException, IOException, SAXException { 089 Stopwatch stopwatch = Stopwatch.createStarted(); 090 Logging.debug("Starting DOM parsing of {0}", is); 091 Document result = newSafeDOMBuilder().parse(is); 092 Logging.debug(stopwatch.toString("DOM parsing")); 093 return result; 094 } 095 096 /** 097 * Returns a new secure SAX parser, supporting XML namespaces. 098 * @return a new secure SAX parser, supporting XML namespaces 099 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration. 100 * @throws SAXException for SAX errors. 101 */ 102 public static SAXParser newSafeSAXParser() throws ParserConfigurationException, SAXException { 103 SAXParserFactory parserFactory = SAXParserFactory.newInstance(); 104 parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 105 parserFactory.setFeature(FEATURE_DISALLOW_DOCTYPE_DECL, true); 106 parserFactory.setNamespaceAware(true); 107 return parserFactory.newSAXParser(); 108 } 109 110 /** 111 * Parse the content given {@link org.xml.sax.InputSource} as XML using the specified {@link org.xml.sax.helpers.DefaultHandler}. 112 * This method uses a secure SAX parser, supporting XML namespaces. 113 * 114 * @param is The InputSource containing the content to be parsed. 115 * @param dh The SAX DefaultHandler to use. 116 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration. 117 * @throws SAXException for SAX errors. 118 * @throws IOException if any IO errors occur. 119 */ 120 public static void parseSafeSAX(InputSource is, DefaultHandler dh) throws ParserConfigurationException, SAXException, IOException { 121 Stopwatch stopwatch = Stopwatch.createStarted(); 122 Logging.debug("Starting SAX parsing of {0} using {1}", is, dh); 123 newSafeSAXParser().parse(is, dh); 124 Logging.debug(stopwatch.toString("SAX parsing")); 125 } 126 127 /** 128 * Returns a new secure {@link XMLInputFactory}. 129 * @return a new secure {@code XMLInputFactory}, for which external entities are not loaded 130 */ 131 public static XMLInputFactory newSafeXMLInputFactory() { 132 XMLInputFactory factory = XMLInputFactory.newInstance(); 133 // do not try to load external entities, nor validate the XML 134 factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); 135 factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); 136 factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); 137 return factory; 138 } 139 140 /** 141 * Returns a new secure {@link TransformerFactory}. 142 * @return a new secure {@link TransformerFactory} 143 * @throws TransformerConfigurationException if the factory or the Transformers or Templates it creates cannot support this feature. 144 */ 145 public static TransformerFactory newSafeTransformerFactory() throws TransformerConfigurationException { 146 TransformerFactory factory = TransformerFactory.newInstance(); 147 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 148 return factory; 149 } 150 151 /** 152 * Returns a new secure {@link Validator}. 153 * @param schema XML schema 154 * @return a new secure {@link Validator} 155 * @since 14441 156 */ 157 public static Validator newSafeValidator(Schema schema) { 158 Validator validator = schema.newValidator(); 159 try { 160 validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 161 validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 162 } catch (SAXNotRecognizedException | SAXNotSupportedException e) { 163 // All implementations that implement JAXP 1.5 or newer are required to support these two properties 164 Logging.trace(e); 165 } 166 return validator; 167 } 168 169 /** 170 * Get the first child element 171 * @param parent parent node 172 * @return the first child element 173 * @since 14348 174 */ 175 public static Element getFirstChildElement(Node parent) { 176 NodeList children = parent.getChildNodes(); 177 return (Element) IntStream.range(0, children.getLength()) 178 .mapToObj(children::item) 179 .filter(child -> child instanceof Element) 180 .findFirst().orElse(null); 181 } 182}