Merge branch 'master' of ../controller
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / xml / UntrustedXML.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.util.xml;
9
10 import com.google.common.annotations.Beta;
11 import java.io.InputStream;
12 import java.io.Reader;
13 import java.nio.charset.Charset;
14 import java.util.function.Supplier;
15 import javax.xml.XMLConstants;
16 import javax.xml.parsers.DocumentBuilder;
17 import javax.xml.parsers.DocumentBuilderFactory;
18 import javax.xml.parsers.ParserConfigurationException;
19 import javax.xml.parsers.SAXParser;
20 import javax.xml.parsers.SAXParserFactory;
21 import javax.xml.stream.XMLInputFactory;
22 import javax.xml.stream.XMLStreamException;
23 import javax.xml.stream.XMLStreamReader;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.opendaylight.yangtools.util.ClassLoaderUtils;
26 import org.xml.sax.SAXException;
27 import org.xml.sax.SAXNotRecognizedException;
28 import org.xml.sax.SAXNotSupportedException;
29
30 /**
31  * Set of utility methods for instantiating parser that deal with untrusted XML sources.
32  *
33  * @author Robert Varga
34  */
35 @Beta
36 public final class UntrustedXML {
37     private static final @NonNull DocumentBuilderFactory DBF;
38
39     static {
40         final DocumentBuilderFactory f = getLimited(DocumentBuilderFactory::newInstance);
41         f.setCoalescing(true);
42         f.setExpandEntityReferences(false);
43         f.setIgnoringElementContentWhitespace(true);
44         f.setIgnoringComments(true);
45         f.setNamespaceAware(true);
46         f.setXIncludeAware(false);
47         try {
48             f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
49             f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
50             f.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
51             f.setFeature("http://xml.org/sax/features/external-general-entities", false);
52             f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
53         } catch (final ParserConfigurationException e) {
54             throw new ExceptionInInitializerError(e);
55         }
56         DBF = f;
57     }
58
59     private static final SAXParserFactory SPF;
60
61     static {
62         final SAXParserFactory f = getLimited(SAXParserFactory::newInstance);
63         f.setNamespaceAware(true);
64         f.setXIncludeAware(false);
65         try {
66             f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
67             f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
68             f.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
69             f.setFeature("http://xml.org/sax/features/external-general-entities", false);
70             f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
71         } catch (final SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
72             throw new ExceptionInInitializerError(e);
73         }
74
75         SPF = f;
76     }
77
78     private static final XMLInputFactory XIF;
79
80     static {
81         final XMLInputFactory f = getLimited(XMLInputFactory::newInstance);
82         f.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
83         f.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
84         f.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
85         f.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
86
87         XIF = f;
88     }
89
90     private UntrustedXML() {
91         throw new UnsupportedOperationException();
92     }
93
94     /**
95      * Create a new {@link DocumentBuilder} for dealing with untrusted XML data. This method is equivalent to
96      * {@link DocumentBuilderFactory#newDocumentBuilder()}, except it does not throw a checked exception.
97      *
98      * @return A new DocumentBuilder
99      * @throws UnsupportedOperationException if the runtime fails to instantiate a good enough builder
100      */
101     public static @NonNull DocumentBuilder newDocumentBuilder() {
102         try {
103             return DBF.newDocumentBuilder();
104         } catch (ParserConfigurationException e) {
105             throw new UnsupportedOperationException("Failed to instantiate a DocumentBuilder", e);
106         }
107     }
108
109     /**
110      * Create a new {@link SAXParser} for dealing with untrusted XML data. This method is equivalent to
111      * {@link SAXParserFactory#newSAXParser()}, except it does not throw a checked exception.
112      *
113      * @return A new SAXParser
114      * @throws UnsupportedOperationException if the runtime fails to instantiate a good enough builder
115      */
116     public static @NonNull SAXParser newSAXParser() {
117         try {
118             return SPF.newSAXParser();
119         } catch (ParserConfigurationException | SAXException e) {
120             throw new UnsupportedOperationException("Failed to instantiate a SAXParser", e);
121         }
122     }
123
124     /**
125      * Create a new {@link XMLStreamReader} for dealing with untrusted XML data. This method is equivalent to
126      * {@link XMLInputFactory#createXMLStreamReader(InputStream)}.
127      *
128      * @return A new XMLStreamReader
129      * @throws XMLStreamException when the underlying factory throws it
130      */
131     public static @NonNull XMLStreamReader createXMLStreamReader(final InputStream stream) throws XMLStreamException {
132         return XIF.createXMLStreamReader(stream);
133     }
134
135     /**
136      * Create a new {@link XMLStreamReader} for dealing with untrusted XML data. This method is equivalent to
137      * {@link XMLInputFactory#createXMLStreamReader(InputStream, String)}, except it takes an explict charset argument.
138      *
139      * @return A new XMLStreamReader
140      * @throws XMLStreamException when the underlying factory throws it
141      */
142     public static @NonNull XMLStreamReader createXMLStreamReader(final InputStream stream, final Charset charset)
143             throws XMLStreamException {
144         return XIF.createXMLStreamReader(stream, charset.name());
145     }
146
147     /**
148      * Create a new {@link XMLStreamReader} for dealing with untrusted XML data. This method is equivalent to
149      * {@link XMLInputFactory#createXMLStreamReader(Reader)}.
150      *
151      * @return A new XMLStreamReader
152      * @throws XMLStreamException when the underlying factory throws it
153      */
154     public static @NonNull XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
155         return XIF.createXMLStreamReader(reader);
156     }
157
158     private static <T> T getLimited(final @NonNull Supplier<T> supplier) {
159         final ClassLoader loader = UntrustedXML.class.getClassLoader();
160         return loader == null ? supplier.get() : ClassLoaderUtils.getWithClassLoader(loader, supplier);
161     }
162 }