XalanProvider.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.xml;

import static org.apache.commons.xml.JaxpSetters.setFeature;

import javax.xml.XMLConstants;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.xpath.XPathFactory;

/**
 * Hardening recipes for the external Apache Xalan distribution (the {@code xalan:xalan} artifact).
 *
 * <p>Factory classes live under the {@code org.apache.xalan.*} and {@code org.apache.xpath.*} packages. Xalan ships only TrAX and XPath; its DOM, SAX, StAX and
 * Schema needs are served by whatever JDK or external Xerces is on the classpath.</p>
 *
 * <p>Hardening recipe applied to every factory below uses the same building blocks:</p>
 * <ul>
 *     <li><strong>FSP</strong> ({@link XMLConstants#FEATURE_SECURE_PROCESSING}, set to {@code true}): enables Xalan's secure-processing mode, which disables
 *         reflection-based extension functions. Required.</li>
 *     <li><strong>{@link Resolvers.DenyAll#URI}</strong>: required. Xalan does not implement the JAXP 1.5 {@code ACCESS_EXTERNAL_*} attributes; a deny-all
 *         {@link javax.xml.transform.URIResolver} blocks {@code xsl:include}, {@code xsl:import}, {@code xsl:source-document} during stylesheet compilation
 *         and {@code document()}, {@code unparsed-text()}, {@code collection()} at runtime.</li>
 *     <li><strong>{@link HardeningTransformerFactory} + {@link HardeningTemplates} + {@link HardeningTransformer}</strong>: required. Xalan's source-document
 *         parsing path falls back to {@code SAXParserFactory.newInstance()} whenever the input is not a {@link javax.xml.transform.dom.DOMSource} or a
 *         {@link javax.xml.transform.sax.SAXSource} carrying its own {@link org.xml.sax.XMLReader}; it only sets FSP on the resulting reader. The wrappers
 *         rewrite every Source through an {@link XmlFactories}-hardened reader so the Xalan internals never get to provision their own.</li>
 * </ul>
 *
 * <h2>Caveats</h2>
 * <ul>
 *     <li>Replacing the {@code URIResolver} on the factory or transformer cancels the deny-all hardening for XSLT URI fetches; Xalan exposes no
 *         tamper-resistant equivalent of JAXP 1.5 {@code ACCESS_EXTERNAL_STYLESHEET}.</li>
 * </ul>
 */
final class XalanProvider {

    static TransformerFactory configure(final TransformerFactory factory) {
        // Required: enables Xalan's secure-processing mode (extension-function block)
        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
        // Required: Xalan does not honour JAXP 1.5 ACCESS_EXTERNAL_*; the deny-all URIResolver blocks:
        // xsl:import/xsl:include at compile time and document()/unparsed-text() at runtime.
        factory.setURIResolver(Resolvers.DenyAll.URI);
        // Required: Xalan's internal SAX reader is sourced from SAXParserFactory.newInstance() and only carries FSP.
        // We replace it with our hardened factory
        return new HardeningTransformerFactory((SAXTransformerFactory) factory);
    }

    static XPathFactory configure(final XPathFactory factory) {
        // Required: enables Xalan's secure-processing mode; XPathFactory has no property API for finer control.
        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
        return factory;
    }

    private XalanProvider() {
    }
}