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            return newInstance(JDK_SAX_PARSER_FACTORY, null);
222        } catch (final FactoryConfigurationError e) {
223            // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser.
224            return newInstance();
225        }
226    }
227
228    /**
229     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the system-default implementation, enabling namespace awareness on
230     * {@link #newDefaultInstance()}, the behavior {@code SAXParserFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have.
231     *
232     * @return A secure, namespace-aware factory.
233     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
234     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides
235     *                                   neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android).
236     */
237    public static SAXParserFactory newDefaultNSInstance() {
238        return makeNSAware(newDefaultInstance());
239    }
240
241    /**
242     * Returns a new, secure {@link SAXParserFactory}.
243     *
244     * @return A secure factory.
245     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
246     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
247     *                                   error} or if the implementation is not available or cannot be instantiated.
248     */
249    public static SAXParserFactory newInstance() {
250        return secure(SAXParserFactory.newInstance());
251    }
252
253    /**
254     * Returns a new, secure {@link SAXParserFactory} of the given implementation class.
255     *
256     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
257     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
258     * @return A secure factory.
259     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
260     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
261     */
262    public static SAXParserFactory newInstance(final String factoryClassName, final ClassLoader classLoader) {
263        return secure(SAXParserFactory.newInstance(factoryClassName, classLoader));
264    }
265
266    /**
267     * Returns a new, secure, namespace-aware {@link SAXParserFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior
268     * {@code SAXParserFactory.newNSInstance()} (Java 13 or later) is specified to have.
269     *
270     * @return A secure, namespace-aware factory.
271     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
272     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
273     *                                   error} or if the implementation is not available or cannot be instantiated.
274     */
275    public static SAXParserFactory newNSInstance() {
276        return makeNSAware(newInstance());
277    }
278
279    /**
280     * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with.
281     * <p>
282     * While {@code overrideDefaultParser} is {@code false}, the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it:
283     * 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
284     * parser, so it is honored through the standard lookup rather than bypassed.
285     * </p>
286     *
287     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
288     * @return A secure, namespace-aware factory.
289     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
290     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
291     *                                   implementation is not available or cannot be instantiated.
292     */
293    static SAXParserFactory newNSInstance(final boolean overrideDefaultParser) {
294        return overrideDefaultParser || System.getProperty(SAX_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance();
295    }
296
297    /**
298     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the given implementation class, enabling namespace awareness on
299     * {@link #newInstance(String, ClassLoader)}, the behavior {@code SAXParserFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified to
300     * have.
301     *
302     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
303     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
304     * @return A secure, namespace-aware factory.
305     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
306     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
307     */
308    public static SAXParserFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) {
309        return makeNSAware(newInstance(factoryClassName, classLoader));
310    }
311
312    /**
313     * Creates a new, secure, namespace-aware {@link SAXParser} from {@link #newNSInstance()}.
314     * <p>
315     * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned parser and call {@link SAXParser#reset()} between
316     * documents. Reusing the parser saves more than caching the factory would, and {@code reset()} costs next to nothing while keeping handler state from
317     * leaking between parses. A parser is not thread-safe, so reuse it within one thread.
318     * </p>
319     *
320     * @return A secure, namespace-aware parser.
321     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
322     *                                   create a parser.
323     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
324     *                                   error} or if the implementation is not available or cannot be instantiated.
325     * @since 1.1.0
326     */
327    public static SAXParser newNSSAXParser() {
328        try {
329            return newNSInstance().newSAXParser();
330        } catch (final ParserConfigurationException | SAXException e) {
331            // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment.
332            throw SecureException.creationFailed(SAXParser.class, e);
333        }
334    }
335
336    /**
337     * Creates a new, secure, namespace-aware {@link XMLReader} from {@link #newNSInstance()}, with no content handler registered.
338     * <p>
339     * 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
340     * 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
341     * one thread.
342     * </p>
343     *
344     * @return A secure, namespace-aware reader.
345     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
346     *                                   create a reader.
347     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
348     *                                   error} or if the implementation is not available or cannot be instantiated.
349     * @since 1.1.0
350     */
351    public static XMLReader newNSXMLReader() {
352        return newNSXMLReader(null);
353    }
354
355    /**
356     * Creates a new, secure, namespace-aware {@link XMLReader} from {@link #newNSInstance()}.
357     * <p>
358     * 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
359     * 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
360     * one thread.
361     * </p>
362     *
363     * @param handler The content handler to register on the reader, or {@code null} to register none.
364     * @return A secure, namespace-aware reader.
365     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot
366     *                                   create a reader.
367     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
368     *                                   error} or if the implementation is not available or cannot be instantiated.
369     * @since 1.1.0
370     */
371    public static XMLReader newNSXMLReader(final ContentHandler handler) {
372        return newXMLReader(newNSInstance(), handler);
373    }
374
375    /**
376     * Creates a new secure, namespace-aware {@link XMLReader} for the TrAX, XPath and schema wrappers to parse sources with, from the factory
377     * {@link #newNSInstance(boolean)} selects.
378     *
379     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
380     * @return a secure reader.
381     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader; providing one is a routine capability of every
382     *                                   supported implementation, so a failure signals a broken environment, not a per-parse condition.
383     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
384     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
385     */
386    static XMLReader newXMLReader(final boolean overrideDefaultParser) {
387        return newXMLReader(newNSInstance(overrideDefaultParser), null);
388    }
389
390    /**
391     * Creates a new {@link XMLReader} from the given secure factory.
392     *
393     * @param factory The secure factory; never {@code null}.
394     * @param handler The content handler to register on the reader, or {@code null} to register none.
395     * @return A secure reader.
396     * @throws IllegalStateException Thrown if the factory cannot create a reader.
397     */
398    private static XMLReader newXMLReader(final SAXParserFactory factory, final ContentHandler handler) {
399        final XMLReader reader;
400        try {
401            reader = factory.newSAXParser().getXMLReader();
402        } catch (final ParserConfigurationException | SAXException e) {
403            // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment.
404            throw SecureException.creationFailed(XMLReader.class, e);
405        }
406        if (handler != null) {
407            reader.setContentHandler(handler);
408        }
409        return reader;
410    }
411
412    /**
413     * Applies capability-driven secure settings to any {@link SAXParserFactory} on the classpath.
414     *
415     * <p>
416     * Rather than branching on the implementation class, this method probes what the factory supports and adapts. Because
417     * {@link SAXParserFactory} exposes only a feature API and no property API, the per-parse configuration runs on each {@link XMLReader} the factory produces,
418     * funneled through the nested wrapper into {@link #secure(XMLReader)}:
419     * </p>
420     * <ul>
421     * <li><strong>Android</strong> (Harmony / Expat): {@link XMLConstants#FEATURE_SECURE_PROCESSING FSP} and the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties
422     * are not recognized, and libexpat enforces its own Billion Laughs check, so neither is applied. Two fixups are still needed: an ignore-all resolver
423     *         (Expat ignores external fetches silently when no resolver is set; the floor keeps that behavior non-bypassable, resolving anything unresolved to
424     *         empty), and a {@link SecureExpatXMLReader} so the unsupported {@code namespace-prefixes} feature is rejected at
425     *         configuration time rather than mid-parse.</li>
426     *     <li><strong>FSP</strong>: required on every other reader. It switches on the implementation's built-in security manager, which is what carries the
427     *         processing limits.</li>
428     * <li><strong>Ignore-all resolver floor</strong>: every reader is wrapped in a {@link SecureXMLReader} that keeps an ignore-all {@link EntityResolver}
429     * floor.
430     *         That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's XInclude processor ignores
431     * {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} properties are needed here. A caller can
432     *         chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li>
433     * </ul>
434     *
435     * @param factory The factory to secure; never {@code null}.
436     * @return a secure factory.
437     */
438    static SAXParserFactory secure(final SAXParserFactory factory) {
439        // Required: enables the implementation's security manager, which carries the limits. Android's Expat rejects FSP, so it is skipped there.
440        if (!ANDROID_SAX_PARSER_FACTORY.equals(factory.getClass().getName())) {
441            setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
442        }
443        // The per-parse securing (limits, entity blocking, Android fixups) lives in secure(XMLReader) because SAXParserFactory has no property API.
444        return new Wrapper(factory);
445    }
446
447    /**
448     * Rewrites a {@link Source} so that any SAX parsing it triggers runs through a secure {@link XMLReader}.
449     * <p>
450     * Only a {@link StreamSource} or a {@link SAXSource} without a reader is enriched with a secure, namespace-aware reader; other source kinds are returned
451     * as-is. Used by the TrAX and schema wrappers to route every source they parse through the secure SAX path.
452     * </p>
453     *
454     * @param source           The source to secure; never {@code null}.
455     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
456     * @return a secure source.
457     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader.
458     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
459     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
460     */
461    static Source secure(final Source source, final boolean overrideDefaultParser) {
462        if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
463            final InputSource inputSource = SAXSource.sourceToInputSource(source);
464            return inputSource == null ? source : new SAXSource(newXMLReader(overrideDefaultParser), inputSource);
465        }
466        return source;
467    }
468
469    /**
470     * Secures an existing {@link XMLReader}.
471     *
472     * @param reader The reader to secure; never {@code null}.
473     * @return A secure reader.
474     * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation.
475     */
476    static XMLReader secure(final XMLReader reader) {
477        if (reader instanceof SecureXMLReader) {
478            // Already secure (for example, a reader from a secure factory passed back through secure(XMLReader)); the floor is already in place.
479            return reader;
480        }
481        if (ANDROID_EXPAT_READER.equals(reader.getClass().getName())) {
482            // Expat ignores external fetches when no resolver is set; the ignore-all floor keeps that behavior non-bypassable (routing a caller-set resolver,
483            // including SAXParser.parse's handler, through it and resolving anything unresolved to empty) and, via SecureExpatXMLReader, rejects the
484            // unsupported namespace-prefixes feature eagerly rather than mid-parse.
485            return new SecureExpatXMLReader(reader);
486        }
487        // Required: enables the JDK XMLSecurityManager / Xerces SecurityManager limits.
488        setFeature(reader, XMLConstants.FEATURE_SECURE_PROCESSING, true);
489        // Required: SecureXMLReader installs an ignore-all EntityResolver floor on the reader.
490        // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* properties are needed here.
491        // Callers can chain their resolvers, but not override the floor.
492        return new SecureXMLReader(reader);
493    }
494
495    private static void setFeature(final SAXParserFactory factory, final String feature, final boolean value) {
496        try {
497            factory.setFeature(feature, value);
498        } catch (final Exception e) {
499            throw SecureException.featureFailed(feature, factory, e);
500        }
501    }
502
503    private static void setFeature(final XMLReader reader, final String feature, final boolean value) {
504        try {
505            reader.setFeature(feature, value);
506        } catch (final Exception e) {
507            throw SecureException.featureFailed(feature, reader, e);
508        }
509    }
510
511    private SecureSAXParserFactory() {
512        // static only
513    }
514}