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.SchemaFactory;
017import javax.xml.validation.SchemaFactoryConfigurationError;
018
019import org.w3c.dom.Document;
020import org.xml.sax.InputSource;
021import org.xml.sax.SAXException;
022import org.xml.sax.helpers.DefaultHandler;
023
024/**
025 * XML utils, mainly used to construct safe factories.
026 * @since 13901
027 */
028public final class XmlUtils {
029
030    private XmlUtils() {
031        // Hide default constructor for utils classes
032    }
033
034    /**
035     * Returns the W3C XML Schema factory implementation. Robust method dealing with ContextClassLoader problems.
036     * @return the W3C XML Schema factory implementation
037     */
038    public static SchemaFactory newXmlSchemaFactory() {
039        try {
040            return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
041        } catch (SchemaFactoryConfigurationError e) {
042            Logging.debug(e);
043            // Can happen with icedtea-web. Use workaround from https://issues.apache.org/jira/browse/GERONIMO-6185
044            Thread currentThread = Thread.currentThread();
045            ClassLoader old = currentThread.getContextClassLoader();
046            currentThread.setContextClassLoader(null);
047            try {
048                return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
049            } finally {
050                currentThread.setContextClassLoader(old);
051            }
052        }
053    }
054
055    /**
056     * Returns a new secure DOM builder, supporting XML namespaces.
057     * @return a new secure DOM builder, supporting XML namespaces
058     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
059     */
060    public static DocumentBuilder newSafeDOMBuilder() throws ParserConfigurationException {
061        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
062        builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
063        builderFactory.setNamespaceAware(true);
064        builderFactory.setValidating(false);
065        return builderFactory.newDocumentBuilder();
066    }
067
068    /**
069     * Parse the content given {@link InputStream} as XML.
070     * This method uses a secure DOM builder, supporting XML namespaces.
071     *
072     * @param is The InputStream containing the content to be parsed.
073     * @return the result DOM document
074     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
075     * @throws IOException if any IO errors occur.
076     * @throws SAXException for SAX errors.
077     */
078    public static Document parseSafeDOM(InputStream is) throws ParserConfigurationException, IOException, SAXException {
079        long start = System.currentTimeMillis();
080        Logging.debug("Starting DOM parsing of {0}", is);
081        Document result = newSafeDOMBuilder().parse(is);
082        if (Logging.isDebugEnabled()) {
083            Logging.debug("DOM parsing done in {0}", Utils.getDurationString(System.currentTimeMillis() - start));
084        }
085        return result;
086    }
087
088    /**
089     * Returns a new secure SAX parser, supporting XML namespaces.
090     * @return a new secure SAX parser, supporting XML namespaces
091     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
092     * @throws SAXException for SAX errors.
093     */
094    public static SAXParser newSafeSAXParser() throws ParserConfigurationException, SAXException {
095        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
096        parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
097        parserFactory.setNamespaceAware(true);
098        return parserFactory.newSAXParser();
099    }
100
101    /**
102     * Parse the content given {@link org.xml.sax.InputSource} as XML using the specified {@link org.xml.sax.helpers.DefaultHandler}.
103     * This method uses a secure SAX parser, supporting XML namespaces.
104     *
105     * @param is The InputSource containing the content to be parsed.
106     * @param dh The SAX DefaultHandler to use.
107     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
108     * @throws SAXException for SAX errors.
109     * @throws IOException if any IO errors occur.
110     */
111    public static void parseSafeSAX(InputSource is, DefaultHandler dh) throws ParserConfigurationException, SAXException, IOException {
112        long start = System.currentTimeMillis();
113        Logging.debug("Starting SAX parsing of {0} using {1}", is, dh);
114        newSafeSAXParser().parse(is, dh);
115        if (Logging.isDebugEnabled()) {
116            Logging.debug("SAX parsing done in {0}", Utils.getDurationString(System.currentTimeMillis() - start));
117        }
118    }
119
120    /**
121     * Returns a new secure {@link XMLInputFactory}.
122     * @return a new secure {@code XMLInputFactory}, for which external entities are not loaded
123     */
124    public static XMLInputFactory newSafeXMLInputFactory() {
125        XMLInputFactory factory = XMLInputFactory.newInstance();
126        // do not try to load external entities, nor validate the XML
127        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
128        factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
129        factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
130        return factory;
131    }
132
133    /**
134     * Returns a new secure {@link TransformerFactory}.
135     * @return a new secure {@link TransformerFactory}
136     * @throws TransformerConfigurationException if the factory or the Transformers or Templates it creates cannot support this feature.
137     */
138    public static TransformerFactory newSafeTransformerFactory() throws TransformerConfigurationException {
139        TransformerFactory factory = TransformerFactory.newInstance();
140        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
141        return factory;
142    }
143}