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.FactoryConfigurationError;
025import javax.xml.parsers.ParserConfigurationException;
026import javax.xml.parsers.SAXParser;
027import javax.xml.parsers.SAXParserFactory;
028import javax.xml.transform.Source;
029import javax.xml.transform.sax.SAXSource;
030import javax.xml.transform.stream.StreamSource;
031import javax.xml.validation.Schema;
032
033import org.xml.sax.ContentHandler;
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 EntityResolver}
047 * 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 * This class is not itself a {@link SAXParserFactory}, so it inherits none of the static JAXP factory methods. A caller therefore cannot obtain an unsecured
051 * factory through this class by calling a method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public wrapper
052 * class.
053 * </p>
054 *
055 * @see org.apache.commons.xml.secure
056 */
057public final class SecureSAXParserFactory {
058
059    /**
060     * {@link SecureXMLReader} for Android's {@code org.apache.harmony.xml.ExpatReader} that additionally surfaces its {@code namespace-prefixes} limitation at
061     * configuration time.
062     *
063     * <p>
064     * ExpatReader does not actually support the {@code namespace-prefixes} feature: enabling it is accepted by {@code setFeature} but fails later, during
065     * {@code parse}, with a {@link SAXNotSupportedException}. Reporting the rejection eagerly from {@link #setFeature(String, boolean)} lets consumers that
066     * probe the feature, such as Xalan's identity transformer, catch the exception and fall back instead of failing the whole parse.
067     * </p>
068     */
069    static final class SecureExpatXMLReader extends SecureXMLReader {
070
071        private static final String NAMESPACE_PREFIXES_FEATURE = "http://xml.org/sax/features/namespace-prefixes";
072
073        SecureExpatXMLReader(final XMLReader delegate) {
074            super(delegate);
075        }
076
077        @Override
078        public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
079            if (value && NAMESPACE_PREFIXES_FEATURE.equals(name)) {
080                throw new SAXNotSupportedException("ExpatReader does not support enabling the '" + NAMESPACE_PREFIXES_FEATURE + "' feature");
081            }
082            super.setFeature(name, value);
083        }
084    }
085    /**
086     * Universal SAX factory wrapper that funnels every produced parser through {@link SecureSAXParserFactory#secure(XMLReader)}.
087     * <p>
088     * {@link SAXParserFactory} exposes only a feature API and no property API, so the per-parse secure configuration (limits, entity blocking,
089     * implementation-specific fixups) has to run on each {@link XMLReader} the factory produces. This wrapper returns a {@link SecureSAXParser}, which applies
090     * that securing lazily to both the SAX 2 {@link XMLReader} and the SAX 1 {@link org.xml.sax.Parser} it exposes.
091     * </p>
092     */
093    private static final class Wrapper extends SAXParserFactory {
094
095        private final SAXParserFactory delegate;
096
097        /**
098         * Constructs a new instance.
099         *
100         * @param delegate The delegate to wrap; must not be {@code null}.
101         * @throws NullPointerException Thrown if {@code delegate} is {@code null}.
102         */
103        private Wrapper(final SAXParserFactory delegate) {
104            this.delegate = Objects.requireNonNull(delegate, "delegate");
105        }
106
107        @Override
108        public boolean getFeature(final String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
109            return delegate.getFeature(name);
110        }
111
112        @Override
113        public Schema getSchema() {
114            return delegate.getSchema();
115        }
116
117        @Override
118        public boolean isNamespaceAware() {
119            return delegate.isNamespaceAware();
120        }
121
122        @Override
123        public boolean isValidating() {
124            return delegate.isValidating();
125        }
126
127        @Override
128        public boolean isXIncludeAware() {
129            return delegate.isXIncludeAware();
130        }
131
132        @Override
133        public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
134            return new SecureSAXParser(delegate.newSAXParser());
135        }
136
137        @Override
138        public void setFeature(final String name, final boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
139            delegate.setFeature(name, value);
140        }
141
142        @Override
143        public void setNamespaceAware(final boolean awareness) {
144            delegate.setNamespaceAware(awareness);
145        }
146
147        @Override
148        public void setSchema(final Schema schema) {
149            delegate.setSchema(schema);
150        }
151
152        @Override
153        public void setValidating(final boolean validating) {
154            delegate.setValidating(validating);
155        }
156
157        @Override
158        public void setXIncludeAware(final boolean state) {
159            delegate.setXIncludeAware(state);
160        }
161    }
162    /**
163     * Class name of Android's Expat-backed {@link XMLReader}.
164     */
165    private static final String ANDROID_EXPAT_READER = "org.apache.harmony.xml.ExpatReader";
166
167    /**
168     * Class name of Android's Harmony-based {@link SAXParserFactory}, backed by the native Expat parser.
169     */
170    private static final String ANDROID_SAX_PARSER_FACTORY = "org.apache.harmony.xml.parsers.SAXParserFactoryImpl";
171
172    /**
173     * Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}.
174     */
175    static final String JDK_SAX_PARSER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl";
176
177    /**
178     * The JDK feature governing whether an implementation's internal parser lookup may resolve a third-party parser. The secure wrappers parse every source
179     * themselves, so instead of configuring the implementation the TrAX, XPath and schema wrappers read this feature and pick the rewrite parser accordingly.
180     */
181    static final String OVERRIDE_DEFAULT_PARSER = "jdk.xml.overrideDefaultParser";
182
183    /**
184     * System property naming the {@link SAXParserFactory} implementation, the JDK's own mechanism for reconfiguring the default parser.
185     */
186    private static final String SAX_FACTORY_ID = "javax.xml.parsers.SAXParserFactory";
187
188    private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(SAXParserFactory.class, "newDefaultInstance");
189
190    /**
191     * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here.
192     *
193     * @param factory The factory to configure; never {@code null}.
194     * @return The given factory, namespace-aware.
195     */
196    private static SAXParserFactory makeNSAware(final SAXParserFactory factory) {
197        factory.setNamespaceAware(true);
198        return factory;
199    }
200
201    /**
202     * Returns a new, secure {@link SAXParserFactory} of the system-default implementation.
203     * <p>
204     * Obtained from {@code SAXParserFactory.newDefaultInstance()} where the platform provides it (Java 9 or later),
205     * by instantiating the JDK's built-in implementation directly on Java 8,
206     * and by the standard {@link #newInstance()} lookup where the platform provides neither
207     * (for example, Android, whose lookup is itself pinned to the platform implementation).
208     * </p>
209     *
210     * @return A secure factory.
211     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
212     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither
213     *                                   {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android).
214     */
215    public static SAXParserFactory newDefaultInstance() {
216        if (MH_newDefaultInstance != null) {
217            return secure(MethodHandleFactory.invokeExact(() -> (SAXParserFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class));
218        }
219        try {
220            // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead.
221            // Load it from the system class loader: the context class loader may not see it (for example an OSGi bundle class loader).
222            return newInstance(JDK_SAX_PARSER_FACTORY, ClassLoader.getSystemClassLoader());
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 SAXParserFactory} of the system-default implementation, enabling namespace awareness on
231     * {@link #newDefaultInstance()}, the behavior {@code SAXParserFactory.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 SAXParserFactory newDefaultNSInstance() {
239        return makeNSAware(newDefaultInstance());
240    }
241
242    /**
243     * Returns a new, secure {@link SAXParserFactory}.
244     *
245     * @return A secure factory.
246     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
247     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
248     *                                   error} or if the implementation is not available or cannot be instantiated.
249     */
250    public static SAXParserFactory newInstance() {
251        return secure(SAXParserFactory.newInstance());
252    }
253
254    /**
255     * Returns a new, secure {@link SAXParserFactory} of the given implementation class.
256     *
257     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
258     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
259     * @return A secure factory.
260     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
261     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
262     */
263    public static SAXParserFactory newInstance(final String factoryClassName, final ClassLoader classLoader) {
264        return secure(SAXParserFactory.newInstance(factoryClassName, classLoader));
265    }
266
267    /**
268     * Returns a new, secure, namespace-aware {@link SAXParserFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior
269     * {@code SAXParserFactory.newNSInstance()} (Java 13 or later) is specified to have.
270     *
271     * @return A secure, namespace-aware factory.
272     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
273     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
274     *                                   error} or if the implementation is not available or cannot be instantiated.
275     */
276    public static SAXParserFactory newNSInstance() {
277        return makeNSAware(newInstance());
278    }
279
280    /**
281     * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with.
282     * <p>
283     * While {@code overrideDefaultParser} is {@code false}, the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it:
284     * the built-in parser, unless the {@value #SAX_FACTORY_ID} system property is set. That property is the JDK's own mechanism for reconfiguring the default
285     * parser, so it is honored through the standard lookup rather than bypassed.
286     * </p>
287     *
288     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
289     * @return A secure, namespace-aware factory.
290     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
291     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
292     *                                   implementation is not available or cannot be instantiated.
293     */
294    static SAXParserFactory newNSInstance(final boolean overrideDefaultParser) {
295        return overrideDefaultParser || System.getProperty(SAX_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance();
296    }
297
298    /**
299     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the given implementation class, enabling namespace awareness on
300     * {@link #newInstance(String, ClassLoader)}, the behavior {@code SAXParserFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified to
301     * have.
302     *
303     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
304     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
305     * @return A secure, namespace-aware factory.
306     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
307     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
308     */
309    public static SAXParserFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) {
310        return makeNSAware(newInstance(factoryClassName, classLoader));
311    }
312
313    /**
314     * Creates a new, secure, namespace-aware {@link SAXParser} from {@link #newNSInstance()}.
315     * <p>
316     * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned parser and call {@link SAXParser#reset()} between
317     * documents. Reusing the parser saves more than caching the factory would, and {@code reset()} costs next to nothing while keeping handler state from
318     * leaking between parses. A parser is not thread-safe, so reuse it within one thread.
319     * </p>
320     *
321     * @return A secure, namespace-aware parser.
322     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
323     *                                   create a parser.
324     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
325     *                                   error} or if the implementation is not available or cannot be instantiated.
326     * @since 1.1.0
327     */
328    public static SAXParser newNSSAXParser() {
329        try {
330            return newNSInstance().newSAXParser();
331        } catch (final ParserConfigurationException | SAXException e) {
332            // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment.
333            throw SecureException.creationFailed(SAXParser.class, e);
334        }
335    }
336
337    /**
338     * Creates a new, secure, namespace-aware {@link XMLReader} from {@link #newNSInstance()}, with no content handler registered.
339     * <p>
340     * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned reader and parse each document with it. Reusing the
341     * reader saves more than caching the factory would. Handlers set on the reader stay set between parses, and a reader is not thread-safe, so reuse it within
342     * one thread.
343     * </p>
344     *
345     * @return A secure, namespace-aware reader.
346     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
347     *                                   create a reader.
348     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
349     *                                   error} or if the implementation is not available or cannot be instantiated.
350     * @since 1.1.0
351     */
352    public static XMLReader newNSXMLReader() {
353        return newNSXMLReader(null);
354    }
355
356    /**
357     * Creates a new, secure, namespace-aware {@link XMLReader} from {@link #newNSInstance()}.
358     * <p>
359     * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned reader and parse each document with it. Reusing the
360     * reader saves more than caching the factory would. Handlers set on the reader stay set between parses, and a reader is not thread-safe, so reuse it within
361     * one thread.
362     * </p>
363     *
364     * @param handler The content handler to register on the reader, or {@code null} to register none.
365     * @return A secure, namespace-aware reader.
366     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
367     *                                   create a reader.
368     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
369     *                                   error} or if the implementation is not available or cannot be instantiated.
370     * @since 1.1.0
371     */
372    public static XMLReader newNSXMLReader(final ContentHandler handler) {
373        return newXMLReader(newNSInstance(), handler);
374    }
375
376    /**
377     * Creates a new secure, namespace-aware {@link XMLReader} for the TrAX, XPath and schema wrappers to parse sources with, from the factory
378     * {@link #newNSInstance(boolean)} selects.
379     *
380     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
381     * @return a secure reader.
382     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader; providing one is a routine capability of every
383     *                                   supported implementation, so a failure signals a broken environment, not a per-parse condition.
384     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
385     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
386     */
387    static XMLReader newXMLReader(final boolean overrideDefaultParser) {
388        return newXMLReader(newNSInstance(overrideDefaultParser), null);
389    }
390
391    /**
392     * Creates a new {@link XMLReader} from the given secure factory.
393     *
394     * @param factory The secure factory; never {@code null}.
395     * @param handler The content handler to register on the reader, or {@code null} to register none.
396     * @return A secure reader.
397     * @throws IllegalStateException Thrown if the factory cannot create a reader.
398     */
399    private static XMLReader newXMLReader(final SAXParserFactory factory, final ContentHandler handler) {
400        final XMLReader reader;
401        try {
402            reader = factory.newSAXParser().getXMLReader();
403        } catch (final ParserConfigurationException | SAXException e) {
404            // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment.
405            throw SecureException.creationFailed(XMLReader.class, e);
406        }
407        if (handler != null) {
408            reader.setContentHandler(handler);
409        }
410        return reader;
411    }
412
413    /**
414     * Applies capability-driven secure settings to any {@link SAXParserFactory} on the classpath.
415     *
416     * <p>
417     * Rather than branching on the implementation class, this method probes what the factory supports and adapts. Because
418     * {@link SAXParserFactory} exposes only a feature API and no property API, the per-parse configuration runs on each {@link XMLReader} the factory produces,
419     * funneled through the nested wrapper into {@link #secure(XMLReader)}:
420     * </p>
421     * <ul>
422     * <li><strong>Android</strong> (Harmony / Expat): {@link XMLConstants#FEATURE_SECURE_PROCESSING FSP} and the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties
423     * are not recognized, and libexpat enforces its own Billion Laughs check, so neither is applied. Two fixups are still needed: an ignore-all resolver
424     *         (Expat ignores external fetches silently when no resolver is set; the floor keeps that behavior non-bypassable, resolving anything unresolved to
425     *         empty), and a {@link SecureExpatXMLReader} so the unsupported {@code namespace-prefixes} feature is rejected at
426     *         configuration time rather than mid-parse.</li>
427     *     <li><strong>FSP</strong>: required on every other reader. It switches on the implementation's built-in security manager, which is what carries the
428     *         processing limits.</li>
429     * <li><strong>Ignore-all resolver floor</strong>: every reader is wrapped in a {@link SecureXMLReader} that keeps an ignore-all {@link EntityResolver}
430     * floor.
431     *         That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's XInclude processor ignores
432     * {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} properties are needed here. A caller can
433     *         chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li>
434     * </ul>
435     *
436     * @param factory The factory to secure; never {@code null}.
437     * @return a secure factory.
438     */
439    static SAXParserFactory secure(final SAXParserFactory factory) {
440        // Required: enables the implementation's security manager, which carries the limits. Android's Expat rejects FSP, so it is skipped there.
441        if (!ANDROID_SAX_PARSER_FACTORY.equals(factory.getClass().getName())) {
442            setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
443        }
444        // The per-parse securing (limits, entity blocking, Android fixups) lives in secure(XMLReader) because SAXParserFactory has no property API.
445        return new Wrapper(factory);
446    }
447
448    /**
449     * Rewrites a {@link Source} so that any SAX parsing it triggers runs through a secure {@link XMLReader}.
450     * <p>
451     * Only a {@link StreamSource} or a {@link SAXSource} without a reader is enriched with a secure, namespace-aware reader; other source kinds are returned
452     * as-is. Used by the TrAX and schema wrappers to route every source they parse through the secure SAX path.
453     * </p>
454     *
455     * @param source           The source to secure; never {@code null}.
456     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
457     * @return a secure source.
458     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader.
459     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
460     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
461     */
462    static Source secure(final Source source, final boolean overrideDefaultParser) {
463        if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
464            final InputSource inputSource = SAXSource.sourceToInputSource(source);
465            return inputSource == null ? source : new SAXSource(newXMLReader(overrideDefaultParser), inputSource);
466        }
467        return source;
468    }
469
470    /**
471     * Secures an existing {@link XMLReader}.
472     *
473     * @param reader The reader to secure; never {@code null}.
474     * @return A secure reader.
475     * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation.
476     */
477    static XMLReader secure(final XMLReader reader) {
478        if (reader instanceof SecureXMLReader) {
479            // Already secure (for example, a reader from a secure factory passed back through secure(XMLReader)); the floor is already in place.
480            return reader;
481        }
482        if (ANDROID_EXPAT_READER.equals(reader.getClass().getName())) {
483            // Expat ignores external fetches when no resolver is set; the ignore-all floor keeps that behavior non-bypassable (routing a caller-set resolver,
484            // including SAXParser.parse's handler, through it and resolving anything unresolved to empty) and, via SecureExpatXMLReader, rejects the
485            // unsupported namespace-prefixes feature eagerly rather than mid-parse.
486            return new SecureExpatXMLReader(reader);
487        }
488        // Required: enables the JDK XMLSecurityManager / Xerces SecurityManager limits.
489        setFeature(reader, XMLConstants.FEATURE_SECURE_PROCESSING, true);
490        // Required: SecureXMLReader installs an ignore-all EntityResolver floor on the reader.
491        // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* properties are needed here.
492        // Callers can chain their resolvers, but not override the floor.
493        return new SecureXMLReader(reader);
494    }
495
496    private static void setFeature(final SAXParserFactory factory, final String feature, final boolean value) {
497        try {
498            factory.setFeature(feature, value);
499        } catch (final Exception e) {
500            throw SecureException.featureFailed(feature, factory, e);
501        }
502    }
503
504    private static void setFeature(final XMLReader reader, final String feature, final boolean value) {
505        try {
506            reader.setFeature(feature, value);
507        } catch (final Exception e) {
508            throw SecureException.featureFailed(feature, reader, e);
509        }
510    }
511
512    private SecureSAXParserFactory() {
513        // static only
514    }
515}