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}