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        long start = System.currentTimeMillis();
087        Logging.debug("Starting DOM parsing of {0}", is);
088        Document result = newSafeDOMBuilder().parse(is);
089        if (Logging.isDebugEnabled()) {
090            Logging.debug("DOM parsing done in {0}", Utils.getDurationString(System.currentTimeMillis() - start));
091        }
092        return result;
093    }
094
095    /**
096     * Returns a new secure SAX parser, supporting XML namespaces.
097     * @return a new secure SAX parser, supporting XML namespaces
098     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
099     * @throws SAXException for SAX errors.
100     */
101    public static SAXParser newSafeSAXParser() throws ParserConfigurationException, SAXException {
102        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
103        parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
104        parserFactory.setNamespaceAware(true);
105        return parserFactory.newSAXParser();
106    }
107
108    /**
109     * Parse the content given {@link org.xml.sax.InputSource} as XML using the specified {@link org.xml.sax.helpers.DefaultHandler}.
110     * This method uses a secure SAX parser, supporting XML namespaces.
111     *
112     * @param is The InputSource containing the content to be parsed.
113     * @param dh The SAX DefaultHandler to use.
114     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
115     * @throws SAXException for SAX errors.
116     * @throws IOException if any IO errors occur.
117     */
118    public static void parseSafeSAX(InputSource is, DefaultHandler dh) throws ParserConfigurationException, SAXException, IOException {
119        long start = System.currentTimeMillis();
120        Logging.debug("Starting SAX parsing of {0} using {1}", is, dh);
121        newSafeSAXParser().parse(is, dh);
122        if (Logging.isDebugEnabled()) {
123            Logging.debug("SAX parsing done in {0}", Utils.getDurationString(System.currentTimeMillis() - start));
124        }
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        for (int i = 0; i < children.getLength(); i++) {
178            Node child = children.item(i);
179            if (child instanceof Element) {
180                return (Element) child;
181            }
182        }
183        return null;
184    }
185}