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.FactoryConfigurationError;
026import javax.xml.parsers.ParserConfigurationException;
027import javax.xml.parsers.SAXParser;
028import javax.xml.parsers.SAXParserFactory;
029import javax.xml.transform.Source;
030import javax.xml.transform.sax.SAXSource;
031import javax.xml.transform.stream.StreamSource;
032import javax.xml.validation.Schema;
033
034import org.xml.sax.EntityResolver;
035import org.xml.sax.InputSource;
036import org.xml.sax.SAXException;
037import org.xml.sax.SAXNotRecognizedException;
038import org.xml.sax.SAXNotSupportedException;
039import org.xml.sax.XMLReader;
040
041/**
042 * Creates new, secure {@link SAXParserFactory} instances.
043 * <p>
044 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}, XInclude resolution is denied by default. When
045 * {@link SAXParserFactory#setXIncludeAware(boolean) setXIncludeAware(true)} is called on the returned factory, the parser will process {@code xi:include}
046 * elements but every external resource lookup is rejected. To permit specific trusted resources, install an {@link org.xml.sax.EntityResolver
047 * EntityResolver} on the {@link org.xml.sax.XMLReader} that allow-lists them; any href the resolver does not explicitly allow stays blocked.
048 * </p>
049 * <p>
050 * Not a {@link SAXParserFactory} itself, so none of the JAXP static factory methods is inherited: a caller cannot reach a non-secured factory through this class
051 * by calling an inherited method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public wrapper class.
052 * </p>
053 *
054 * @see org.apache.commons.xml.secure
055 */
056public final class SecureSAXParserFactory {
057
058    /**
059     * {@link SecureXMLReader} for Android's {@code org.apache.harmony.xml.ExpatReader} that additionally surfaces its {@code namespace-prefixes} limitation at
060     * configuration time.
061     *
062     * <p>ExpatReader does not actually support the {@code namespace-prefixes} feature: enabling it is accepted by {@code setFeature} but fails later, during
063     * {@code parse}, with a {@link SAXNotSupportedException}. Reporting the rejection eagerly from {@link #setFeature(String, boolean)} lets consumers that probe
064     * the feature, such as Xalan's identity transformer, catch the exception and fall back instead of failing the whole parse.</p>
065     */
066    static final class SecureExpatXMLReader extends SecureXMLReader {
067
068        private static final String NAMESPACE_PREFIXES_FEATURE = "http://xml.org/sax/features/namespace-prefixes";
069
070        SecureExpatXMLReader(final XMLReader delegate) {
071            super(delegate);
072        }
073
074        @Override
075        public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
076            if (value && NAMESPACE_PREFIXES_FEATURE.equals(name)) {
077                throw new SAXNotSupportedException("ExpatReader does not support enabling the '" + NAMESPACE_PREFIXES_FEATURE + "' feature");
078            }
079            super.setFeature(name, value);
080        }
081    }
082    /**
083     * Universal SAX factory wrapper that funnels every produced parser through {@link SecureSAXParserFactory#secure(XMLReader)}.
084     * <p>
085     * {@link SAXParserFactory} exposes only a feature API and no property API, so the per-parse secure (limits, entity blocking, implementation-specific fixups)
086     * has to run on each {@link XMLReader} the factory produces. This wrapper returns a {@link SecureSAXParser}, which applies that securing lazily to both the
087     * SAX 2 {@link XMLReader} and the SAX 1 {@link org.xml.sax.Parser} it exposes.
088     * </p>
089     *
090     * @see org.apache.commons.xml.secure
091     */
092    private static final class Wrapper extends SAXParserFactory {
093
094        private final SAXParserFactory delegate;
095
096        /**
097         * Constructs a new instance.
098         *
099         * @param delegate the delegate to wrap; must not be {@code null}.
100         * @throws NullPointerException if {@code delegate} is {@code null}.
101         */
102        private Wrapper(final SAXParserFactory delegate) {
103            this.delegate = Objects.requireNonNull(delegate, "delegate");
104        }
105
106        @Override
107        public boolean getFeature(final String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
108            return delegate.getFeature(name);
109        }
110
111        @Override
112        public Schema getSchema() {
113            return delegate.getSchema();
114        }
115
116        @Override
117        public boolean isNamespaceAware() {
118            return delegate.isNamespaceAware();
119        }
120
121        @Override
122        public boolean isValidating() {
123            return delegate.isValidating();
124        }
125
126        @Override
127        public boolean isXIncludeAware() {
128            return delegate.isXIncludeAware();
129        }
130
131        @Override
132        public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
133            return new SecureSAXParser(delegate.newSAXParser());
134        }
135
136        @Override
137        public void setFeature(final String name, final boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
138            delegate.setFeature(name, value);
139        }
140
141        @Override
142        public void setNamespaceAware(final boolean awareness) {
143            delegate.setNamespaceAware(awareness);
144        }
145
146        @Override
147        public void setSchema(final Schema schema) {
148            delegate.setSchema(schema);
149        }
150
151        @Override
152        public void setValidating(final boolean validating) {
153            delegate.setValidating(validating);
154        }
155
156        @Override
157        public void setXIncludeAware(final boolean state) {
158            delegate.setXIncludeAware(state);
159        }
160    }
161    /** Class name of Android's Expat-backed {@link XMLReader}. */
162    private static final String ANDROID_EXPAT_READER = "org.apache.harmony.xml.ExpatReader";
163
164    /** Class name of Android's Harmony-based {@link SAXParserFactory}, backed by the native Expat parser. */
165    private static final String ANDROID_SAX_PARSER_FACTORY = "org.apache.harmony.xml.parsers.SAXParserFactoryImpl";
166
167    /** Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. */
168    private static final String JDK_SAX_PARSER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl";
169
170    /**
171     * The JDK feature governing whether an implementation's internal parser lookup may resolve a third-party parser. The secure wrappers parse every source
172     * themselves, so instead of configuring the implementation the TrAX, XPath and schema wrappers read this feature and pick the rewrite parser accordingly.
173     */
174    static final String OVERRIDE_DEFAULT_PARSER = "jdk.xml.overrideDefaultParser";
175
176    /** System property naming the {@link SAXParserFactory} implementation, the JDK's own mechanism for reconfiguring the default parser. */
177    private static final String SAX_FACTORY_ID = "javax.xml.parsers.SAXParserFactory";
178
179    private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(SAXParserFactory.class, "newDefaultInstance",
180            MethodType.methodType(SAXParserFactory.class));
181
182    /**
183     * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here.
184     *
185     * @param factory the factory to configure; never {@code null}.
186     * @return The given factory, namespace-aware.
187     */
188    private static SAXParserFactory makeNSAware(final SAXParserFactory factory) {
189        factory.setNamespaceAware(true);
190        return factory;
191    }
192
193    /**
194     * Returns a new, secure {@link SAXParserFactory} of the system-default implementation.
195     * <p>
196     * Obtained as by {@code SAXParserFactory.newDefaultInstance()} where the platform provides it (Java 9 or later),
197     * by instantiating the JDK's built-in implementation directly on Java 8,
198     * and by the standard {@link #newInstance()} lookup where the platform provides neither
199     * (for example, Android, whose lookup is itself pinned to the platform implementation).
200     * </p>
201     *
202     * @return A secure factory.
203     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
204     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither
205     *                                   {@code newDefaultInstance()} nor the JDK's built-in implementation (for example Android).
206     */
207    public static SAXParserFactory newDefaultInstance() {
208        if (MH_newDefaultInstance != null) {
209            return secure(MethodHandleFactory.invokeExact(() -> (SAXParserFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class));
210        }
211        try {
212            // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead.
213            return newInstance(JDK_SAX_PARSER_FACTORY, null);
214        } catch (final FactoryConfigurationError e) {
215            // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser.
216            return newInstance();
217        }
218    }
219
220    /**
221     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the system-default implementation, enabling namespace awareness on
222     * {@link #newDefaultInstance()}, the behavior {@code SAXParserFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have.
223     *
224     * @return A secure, namespace-aware factory.
225     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
226     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides
227     *                                   neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example Android).
228     */
229    public static SAXParserFactory newDefaultNSInstance() {
230        return makeNSAware(newDefaultInstance());
231    }
232
233    /**
234     * Returns a new, secure {@link SAXParserFactory}.
235     *
236     * @return A secure factory.
237     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
238     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
239     *                                   error} or if the implementation is not available or cannot be instantiated.
240     */
241    public static SAXParserFactory newInstance() {
242        return secure(SAXParserFactory.newInstance());
243    }
244
245    /**
246     * Returns a new, secure {@link SAXParserFactory} of the given implementation class.
247     *
248     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
249     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
250     * @return A secure factory.
251     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
252     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
253     */
254    public static SAXParserFactory newInstance(final String factoryClassName, final ClassLoader classLoader) {
255        return secure(SAXParserFactory.newInstance(factoryClassName, classLoader));
256    }
257
258    /**
259     * Returns a new, secure, namespace-aware {@link SAXParserFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior
260     * {@code SAXParserFactory.newNSInstance()} (Java 13 or later) is specified to have.
261     *
262     * @return A secure, namespace-aware factory.
263     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
264     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
265     *                                   error} or if the implementation is not available or cannot be instantiated.
266     */
267    public static SAXParserFactory newNSInstance() {
268        return makeNSAware(newInstance());
269    }
270
271    /**
272     * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with.
273     * <p>
274     * 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 parser,
275     * unless the {@value #SAX_FACTORY_ID} system property is set — that property is the JDK's own mechanism for reconfiguring the default
276     * parser, so it is honored through the standard lookup rather than bypassed.
277     * </p>
278     *
279     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
280     * @return A secure, namespace-aware factory.
281     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
282     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
283     *                                   implementation is not available or cannot be instantiated.
284     */
285    static SAXParserFactory newNSInstance(final boolean overrideDefaultParser) {
286        return overrideDefaultParser || System.getProperty(SAX_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance();
287    }
288
289    /**
290     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the given implementation class, enabling namespace awareness on
291     * {@link #newInstance(String, ClassLoader)}, the behavior {@code SAXParserFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified to have.
292     *
293     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
294     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
295     * @return A secure, namespace-aware factory.
296     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
297     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
298     */
299    public static SAXParserFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) {
300        return makeNSAware(newInstance(factoryClassName, classLoader));
301    }
302
303    /**
304     * Creates a new secure, namespace-aware {@link XMLReader} for the TrAX, XPath and schema wrappers to parse sources with, from the factory
305     * {@link #newNSInstance(boolean)} selects.
306     *
307     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
308     * @return a secure reader.
309     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader; providing one is a routine capability of every
310     *                                   supported implementation, so a failure signals a broken environment, not a per-parse condition.
311     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
312     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
313     */
314    static XMLReader newXMLReader(final boolean overrideDefaultParser) {
315        try {
316            return newNSInstance(overrideDefaultParser).newSAXParser().getXMLReader();
317        } catch (ParserConfigurationException | SAXException e) {
318            throw SecureException.readerFailed(e);
319        }
320    }
321
322    /**
323     * Capability-driven securing for any {@link SAXParserFactory} on the classpath.
324     *
325     * <p>Rather than branching on the implementation class, this method probes what the parser supports and adapts. Because
326     * {@link SAXParserFactory} exposes only a feature API and no property API, the per-parse configuration runs on each {@link XMLReader} the factory produces,
327     * funnelled through the nested wrapper into {@link #secure(XMLReader)}:</p>
328     * <ul>
329     *     <li><strong>Android</strong> (Harmony / Expat): {@link XMLConstants#FEATURE_SECURE_PROCESSING FSP} and the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties
330     *         are not recognized, and libexpat enforces its own Billion Laughs check, so neither is applied. Two fixups are still needed: an ignore-all resolver
331     *         (Expat ignores external fetches silently when no resolver is set; the floor keeps that behavior non-bypassable, resolving anything unresolved to
332     *         empty), and a {@link SecureExpatXMLReader} so the unsupported {@code namespace-prefixes} feature is rejected at
333     *         configuration time rather than mid-parse.</li>
334     *     <li><strong>FSP</strong>: required on every other reader. It switches on the implementation's built-in security manager, which is what carries the
335     *         processing limits.</li>
336     *     <li><strong>Ignore-all resolver floor</strong>: every reader is wrapped in a {@link SecureXMLReader} that keeps an ignore-all {@link EntityResolver} floor.
337     *         That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's XInclude processor ignores
338     *         {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} properties are needed here. A caller can
339     *         chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li>
340     * </ul>
341     *
342     * @param factory the factory to secure; never {@code null}.
343     * @return a secure factory.
344     */
345    static SAXParserFactory secure(final SAXParserFactory factory) {
346        // Required: enables the implementation's security manager, which carries the limits. Android's Expat rejects FSP, so it is skipped there.
347        if (!ANDROID_SAX_PARSER_FACTORY.equals(factory.getClass().getName())) {
348            setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
349        }
350        // The per-parse securing (limits, entity blocking, Android fixups) lives in secure(XMLReader) because SAXParserFactory has no property API.
351        return new Wrapper(factory);
352    }
353
354    /**
355     * Rewrites a {@link Source} so that any SAX parsing it triggers runs through a secure {@link XMLReader}.
356     * <p>
357     * Only a {@link StreamSource} or a {@link SAXSource} without a reader is enriched with a secure, namespace-aware reader; other source kinds are returned
358     * as-is. Used by the TrAX and schema wrappers to route every source they parse through the SAX secure path.
359     * </p>
360     *
361     * @param source           the source to secure; never {@code null}.
362     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
363     * @return a secure source.
364     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader.
365     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
366     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
367     */
368    static Source secure(final Source source, final boolean overrideDefaultParser) {
369        if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
370            final InputSource inputSource = SAXSource.sourceToInputSource(source);
371            return inputSource == null ? source : new SAXSource(newXMLReader(overrideDefaultParser), inputSource);
372        }
373        return source;
374    }
375
376    /**
377     * Secures an existing {@link XMLReader}.
378     *
379     * @param reader The reader to secure; never {@code null}.
380     * @return A secure reader.
381     * @throws IllegalStateException if a required secure setting cannot be applied to the underlying implementation.
382     */
383    static XMLReader secure(final XMLReader reader) {
384        if (reader instanceof SecureXMLReader) {
385            // Already secure (for example, a reader from a secure factory passed back through secure(XMLReader)); the floor is already in place.
386            return reader;
387        }
388        if (ANDROID_EXPAT_READER.equals(reader.getClass().getName())) {
389            // Expat ignores external fetches when no resolver is set; the ignore-all floor keeps that behavior non-bypassable (routing a caller-set resolver,
390            // including SAXParser.parse's handler, through it and resolving anything unresolved to empty) and, via SecureExpatXMLReader, rejects the
391            // unsupported namespace-prefixes feature eagerly rather than mid-parse.
392            return new SecureExpatXMLReader(reader);
393        }
394        // Required: enables the JDK XMLSecurityManager / Xerces SecurityManager limits.
395        setFeature(reader, XMLConstants.FEATURE_SECURE_PROCESSING, true);
396        // Required: SecureXMLReader installs an ignore-all EntityResolver floor on the reader.
397        // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* properties are needed here.
398        // Callers can chain their resolvers, but not override the floor.
399        return new SecureXMLReader(reader);
400    }
401
402    private static void setFeature(final SAXParserFactory factory, final String feature, final boolean value) {
403        try {
404            factory.setFeature(feature, value);
405        } catch (final Exception e) {
406            throw SecureException.featureFailed(feature, factory, e);
407        }
408    }
409
410    private static void setFeature(final XMLReader reader, final String feature, final boolean value) {
411        try {
412            reader.setFeature(feature, value);
413        } catch (final Exception e) {
414            throw SecureException.featureFailed(feature, reader, e);
415        }
416    }
417
418    private SecureSAXParserFactory() {
419        // static only
420    }
421}