Resolvers.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 java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.EntityResolver2;
/**
* Stateless resolver singletons that fix the outcome of every external lookup.
*
* <p>Two flavours are exposed, each as a typed singleton field per resolver interface:</p>
* <ul>
* <li>{@link DenyAll} refuses every lookup with an exception. Use this on schema/XSLT compile paths and on parser entity hooks where any external fetch is
* a hardening violation.</li>
* <li>{@link IgnoreAll} returns an empty input. Use this on Woodstox's DTD-subset and undeclared-entity hooks where the parse must continue with no
* replacement content.</li>
* </ul>
*
* <p>{@link XMLResolver} and {@link EntityResolver2} both declare a 4-arg {@code resolveEntity(String, String, String, String)} with identical erasure but
* different parameter semantics, return types ({@link Object} vs {@link InputSource}) and throws clauses ({@link XMLStreamException} vs {@link SAXException}),
* so they cannot coexist on the same class. Each flavour therefore exposes its {@code XMLResolver} and {@code EntityResolver2} singletons separately.</p>
*/
final class Resolvers {
/**
* Refuses every external resource lookup with an exception.
*
* <p>The single-method resolvers ({@link LSResourceResolver}, {@link URIResolver}, {@link XMLResolver}) are exposed as lambdas; {@link EntityResolver2}
* declares three methods, so it lives in a private nested class.</p>
*/
static final class DenyAll {
/**
* {@link EntityResolver2}: refuses every external entity lookup performed by a SAX or DOM parser.
*/
private static final class DenyAllEntityResolver2 implements EntityResolver2 {
private DenyAllEntityResolver2() {
}
@Override
public InputSource getExternalSubset(final String name, final String baseURI) {
// Canonical EntityResolver2 "no synthetic subset" signal; matches the behaviour of an absent resolver. Blocking happens in resolveEntity below.
return null;
}
@Override
public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException {
throw new SAXException(forbiddenMessage(null, null, publicId, systemId, null));
}
@Override
public InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId) throws SAXException {
throw new SAXException(forbiddenMessage(name, null, publicId, systemId, baseURI));
}
}
/**
* Refuses every external entity lookup performed by a SAX or DOM parser, including the external DTD subset.
*/
static final EntityResolver2 ENTITY2 = new DenyAllEntityResolver2();
/**
* Refuses every {@code xs:import}/{@code xs:include}/{@code xs:redefine} lookup at schema-compile time.
*/
static final LSResourceResolver LS_RESOURCE = (type, namespaceURI, publicId, systemId, baseURI) -> {
throw new SecurityException(forbiddenMessage(type, namespaceURI, publicId, systemId, baseURI));
};
/**
* Refuses every {@code xsl:import}/{@code xsl:include}/{@code document()} lookup during XSLT compile and transform.
*/
static final URIResolver URI = (href, base) -> {
throw new TransformerException(forbiddenMessage("uri", null, null, href, base));
};
/**
* Refuses every external entity lookup performed by a StAX parser.
*/
static final XMLResolver XML = (publicID, systemID, baseURI, namespace) -> {
throw new XMLStreamException(forbiddenMessage(null, namespace, publicID, systemID, baseURI));
};
private DenyAll() {
}
}
/**
* Returns an empty input for every external resource lookup so the parse can continue without replacement content.
*
* <p>Only an {@link XMLResolver} flavour is exposed: schema and XSLT compile paths must always deny imports, and SAX/DOM use the deny-all hooks plus
* {@link AndroidProvider}'s subset-aware resolver where needed.</p>
*/
static final class IgnoreAll {
/**
* Empty {@link ByteArrayInputStream} shared across every call. {@code read()} on a zero-length array always returns {@code -1}, so reusing the
* instance is safe even though the type is technically stateful.
*/
private static final InputStream EMPTY = new ByteArrayInputStream(new byte[0]);
/**
* Returns an empty input for every external entity lookup performed by a StAX parser.
*/
static final XMLResolver XML = (publicID, systemID, baseURI, namespace) -> EMPTY;
private IgnoreAll() {
}
}
private static String forbiddenMessage(final String type, final String namespace, final String publicId, final String systemId, final String baseURI) {
return String.format("External resource fetch forbidden by hardening: type=%s, namespace=%s, publicId=%s, systemId=%s, baseURI=%s", type, namespace,
publicId, systemId, baseURI);
}
private Resolvers() {
}
}