SaxonProvider.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 javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.XMLReader;
import net.sf.saxon.Configuration;
import net.sf.saxon.jaxp.SaxonTransformerFactory;
import net.sf.saxon.lib.Feature;
import net.sf.saxon.xpath.XPathFactoryImpl;
/**
* Hardening recipes for Saxon-HE ({@code net.sf.saxon:Saxon-HE}).
*
* <p>Saxon supplies {@link TransformerFactory} and {@link XPathFactory} implementations; it does not ship a DOM, SAX, StAX or Schema factory of its own.</p>
*/
final class SaxonProvider {
/**
* A Saxon {@link Configuration} that locks down every channel through which Saxon would otherwise reach external resources.
*
* <p>Three layers of restriction are applied:</p>
*
* <ol>
* <li><b>SAX layer.</b> {@link #makeParser} hands every {@link XMLReader} Saxon would otherwise use through
* {@link XmlFactories#harden(XMLReader)}, which routes it to the matching bundled hardening recipe. DOCTYPE, external entities and XInclude
* are refused at parse time.</li>
* <li><b>URI-resolution layer.</b> {@link Feature#ALLOWED_PROTOCOLS} is set to the empty string. This blocks XSLT inclusions {@code xsl:include},
* {@code xsl:import}, {@code xsl:source-document}, and the XPath/XSLT functions {@code fn:doc}, {@code fn:document}, {@code fn:unparsed-text},
* {@code fn:collection}, {@code fn:json-doc} and {@code fn:transform}.</li>
* <li><b>Extension-function layer.</b> {@link Feature#ALLOW_EXTERNAL_FUNCTIONS} is disabled, so reflection-based extension calls cannot be used to
* sidestep the URI restrictions.</li>
* </ol>
*/
private static class HardenedConfiguration extends Configuration {
private HardenedConfiguration() {
// Extension-function layer: turn off Saxon's reflection-based extension calls. Without this an attacker could bypass URI restrictions through
// user-supplied Java extensions.
setBooleanProperty(Feature.ALLOW_EXTERNAL_FUNCTIONS, false);
// URI-resolution layer: empty string disallows every URI scheme. Saxon front-ends the existing ResourceResolver with a ProtocolRestrictor; a
// later setResourceResolver call would cancel this filter, so this stays last in the constructor.
setConfigurationProperty(Feature.ALLOWED_PROTOCOLS, "");
// Use the parser below for both style and source:
setStyleParserClass("#DEFAULT");
setSourceParserClass("#DEFAULT");
}
/**
* Saxon's hook for instantiating a new SAX parser.
*/
@Override
public XMLReader makeParser(final String className) throws TransformerFactoryConfigurationError {
try {
return XmlFactories.harden(super.makeParser(className));
} catch (final HardeningException e) {
throw new TransformerFactoryConfigurationError(e);
}
}
}
private static class SaxonProviderConfigurer {
private static TransformerFactory configure(final TransformerFactory factory) {
((SaxonTransformerFactory) factory).setConfiguration(new HardenedConfiguration());
return factory;
}
private static XPathFactory configure(final XPathFactory factory) {
((XPathFactoryImpl) factory).setConfiguration(new HardenedConfiguration());
return factory;
}
}
static TransformerFactory configure(final TransformerFactory factory) {
try {
return SaxonProviderConfigurer.configure(factory);
} catch (LinkageError e) {
// Unlikely, but protects method execution from missing optional dependency
throw new IllegalStateException(e);
}
}
static XPathFactory configure(final XPathFactory factory) {
try {
return SaxonProviderConfigurer.configure(factory);
} catch (LinkageError e) {
// Unlikely, but protects method execution from missing optional dependency
throw new IllegalStateException(e);
}
}
private SaxonProvider() {
}
}