org.xmlresolver.Resolver Maven / Gradle / Ivy
Show all versions of xmlresolver Show documentation
package org.xmlresolver;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.EntityResolver2;
import org.xmlresolver.logging.AbstractLogger;
import org.xmlresolver.logging.ResolverLogger;
import org.xmlresolver.sources.ResolverInputSource;
import org.xmlresolver.sources.ResolverLSInput;
import org.xmlresolver.sources.ResolverSAXSource;
import org.xmlresolver.utils.URIUtils;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.util.HashSet;
/** An implementation of many resolver interfaces.
*
* This class is probably the most common entry point to the XML Catalog resolver. It has a zero
* argument constructor so it can be instantiated directly from its class name (for example, passed to
* an application as a commend line argument or stored in a configuration file). When instantiated
* this way, it will automatically be configured by system properties and an xmlresolver.properties
* configuration file, if one exists.
*
* This class implements the {@link org.xml.sax.EntityResolver}, {@link org.xml.sax.ext.EntityResolver2},
* {@link LSResourceResolver}
* and {@link org.xmlresolver.NamespaceResolver}, and {@link javax.xml.transform.URIResolver} interfaces.
*
* The StAX {@link javax.xml.stream.XMLResolver} interface is implemented by the
* {@link org.xmlresolver.StAXResolver} class because the resolveEntity
method
* of the XMLResolver
interface isn't compatible with the EntityResolver2
* method of the same name.
*
* @see org.xmlresolver.StAXResolver
*/
public class Resolver implements URIResolver, EntityResolver, EntityResolver2, NamespaceResolver, LSResourceResolver {
public static final String PURPOSE_SCHEMA_VALIDATION = "http://www.rddl.org/purposes#schema-validation";
public static final String NATURE_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
public static final String NATURE_XML_SCHEMA_1_1 = "http://www.w3.org/2001/XMLSchema/v1.1";
public static final String NATURE_RELAX_NG = "http://relaxng.org/ns/structure/1.0";
private final ResolverLogger logger;
protected final XMLResolverConfiguration config;
protected final CatalogResolver resolver;
/** Creates a new instance of Resolver.
*
* The default resolver is a new ResourceResolver that uses a static catalog shared by all threads.
*/
public Resolver() {
config = new XMLResolverConfiguration();
resolver = new CatalogResolver(config);
logger = config.getFeature(ResolverFeature.RESOLVER_LOGGER);
}
/** Creates a new instance of a Resolver.
*
* Creates a resolver using a specific Catalog.
*
* @param config The configuration to use.
*/
public Resolver(XMLResolverConfiguration config) {
this.config = config;
resolver = new CatalogResolver(config);
logger = config.getFeature(ResolverFeature.RESOLVER_LOGGER);
}
/** Creates a new instance of a Resolver.
*
* Creates a resolver using a specific underlying ResourceResolver.
*
* @param resolver The resource resolver to use.
*/
public Resolver(CatalogResolver resolver) {
config = resolver.getConfiguration();
this.resolver = resolver;
logger = config.getFeature(ResolverFeature.RESOLVER_LOGGER);
}
/** What version is this?
*
* Returns the version number of this resolver instance.
*
* @return The version number
*/
public static String version() {
return BuildConfig.VERSION;
}
/** Get the Catalog used by this resolver.
*
* @return The underlying catalog.
*/
public XMLResolverConfiguration getConfiguration() {
return resolver.getConfiguration();
}
/** Get the underlying {@link CatalogResolver} used by this resolver.
* @return The catalog resolver.
*/
public CatalogResolver getCatalogResolver() {
return resolver;
}
/** Implements the {@link javax.xml.transform.URIResolver} interface. */
@Override
public Source resolve(String href, String base) throws TransformerException {
ResolvedResource rsrc = resolver.resolveURI(href, base);
if (rsrc == null) {
if (href == null || !config.getFeature(ResolverFeature.ALWAYS_RESOLVE)) {
return null;
}
try {
URI uri = base == null ? null : new URI(base);
// Don't resolve a same-document reference, that will trim the end off the URI.
// The best we can say about a same-document reference is that it refers to whatever
// the current base URI is...
if (!"".equals(href)) {
uri = uri == null ? new URI(href) : uri.resolve(href);
}
rsrc = openConnection(uri, false);
} catch (URISyntaxException | IOException | IllegalArgumentException ex) {
if (resolver.getConfiguration().getFeature(ResolverFeature.THROW_URI_EXCEPTIONS)) {
throw new TransformerException(ex);
}
return null;
}
if (rsrc == null) {
return null;
}
}
ResolverSAXSource source = new ResolverSAXSource(rsrc);
source.setSystemId(rsrc.getResolvedURI().toString());
return source;
}
/** Implements the {@link org.w3c.dom.ls.LSResourceResolver} interface. */
@Override
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
ResolvedResource rsrc = null;
if (type == null || "http://www.w3.org/TR/REC-xml".equals(type)) {
logger.log(AbstractLogger.REQUEST, "resolveResource: XML: %s (baseURI: %s, publicId: %s)",
systemId, baseURI, publicId);
rsrc = resolver.resolveEntity(null, publicId, systemId, baseURI);
} else {
logger.log(AbstractLogger.REQUEST, "resolveResource: %s, %s (namespace: %s, baseURI: %s, publicId: %s)",
type, systemId, namespaceURI, baseURI, publicId);
String purpose = null;
// If it looks like it's going to be used for validation, ...
if (NATURE_XML_SCHEMA.equals(type)
|| NATURE_XML_SCHEMA_1_1.equals(type)
|| NATURE_RELAX_NG.equals(type)) {
purpose = PURPOSE_SCHEMA_VALIDATION;
}
if(systemId != null ) {
rsrc = resolver.resolveNamespace(systemId, baseURI, type, purpose);
}
}
if (rsrc == null) {
return null;
}
return new ResolverLSInput(rsrc, publicId);
}
/** Implements the {@link org.xml.sax.ext.EntityResolver2} interface. */
@Override
public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException {
ResolvedResource rsrc = resolver.resolveEntity(name, null, null, baseURI);
if (rsrc == null) {
return null;
}
ResolverInputSource source = new ResolverInputSource(rsrc);
source.setSystemId(rsrc.getResolvedURI().toString());
return source;
}
/** Implements the {@link org.xml.sax.ext.EntityResolver2} interface. */
@Override
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException {
ResolvedResource rsrc = resolver.resolveEntity(name, publicId, systemId, baseURI);
if (rsrc == null) {
if (systemId == null || !config.getFeature(ResolverFeature.ALWAYS_RESOLVE)) {
return null;
}
rsrc = openConnection(systemId, baseURI, true);
if (rsrc == null) {
return null;
}
}
ResolverInputSource source = new ResolverInputSource(rsrc);
source.setSystemId(rsrc.getResolvedURI().toString());
return source;
}
/** Implements the {@link org.xml.sax.EntityResolver} interface. */
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
ResolvedResource rsrc = resolver.resolveEntity(null, publicId, systemId, null);
if (rsrc == null) {
if (systemId == null || !config.getFeature(ResolverFeature.ALWAYS_RESOLVE)) {
return null;
}
rsrc = openConnection(systemId, null, true);
if (rsrc == null) {
return null;
}
}
ResolverInputSource source = new ResolverInputSource(rsrc);
source.setSystemId(rsrc.getResolvedURI().toString());
return source;
}
/** Implements the {@link org.xmlresolver.NamespaceResolver} interface. */
@Override
public Source resolveNamespace(String uri, String nature, String purpose) throws TransformerException {
ResolvedResource rsrc = resolver.resolveNamespace(uri, null, nature, purpose);
if (rsrc == null) {
return null;
}
ResolverSAXSource source = new ResolverSAXSource(rsrc.getLocalURI(), new InputSource(rsrc.getInputStream()));
source.setSystemId(rsrc.getResolvedURI().toString());
return source;
}
protected ResolvedResource openConnection(String uri, String baseURI, boolean asEntity) throws IOException {
try {
URI absuri = baseURI == null ? URIUtils.cwd() : new URI(baseURI);
absuri = absuri.resolve(uri);
return openConnection(absuri, asEntity);
} catch (IllegalArgumentException ex) {
if (config.getFeature(ResolverFeature.THROW_URI_EXCEPTIONS)) {
throw ex;
}
return null;
} catch (URISyntaxException ex) {
if (config.getFeature(ResolverFeature.THROW_URI_EXCEPTIONS)) {
throw new IOException(ex);
}
return null;
}
}
protected ResolvedResource openConnection(URI originalURI, boolean asEntity) throws IOException {
boolean throwExceptions = config.getFeature(ResolverFeature.THROW_URI_EXCEPTIONS);
HashSet seen = new HashSet<>();
int count = 100;
URI absoluteURI = originalURI;
URLConnection connection = null;
boolean done = false;
int code = 200;
final boolean mergeHttps = config.getFeature(ResolverFeature.MERGE_HTTPS);
final String accessList;
if (asEntity) {
accessList = config.getFeature(ResolverFeature.ACCESS_EXTERNAL_ENTITY);
} else {
accessList = config.getFeature(ResolverFeature.ACCESS_EXTERNAL_DOCUMENT);
}
while (!done) {
if (URIUtils.forbidAccess(accessList, absoluteURI.toString(), mergeHttps)) {
if (asEntity) {
logger.log(AbstractLogger.REQUEST, "resolveEntity, access denied: null");
} else {
logger.log(AbstractLogger.REQUEST, "resolveURI, access denied: null");
}
return null;
}
if (seen.contains(absoluteURI)) {
if (throwExceptions) {
throw new IOException("Redirect loop on " + absoluteURI);
}
return null;
}
if (count <= 0) {
if (throwExceptions) {
throw new IOException("Too many redirects on " + absoluteURI);
}
return null;
}
seen.add(absoluteURI);
count--;
try {
connection = absoluteURI.toURL().openConnection();
connection.connect();
} catch (Exception ex) {
if (throwExceptions) {
throw ex;
}
return null;
}
done = !(connection instanceof HttpURLConnection);
if (!done) {
HttpURLConnection conn = (HttpURLConnection) connection;
code = conn.getResponseCode();
if (code >= 300 && code < 400) {
String loc = conn.getHeaderField("location");
absoluteURI = absoluteURI.resolve(loc);
} else {
done = true;
}
}
}
ResolvedResourceImpl rsrc = new ResolvedResourceImpl(originalURI, absoluteURI, connection.getInputStream(), code, connection.getHeaderFields());
return rsrc;
}
}