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