001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.xml.secure;
019
020import java.lang.invoke.MethodHandle;
021import java.util.Objects;
022
023import javax.xml.XMLConstants;
024import javax.xml.parsers.DocumentBuilder;
025import javax.xml.parsers.DocumentBuilderFactory;
026import javax.xml.parsers.FactoryConfigurationError;
027import javax.xml.parsers.ParserConfigurationException;
028import javax.xml.validation.Schema;
029
030import org.xml.sax.EntityResolver;
031
032/**
033 * Creates new, secure {@link DocumentBuilderFactory} instances.
034 * <p>
035 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}, XInclude resolution is denied by default. When
036 * {@link DocumentBuilderFactory#setXIncludeAware(boolean) setXIncludeAware(true)} is called on the returned factory, the parser will process {@code xi:include}
037 * elements but every external resource lookup is rejected. To permit specific trusted resources, install an {@link org.xml.sax.EntityResolver EntityResolver}
038 * on the {@link DocumentBuilder} that allow-lists them; any href the resolver does not explicitly allow stays blocked.
039 * </p>
040 * <p>
041 * This class is not itself a {@link DocumentBuilderFactory}, so it inherits none of the static JAXP factory methods. A caller therefore cannot obtain an
042 * unsecured factory through this class by calling a method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public
043 * wrapper class.
044 * </p>
045 *
046 * @see org.apache.commons.xml.secure
047 */
048public final class SecureDocumentBuilderFactory {
049
050    /**
051     * {@link DocumentBuilderFactory} wrapper that keeps an ignore-all {@link EntityResolver} floor on every {@link DocumentBuilder} produced.
052     * <p>
053     * Wraps each produced builder in a {@link SecureDocumentBuilder}; required when the underlying factory carries no resolver of its own and does not honor
054     * JAXP 1.5 {@code ACCESS_EXTERNAL_*} (e.g., the external Xerces distribution). A caller-set resolver is routed through the floor rather than replacing it.
055     * </p>
056     */
057    private static final class Wrapper extends DocumentBuilderFactory {
058
059        private final DocumentBuilderFactory delegate;
060
061        /**
062         * Constructs a new instance.
063         *
064         * @param delegate The delegate to wrap; must not be {@code null}.
065         * @throws NullPointerException Thrown if {@code delegate} is {@code null}.
066         */
067        private Wrapper(final DocumentBuilderFactory delegate) {
068            this.delegate = Objects.requireNonNull(delegate, "delegate");
069        }
070
071        @Override
072        public Object getAttribute(final String name) {
073            return delegate.getAttribute(name);
074        }
075
076        @Override
077        public boolean getFeature(final String name) throws ParserConfigurationException {
078            return delegate.getFeature(name);
079        }
080
081        @Override
082        public Schema getSchema() {
083            return delegate.getSchema();
084        }
085
086        @Override
087        public boolean isCoalescing() {
088            return delegate.isCoalescing();
089        }
090
091        @Override
092        public boolean isExpandEntityReferences() {
093            return delegate.isExpandEntityReferences();
094        }
095
096        @Override
097        public boolean isIgnoringComments() {
098            return delegate.isIgnoringComments();
099        }
100
101        @Override
102        public boolean isIgnoringElementContentWhitespace() {
103            return delegate.isIgnoringElementContentWhitespace();
104        }
105
106        @Override
107        public boolean isNamespaceAware() {
108            return delegate.isNamespaceAware();
109        }
110
111        @Override
112        public boolean isValidating() {
113            return delegate.isValidating();
114        }
115
116        @Override
117        public boolean isXIncludeAware() {
118            return delegate.isXIncludeAware();
119        }
120
121        @Override
122        public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
123            return new SecureDocumentBuilder(delegate.newDocumentBuilder());
124        }
125
126        @Override
127        public void setAttribute(final String name, final Object value) {
128            delegate.setAttribute(name, value);
129        }
130
131        @Override
132        public void setCoalescing(final boolean coalescing) {
133            delegate.setCoalescing(coalescing);
134        }
135
136        @Override
137        public void setExpandEntityReferences(final boolean expandEntityRef) {
138            delegate.setExpandEntityReferences(expandEntityRef);
139        }
140
141        @Override
142        public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
143            delegate.setFeature(name, value);
144        }
145
146        @Override
147        public void setIgnoringComments(final boolean ignoreComments) {
148            delegate.setIgnoringComments(ignoreComments);
149        }
150
151        @Override
152        public void setIgnoringElementContentWhitespace(final boolean whitespace) {
153            delegate.setIgnoringElementContentWhitespace(whitespace);
154        }
155
156        @Override
157        public void setNamespaceAware(final boolean awareness) {
158            delegate.setNamespaceAware(awareness);
159        }
160
161        @Override
162        public void setSchema(final Schema schema) {
163            delegate.setSchema(schema);
164        }
165
166        @Override
167        public void setValidating(final boolean validating) {
168            delegate.setValidating(validating);
169        }
170
171        @Override
172        public void setXIncludeAware(final boolean state) {
173            delegate.setXIncludeAware(state);
174        }
175    }
176    /**
177     * Class name of Android's Harmony-based {@link DocumentBuilderFactory}, which exposes no secure surface.
178     */
179    private static final String ANDROID_DOCUMENT_BUILDER_FACTORY = "org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl";
180    /**
181     * System property naming the {@link DocumentBuilderFactory} implementation, the JDK's own mechanism for reconfiguring the default parser.
182     */
183    private static final String DOM_FACTORY_ID = "javax.xml.parsers.DocumentBuilderFactory";
184
185    /**
186     * Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}.
187     */
188    static final String JDK_DOCUMENT_BUILDER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";
189
190    private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(DocumentBuilderFactory.class, "newDefaultInstance");
191
192    /**
193     * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here.
194     *
195     * @param factory The factory to configure; never {@code null}.
196     * @return The given factory, namespace-aware.
197     */
198    private static DocumentBuilderFactory makeNSAware(final DocumentBuilderFactory factory) {
199        factory.setNamespaceAware(true);
200        return factory;
201    }
202
203    /**
204     * Returns a new, secure {@link DocumentBuilderFactory} of the system-default implementation.
205     * <p>
206     * Obtained from {@code DocumentBuilderFactory.newDefaultInstance()} where the platform provides it (Java 9 or later),
207     * by instantiating the JDK's built-in implementation directly on Java 8,
208     * and by the standard {@link #newInstance()} lookup where the platform provides neither
209     * (for example, Android, whose lookup is itself pinned to the platform implementation).
210     * </p>
211     *
212     * @return A secure factory.
213     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
214     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither
215     *                                   {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android).
216     */
217    public static DocumentBuilderFactory newDefaultInstance() {
218        if (MH_newDefaultInstance != null) {
219            return secure(MethodHandleFactory.invokeExact(() -> (DocumentBuilderFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class));
220        }
221        try {
222            // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead.
223            // Load it from the system class loader: the context class loader may not see it (for example an OSGi bundle class loader).
224            return newInstance(JDK_DOCUMENT_BUILDER_FACTORY, ClassLoader.getSystemClassLoader());
225        } catch (final FactoryConfigurationError e) {
226            // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser.
227            return newInstance();
228        }
229    }
230
231    /**
232     * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the system-default implementation, enabling namespace awareness on
233     * {@link #newDefaultInstance()}, the behavior {@code DocumentBuilderFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have.
234     *
235     * @return A secure, namespace-aware factory.
236     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
237     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides
238     *                                   neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android).
239     */
240    public static DocumentBuilderFactory newDefaultNSInstance() {
241        return makeNSAware(newDefaultInstance());
242    }
243
244    /**
245     * Returns a new, secure {@link DocumentBuilderFactory}.
246     *
247     * @return A secure factory.
248     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
249     * @throws IllegalStateException     Thrown if a (non-Android) factory cannot support the secure processing feature
250     *                                   {@link XMLConstants#FEATURE_SECURE_PROCESSING}.
251     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
252     *                                   implementation is not available or cannot be instantiated.
253     */
254    public static DocumentBuilderFactory newInstance() {
255        return secure(DocumentBuilderFactory.newInstance());
256    }
257
258    /**
259     * Returns a new, secure {@link DocumentBuilderFactory} of the given implementation class.
260     *
261     * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation.
262     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
263     * @return A secure factory.
264     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
265     * @throws IllegalStateException     Thrown if a (non-Android) factory cannot support the secure processing feature
266     *                                   {@link XMLConstants#FEATURE_SECURE_PROCESSING}.
267     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
268     */
269    public static DocumentBuilderFactory newInstance(final String factoryClassName, final ClassLoader classLoader) {
270        return secure(DocumentBuilderFactory.newInstance(factoryClassName, classLoader));
271    }
272
273    /**
274     * Creates a new, secure, namespace-aware {@link DocumentBuilder} from {@link #newNSInstance()}.
275     * <p>
276     * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned builder and call {@link DocumentBuilder#reset()}
277     * between documents. Reusing the builder saves more than caching the factory would, and {@code reset()} costs next to nothing while keeping handler state
278     * from leaking between parses. A builder is not thread-safe, so reuse it within one thread.
279     * </p>
280     *
281     * @return A secure, namespace-aware builder.
282     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
283     *                                   create a builder.
284     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
285     *                                   implementation is not available or cannot be instantiated.
286     * @since 1.1.0
287     */
288    public static DocumentBuilder newNSDocumentBuilder() {
289        try {
290            return newNSInstance().newDocumentBuilder();
291        } catch (final ParserConfigurationException e) {
292            // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment.
293            throw SecureException.creationFailed(DocumentBuilder.class, e);
294        }
295    }
296
297    /**
298     * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior
299     * {@code DocumentBuilderFactory.newNSInstance()} (Java 13 or later) is specified to have.
300     *
301     * @return A secure, namespace-aware factory.
302     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
303     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
304     *                                   implementation is not available or cannot be instantiated.
305     */
306    public static DocumentBuilderFactory newNSInstance() {
307        return makeNSAware(newInstance());
308    }
309
310    /**
311     * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with.
312     * <p>
313     * While {@code overrideDefaultParser} is {@code false}, the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it:
314     * the built-in implementation, unless the {@value #DOM_FACTORY_ID} system property is set. That property is the JDK's own mechanism for
315     * reconfiguring the default parser, so it is honored through the standard lookup rather than bypassed.
316     * </p>
317     *
318     * @param overrideDefaultParser whether {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's
319     * default parser.
320     * @return A secure, namespace-aware factory.
321     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
322     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
323     *                                   implementation is not available or cannot be instantiated.
324     */
325    static DocumentBuilderFactory newNSInstance(final boolean overrideDefaultParser) {
326        return overrideDefaultParser || System.getProperty(DOM_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance();
327    }
328
329    /**
330     * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the given implementation class, enabling namespace awareness on
331     * {@link #newInstance(String, ClassLoader)}, the behavior {@code DocumentBuilderFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified
332     * to have.
333     *
334     * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation.
335     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
336     * @return A secure, namespace-aware factory.
337     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
338     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
339     */
340    public static DocumentBuilderFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) {
341        return makeNSAware(newInstance(factoryClassName, classLoader));
342    }
343
344    /**
345     * Applies capability-driven secure settings to any {@link DocumentBuilderFactory} on the classpath.
346     *
347     * <p>
348     * Rather than branching on the implementation class, this method probes what the factory supports and adapts:
349     * </p>
350     * <ul>
351     * <li><strong>Android</strong> (Harmony / KXmlParser): recognized by class name and left untouched. It exposes no {@link
352     * XMLConstants#FEATURE_SECURE_PROCESSING
353     * FSP}, no JAXP 1.5 {@code ACCESS_EXTERNAL_*} and no attribute API at all, while KXmlParser silently drops user-defined entities, so there is nothing to
354     *         apply.</li>
355     *     <li><strong>FSP</strong>: required. It switches on the implementation's built-in security manager, which is what carries the processing limits.</li>
356     *     <li><strong>Ignore-all resolver floor</strong>: every produced {@link DocumentBuilder} is wrapped by the nested wrapper, which keeps an
357     * ignore-all {@link EntityResolver} floor. That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's
358     * XInclude processor ignores {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} attributes are
359     *         needed here. A caller can chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li>
360     * </ul>
361     *
362     * @param factory The factory to secure.
363     * @return A new secure factory or the original factory, as-is, if it is a known Android factory.
364     * @throws SecureException Thrown if a (non-Android) factory cannot support the secure processing feature {@link XMLConstants#FEATURE_SECURE_PROCESSING}.
365     */
366    static DocumentBuilderFactory secure(final DocumentBuilderFactory factory) {
367        // Android exposes no FSP, ACCESS_EXTERNAL_* or attribute API, and KXmlParser drops user-defined entities; nothing to apply.
368        if (ANDROID_DOCUMENT_BUILDER_FACTORY.equals(factory.getClass().getName())) {
369            return factory;
370        }
371        // Required: enables the implementation's security manager, which carries the limits.
372        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
373        // Required: the wrapper installs an ignore-all EntityResolver floor on every DocumentBuilder.
374        // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* attributes are needed here.
375        // Callers can chain their resolvers, but not override the floor.
376        return new Wrapper(factory);
377    }
378
379    /**
380     * Sets a feature on the given factory, throwing a {@link SecureException} if the implementation does not recognize it.
381     *
382     * @param factory The factory to secure.
383     * @param feature The feature to set.
384     * @param value   The value to set.
385     * @throws SecureException   Thrown if this factory cannot support this feature.
386     * @throws NullPointerException Thrown if the {@code feature} parameter is null.
387     */
388    private static void setFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) {
389        try {
390            factory.setFeature(feature, value);
391        } catch (final ParserConfigurationException e) {
392            throw SecureException.featureFailed(feature, factory, e);
393        }
394    }
395
396    private SecureDocumentBuilderFactory() {
397        // static only
398    }
399}