Source code
Description: source: package: org.apache.commons.xml.secure, class: SecureTransformerFactory

    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.io.IOException;
    021import java.lang.invoke.MethodHandle;
    022import java.util.Objects;
    023import java.util.function.Supplier;
    024
    025import javax.xml.XMLConstants;
    026import javax.xml.parsers.DocumentBuilderFactory;
    027import javax.xml.parsers.FactoryConfigurationError;
    028import javax.xml.parsers.ParserConfigurationException;
    029import javax.xml.transform.ErrorListener;
    030import javax.xml.transform.Source;
    031import javax.xml.transform.Templates;
    032import javax.xml.transform.Transformer;
    033import javax.xml.transform.TransformerConfigurationException;
    034import javax.xml.transform.TransformerException;
    035import javax.xml.transform.TransformerFactory;
    036import javax.xml.transform.TransformerFactoryConfigurationError;
    037import javax.xml.transform.URIResolver;
    038import javax.xml.transform.dom.DOMSource;
    039import javax.xml.transform.sax.SAXSource;
    040import javax.xml.transform.sax.SAXTransformerFactory;
    041import javax.xml.transform.sax.TemplatesHandler;
    042import javax.xml.transform.sax.TransformerHandler;
    043import javax.xml.transform.stream.StreamSource;
    044
    045import org.w3c.dom.Document;
    046import org.xml.sax.InputSource;
    047import org.xml.sax.SAXException;
    048import org.xml.sax.XMLFilter;
    049import org.xml.sax.XMLReader;
    050
    051/**
    052 * Creates new, secure {@link TransformerFactory} instances.
    053 * <p>
    054 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}: {@code xsl:import}, {@code xsl:include} and {@code document()} URIs are not
    055 * resolved.
    056 * </p>
    057 * <p>
    058 * The guarantees govern what the transform reads, not what it writes: an output instruction like {@code xsl:result-document} still writes wherever the
    059 * stylesheet directs, so an untrusted stylesheet's output destinations must be restricted outside the library.
    060 * </p>
    061 * <p>
    062 * The guarantees apply to every parser the factory creates internally for the standard {@link TransformerFactory} entry points: stylesheet compilation
    063 * ({@link TransformerFactory#newTemplates(javax.xml.transform.Source) newTemplates(Source)},
    064 * {@link TransformerFactory#newTransformer(javax.xml.transform.Source) newTransformer(Source)}) and source-document reading at
    065 * {@code Transformer.transform(Source, Result)} time.
    066 * </p>
    067 * <p>
    068 * The {@code href} an {@code xml-stylesheet} processing instruction names is content of the document being scanned, so
    069 * {@link TransformerFactory#getAssociatedStylesheet(Source, String, String, String) getAssociatedStylesheet} treats it as any other content-named reference:
    070 * install a {@link URIResolver} resolving that href to compile the stylesheet it points at. Without one the returned {@link Source} carries empty content
    071 * rather than naming the URI, so compiling it cannot fetch a stylesheet the parsed document chose.
    072 * </p>
    073 * <p>
    074 * The {@link javax.xml.transform.sax.SAXTransformerFactory} extension methods ({@code newTransformerHandler(..)}, {@code newTemplatesHandler()},
    075 * {@code newXMLFilter(..)}), if reachable by casting the returned factory, produce objects carrying the same guarantees.
    076 * </p>
    077 * <p>
    078 * This class is not itself a {@link TransformerFactory}, so it inherits none of the static JAXP factory methods. A caller therefore cannot obtain an unsecured
    079 * factory through this class by calling a method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public wrapper
    080 * class.
    081 * </p>
    082 *
    083 * @see org.apache.commons.xml.secure
    084 */
    085public final class SecureTransformerFactory {
    086
    087    /**
    088     * {@link TransformerFactory} wrapper that rewrites every Source-taking entry point through {@link SecureSAXParserFactory#secure(Source, boolean)} before
    089     * delegating.
    090     *
    091     * <p>
    092     * Used by providers whose underlying TrAX implementation pulls a new {@code SAXParserFactory.newInstance()} for any Source that is not already a
    093     * {@link SAXSource} carrying its own {@link XMLReader}, and only sets {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING FSP} on the resulting reader.
    094     * Wrapping the factory and rewriting the Source upstream guarantees the parse runs through an {@link org.apache.commons.xml.secure}-secured reader instead.
    095     * </p>
    096     * <p>
    097     * Three layers cooperate:
    098     * </p>
    099     * <ol>
    100     *   <li>{@link SecureTransformerFactory} rewrites the Source on every entry point that compiles a stylesheet or transforms a one-shot input.</li>
    101     *   <li>{@link SecureTemplates} returns a {@link SecureTransformer} from {@link Templates#newTransformer()} so runtime source parsing is also covered, and
    102     *       restores the factory's URIResolver onto the produced Transformer (which the underlying implementation typically does not propagate through
    103     *       {@code Templates}).</li>
    104     *   <li>{@link SecureTransformer} rewrites the Source on every {@link Transformer#transform(Source, javax.xml.transform.Result)} call.</li>
    105     * </ol>
    106     * <p>
    107     * The {@link SAXTransformerFactory} extension products ride the same wrappers: {@code newTransformerHandler}/{@code newTemplatesHandler} products are
    108     * wrapped ({@link SecureTransformerHandler}, {@link SecureTemplatesHandler}) so the {@link Transformer}/{@link Templates} they expose carry the resolver
    109     * floor, and {@code newXMLFilter} returns a {@link SecureXMLFilter} composed from these wrappers instead of the implementation's filter, which would
    110     * self-provision an unsecured input reader.
    111     * </p>
    112     *
    113     * <h2>Caveats</h2>
    114     * <ul>
    115     *   <li>A {@link SAXSource} that carries its own {@link XMLReader} is trusted as-is: the caller is expected to supply a secure reader (via
    116     *       {@link SecureSAXParserFactory#newInstance()}) in that case. The same applies to the SAX events a caller feeds into a handler, and to a parent reader a
    117     *       caller sets on a returned {@link XMLFilter}. The exception is {@code getAssociatedStylesheet} on an engine that drops the reader (Apache Xalan, and
    118     *       the JDK's XSLTC on Java 8): there the document is pre-parsed into a DOM instead, since the reader would otherwise be replaced by the engine's own.</li>
    119     * </ul>
    120     */
    121    private static final class Wrapper extends SAXTransformerFactory {
    122
    123        /**
    124         * Whether the delegate is Apache Xalan (either its interpretive or its XSLTC factory), whose {@code getAssociatedStylesheet} ignores a SAXSource reader.
    125         *
    126         * @param factory The delegate factory.
    127         * @return Whether the delegate is an {@code org.apache.xalan.} implementation.
    128         */
    129        private static boolean isXalan(final SAXTransformerFactory factory) {
    130            return factory.getClass().getName().startsWith("org.apache.xalan.");
    131        }
    132
    133        /**
    134         * Whether the delegate recognizes {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER}, probed with a same-value {@code setFeature}:
    135         * {@code TransformerFactory.getFeature} cannot signal an unrecognized name (it returns {@code false}), while every implementation rejects a
    136         * {@code setFeature} for a name it does not support (Xalan with {@link TransformerConfigurationException}, Saxon with its own unchecked exception).
    137         *
    138         * @param factory The delegate factory.
    139         * @return Whether the delegate recognizes the feature.
    140         */
    141        private static boolean probeOverrideDefaultParser(final SAXTransformerFactory factory) {
    142            try {
    143                factory.setFeature(SecureSAXParserFactory.OVERRIDE_DEFAULT_PARSER,
    144                        factory.getFeature(SecureSAXParserFactory.OVERRIDE_DEFAULT_PARSER));
    145                return true;
    146            } catch (final Exception e) {
    147                return false;
    148            }
    149        }
    150
    151        private static Templates unwrap(final Templates templates) {
    152            return templates instanceof SecureTemplates ? ((SecureTemplates) templates).getDelegate() : templates;
    153        }
    154
    155        private final SAXTransformerFactory delegate;
    156
    157        /**
    158         * Empty-{@link Source} supplier for the resolver floor, threaded onto every produced Templates/Transformer; {@code null} means the default empty DOM.
    159         */
    160        private final Supplier<Source> emptySource;
    161
    162        private final FallbackIgnoreURIResolver floor;
    163
    164        /** Whether the delegate recognizes {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER}; its value is read per created product, like the JDK. */
    165        private final boolean supportsOverrideDefaultParser;
    166
    167        /**
    168         * Constructs a new instance.
    169         *
    170         * @param delegate The delegate to wrap; must not be {@code null}.
    171         * @throws NullPointerException Thrown if {@code delegate} is {@code null}.
    172         */
    173        private Wrapper(final SAXTransformerFactory delegate) {
    174            this(delegate, null);
    175        }
    176
    177        /**
    178         * Constructs a new instance.
    179         *
    180         * @param delegate    The delegate to wrap; must not be {@code null}.
    181         * @param emptySource The empty-{@link Source} supplier for the resolver floor, threaded onto every produced Templates/Transformer; {@code null} means the
    182         *                    default empty DOM.
    183         * @throws NullPointerException Thrown if {@code delegate} is {@code null}.
    184         */
    185        private Wrapper(final SAXTransformerFactory delegate, final Supplier<Source> emptySource) {
    186            this.delegate = Objects.requireNonNull(delegate, "delegate");
    187            this.emptySource = emptySource;
    188            this.supportsOverrideDefaultParser = probeOverrideDefaultParser(delegate);
    189            this.floor = new FallbackIgnoreURIResolver(null, emptySource, this::overrideDefaultParser);
    190            // Compile-time block for xsl:import/xsl:include and document(); a caller-set resolver is routed through the floor rather than replacing it.
    191            delegate.setURIResolver(floor);
    192        }
    193
    194        /**
    195         * Routes the href an {@code xml-stylesheet} PI yielded through the floor, so a URI distilled from untrusted content is opted in by the caller's
    196         * resolver or resolved to empty like any other content-named reference.
    197         *
    198         * <p>XSLTC-lineage engines resolve the href during the scan, before they install the factory's {@link URIResolver}, and hand back a live
    199         * {@link SAXSource} naming the absolutized URI; compiling it, the one documented use of this method, would then fetch it. Saxon already floors the href
    200         * itself and returns an empty source, so flooring here is also what makes the engines agree.</p>
    201         *
    202         * @param associated The delegate's result; {@code null} when no PI matched.
    203         * @param base       The system id of the scanned document, the base the href was resolved against.
    204         * @return The caller resolver's source for an opted-in href, an empty source otherwise, or {@code null} when no PI matched.
    205         * @throws TransformerConfigurationException Thrown if the floor rejects the href, which it does when {@value SecureException#THROW_ON_UNRESOLVED} is set.
    206         */
    207        private Source floorAssociated(final Source associated, final String base) throws TransformerConfigurationException {
    208            if (associated == null || associated.getSystemId() == null) {
    209                // No PI matched, or the engine already floored the href to a source that names no URI.
    210                return associated;
    211            }
    212            try {
    213                return floor.resolve(associated.getSystemId(), base);
    214            } catch (final TransformerException e) {
    215                throw new TransformerConfigurationException("Failed to resolve the associated stylesheet " + associated.getSystemId(), e);
    216            }
    217        }
    218
    219        /**
    220         * {@inheritDoc}
    221         *
    222         * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    223         *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    224         */
    225        @Override
    226        public Source getAssociatedStylesheet(final Source source, final String media, final String title, final String charset)
    227                throws TransformerConfigurationException {
    228            // Xalan's getAssociatedStylesheet drops a SAXSource's reader and self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849), and the
    229            // JDK's XSLTC did the same before 8u162; hand those a DOM so no parser but ours ever sees the document.
    230            final Source secure = isXalan(delegate) || JAVA_8 ? secureSourceToDom(source) : SecureSAXParserFactory.secure(source, overrideDefaultParser());
    231            return floorAssociated(delegate.getAssociatedStylesheet(secure, media, title, charset), secure.getSystemId());
    232        }
    233
    234        @Override
    235        public Object getAttribute(final String name) {
    236            return delegate.getAttribute(name);
    237        }
    238
    239        @Override
    240        public ErrorListener getErrorListener() {
    241            return delegate.getErrorListener();
    242        }
    243
    244        @Override
    245        public boolean getFeature(final String name) {
    246            return delegate.getFeature(name);
    247        }
    248
    249        @Override
    250        public URIResolver getURIResolver() {
    251            return floor.getDelegate();
    252        }
    253
    254        /**
    255         * {@inheritDoc}
    256         *
    257         * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    258         *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    259         */
    260        @Override
    261        public Templates newTemplates(final Source source) throws TransformerConfigurationException {
    262            // newTemplates() should never return null for a specification-compliant factory.
    263            final Templates templates = delegate.newTemplates(SecureSAXParserFactory.secure(source, overrideDefaultParser()));
    264            return templates == null ? null : new SecureTemplates(templates, getURIResolver(), emptySource, overrideDefaultParser());
    265        }
    266
    267        @Override
    268        public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
    269            // newTemplatesHandler() should never return null for a specification-compliant factory.
    270            final TemplatesHandler handler = delegate.newTemplatesHandler();
    271            return handler == null ? null : new SecureTemplatesHandler(handler, getURIResolver(), emptySource, overrideDefaultParser());
    272        }
    273
    274        @Override
    275        public Transformer newTransformer() throws TransformerConfigurationException {
    276            // Identity transformer: still parses runtime sources, so wrap it to secure Transformer.transform(Source, Result).
    277            // newTransformer() should never return null for a specification-compliant factory.
    278            final Transformer transformer = delegate.newTransformer();
    279            return transformer == null ? null : new SecureTransformer(transformer, getURIResolver(), emptySource, overrideDefaultParser());
    280        }
    281
    282        /**
    283         * {@inheritDoc}
    284         *
    285         * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    286         *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    287         */
    288        @Override
    289        public Transformer newTransformer(final Source source) throws TransformerConfigurationException {
    290            // newTransformer() should never return null for a specification-compliant factory.
    291            final Transformer transformer = delegate.newTransformer(SecureSAXParserFactory.secure(source, overrideDefaultParser()));
    292            return transformer == null ? null : new SecureTransformer(transformer, getURIResolver(), emptySource, overrideDefaultParser());
    293        }
    294
    295        @Override
    296        public TransformerHandler newTransformerHandler() throws TransformerConfigurationException {
    297            return secure(delegate.newTransformerHandler());
    298        }
    299
    300        /**
    301         * {@inheritDoc}
    302         *
    303         * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    304         *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    305         */
    306        @Override
    307        public TransformerHandler newTransformerHandler(final Source source) throws TransformerConfigurationException {
    308            return secure(delegate.newTransformerHandler(SecureSAXParserFactory.secure(source, overrideDefaultParser())));
    309        }
    310
    311        @Override
    312        public TransformerHandler newTransformerHandler(final Templates templates) throws TransformerConfigurationException {
    313            // Implementations cast templates.newTransformer() to their own Transformer type, so hand them the wrapped implementation Templates, not the wrapper.
    314            return secure(delegate.newTransformerHandler(unwrap(templates)));
    315        }
    316
    317        /**
    318         * {@inheritDoc}
    319         *
    320         * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    321         *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    322         */
    323        @Override
    324        public XMLFilter newXMLFilter(final Source source) throws TransformerConfigurationException {
    325            final Templates templates = newTemplates(source);
    326            return templates == null ? null : new SecureXMLFilter((SecureTemplates) templates);
    327        }
    328
    329        @Override
    330        public XMLFilter newXMLFilter(final Templates templates) throws TransformerConfigurationException {
    331            return new SecureXMLFilter(templates instanceof SecureTemplates ? (SecureTemplates) templates
    332                    : new SecureTemplates(templates, getURIResolver(), emptySource, overrideDefaultParser()));
    333        }
    334
    335        /**
    336         * Tests whether parsers should be instantiated via {@code newInstance()} instead of {@code newDefaultInstance()}.
    337         *
    338         * <p>The JDK implementation of {@link TransformerFactory} uses the JDK parsers while {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} is unset
    339         * or {@code false}.</p>
    340         *
    341         * @return {@code true} if parsers should be created via {@code newInstance()}.
    342         */
    343        private boolean overrideDefaultParser() {
    344            return !supportsOverrideDefaultParser || delegate.getFeature(SecureSAXParserFactory.OVERRIDE_DEFAULT_PARSER);
    345        }
    346
    347        private TransformerHandler secure(final TransformerHandler handler) {
    348            return handler == null ? null : new SecureTransformerHandler(handler, getURIResolver(), emptySource, overrideDefaultParser());
    349        }
    350
    351        /**
    352         * Parses a stream or SAX source into a DOM through a secure, namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link DOMSource}
    353         * carrying its system id, so the consumer walks the tree instead of provisioning its own reader. Any other source is left to
    354         * {@link SecureSAXParserFactory#secure(Source, boolean)}.
    355         *
    356         * <p>A {@link SAXSource} carrying the caller's own reader is pre-parsed here too, unlike everywhere else in this class: an engine that reaches this
    357         * method drops that reader anyway, so honoring it is not among the options — the choice is only between this parse and the engine's unsecured one.</p>
    358         *
    359         * @param source The source to scan for an associated stylesheet.
    360         * @return A {@link DOMSource} for a stream or SAX source, otherwise the result of {@link SecureSAXParserFactory#secure(Source, boolean)}.
    361         * @throws TransformerConfigurationException Thrown if the source cannot be parsed.
    362         * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    363         *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    364         * @throws SecureException Thrown if a (non-Android) factory cannot support the secure processing feature {@link XMLConstants#FEATURE_SECURE_PROCESSING}.
    365         */
    366        private Source secureSourceToDom(final Source source) throws TransformerConfigurationException {
    367            if (source instanceof StreamSource || source instanceof SAXSource) {
    368                final InputSource inputSource = SAXSource.sourceToInputSource(source);
    369                if (inputSource != null) {
    370                    try {
    371                        final DocumentBuilderFactory factory = SecureDocumentBuilderFactory.newNSInstance(overrideDefaultParser());
    372                        final Document document = factory.newDocumentBuilder().parse(inputSource);
    373                        return new DOMSource(document, inputSource.getSystemId());
    374                    } catch (final ParserConfigurationException | SAXException | IOException e) {
    375                        throw new TransformerConfigurationException("Failed to parse the source for associated-stylesheet lookup", e);
    376                    }
    377                }
    378            }
    379            return SecureSAXParserFactory.secure(source, overrideDefaultParser());
    380        }
    381
    382        @Override
    383        public void setAttribute(final String name, final Object value) {
    384            delegate.setAttribute(name, value);
    385        }
    386
    387        @Override
    388        public void setErrorListener(final ErrorListener listener) {
    389            delegate.setErrorListener(listener);
    390        }
    391
    392
    393        @Override
    394        public void setFeature(final String name, final boolean value) throws TransformerConfigurationException {
    395            delegate.setFeature(name, value);
    396        }
    397
    398        @Override
    399        public void setURIResolver(final URIResolver resolver) {
    400            floor.setDelegate(resolver);
    401        }
    402    }
    403
    404    /** Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. */
    405    private static final String JDK_TRANSFORMER_FACTORY = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
    406
    407    private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(TransformerFactory.class, "newDefaultInstance");
    408
    409    /**
    410     * {@code true} on Java 8, detected by the absence of {@code TransformerFactory.newDefaultInstance()}, which arrived in Java 9.
    411     * <p>
    412     * The JDK's XSLTC only began honoring the {@link XMLReader} carried by a {@link SAXSource} in {@code getAssociatedStylesheet} in 8u162; through 8u152 it
    413     * provisions its own parser, exactly as Apache Xalan does. Java 8 as a whole is used as the boundary rather than the patch level: the two are
    414     * indistinguishable through any API, and such an old runtime has already chosen correctness of configuration over the cost of a DOM pre-parse.
    415     * </p>
    416     */
    417    private static final boolean JAVA_8 = MH_newDefaultInstance == null;
    418
    419    /**
    420     * Returns a new, secure {@link TransformerFactory} of the system-default implementation.
    421     * <p>
    422     * Obtained from {@code TransformerFactory.newDefaultInstance()} where the platform provides it (Java 9 or later), and by instantiating the JDK's built-in
    423     * implementation directly on Java 8.
    424     * </p>
    425     *
    426     * @return A secure factory.
    427     * @throws IllegalStateException                Thrown if a required secure setting cannot be applied to the underlying implementation.
    428     * @throws TransformerFactoryConfigurationError Thrown if the running platform provides neither {@code newDefaultInstance()} nor the JDK's built-in
    429     *                                                implementation (for example Android).
    430     */
    431    public static TransformerFactory newDefaultInstance() {
    432        if (MH_newDefaultInstance != null) {
    433            return secure(MethodHandleFactory.invokeExact(() -> (TransformerFactory) MH_newDefaultInstance.invokeExact(), TransformerFactoryConfigurationError.class));
    434        }
    435        // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead. Where that class does not exist either (for
    436        // example Android), the lookup miss surfaces as TransformerFactoryConfigurationError, like any newInstance miss.
    437        return newInstance(JDK_TRANSFORMER_FACTORY, null);
    438    }
    439
    440    /**
    441     * Returns a new, secure {@link TransformerFactory}.
    442     *
    443     * @return A secure factory.
    444     * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation.
    445     */
    446    public static TransformerFactory newInstance() {
    447        return secure(TransformerFactory.newInstance());
    448    }
    449
    450    /**
    451     * Returns a new, secure {@link TransformerFactory} of the given implementation class.
    452     *
    453     * @param factoryClassName The fully qualified class name of the {@link TransformerFactory} implementation.
    454     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
    455     * @return A secure factory.
    456     * @throws IllegalStateException                Thrown if a required secure setting cannot be applied to the underlying implementation.
    457     * @throws TransformerFactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
    458     */
    459    public static TransformerFactory newInstance(final String factoryClassName, final ClassLoader classLoader) {
    460        return secure(TransformerFactory.newInstance(factoryClassName, classLoader));
    461    }
    462
    463    /**
    464     * Capability-driven secure for any {@link TransformerFactory} on the classpath.
    465     *
    466     * <p>
    467     * Rather than branching on the implementation class, this method probes what the factory supports and adapts:
    468     * </p>
    469     * <ul>
    470     *     <li><strong>Saxon</strong> ({@code net.sf.saxon}): recognized by package prefix and handed to {@link SaxonProvider#configure(TransformerFactory)} for the
    471     *         channels the standard JAXP knobs cannot close (reflection-based extension functions, the collection finder, the internal SAX parser). It is then
    472     *         wrapped like every other implementation to install the {@link FallbackIgnoreURIResolver} floor; the only
    473     *         difference is the empty-{@link Source} shape the floor returns, {@code EmptySource} for Saxon rather than the default empty DOM document.</li>
    474     *     <li><strong>FSP</strong> ({@link XMLConstants#FEATURE_SECURE_PROCESSING}): required. On XSLTC it enables the runtime evaluator limits; on Xalan it disables
    475     *         reflection-based extension functions.</li>
    476     *     <li><strong>{@link FallbackIgnoreURIResolver} floor</strong>: required. An ignore-all {@link URIResolver} floor, installed by
    477     *         the nested wrapper and carried onto every produced {@link Transformer}, resolves {@code xsl:import}/{@code xsl:include} at compile
    478     *         time and {@code document()} at runtime to an empty document, the one channel both XSLTC and Xalan route through. A caller-set {@link URIResolver} is
    479     *         routed through the floor rather than replacing it, so a caller can opt a specific URI in but cannot reopen the fetch.</li>
    480     *     <li><strong>The nested wrapper</strong>: required. Both implementations fall back to {@code SAXParserFactory.newInstance()} to parse a
    481     *         stylesheet or source document that does not carry its own reader, and only set FSP on it; wrapping the factory rewrites every {@link Source} through an
    482     *         {@link org.apache.commons.xml.secure}-secured reader instead.</li>
    483     * </ul>
    484     *
    485     * @param factory The factory to secure; never {@code null}.
    486     * @return a secure factory.
    487     */
    488    static TransformerFactory secure(final TransformerFactory factory) {
    489        // Required: enables secure processing (XSLTC runtime limits; Xalan's extension-function block).
    490        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
    491        if (SaxonProvider.isSaxon(factory.getClass())) {
    492            // Saxon keeps its vendor Configuration for the channels JAXP cannot close,
    493            // then goes through the same wrapper as every other implementation for the URIResolver floor;
    494            // EmptySource is the empty-source shape Saxon's consumers expect.
    495            return new Wrapper((SAXTransformerFactory) SaxonProvider.configure(factory), SaxonProvider.emptySourceSupplier());
    496        }
    497        // Required: source/stylesheet parsing provisions its own SAX reader otherwise; the wrapper routes every Source through a secure one and installs the
    498        // ignore-all URIResolver floor (blocking xsl:import/include at compile time and document() at runtime) that a caller-set resolver cannot remove.
    499        return new Wrapper((SAXTransformerFactory) factory);
    500    }
    501
    502    private static void setFeature(final TransformerFactory factory, final String feature, final boolean value) {
    503        try {
    504            factory.setFeature(feature, value);
    505        } catch (final Exception e) {
    506            throw SecureException.featureFailed(feature, factory, e);
    507        }
    508    }
    509
    510    private SecureTransformerFactory() {
    511        // static only
    512    }
    513}