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