/*
* Copyright (c) 2014 the original author or authors
*
* Licensed 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
*
* http://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 io.werval.modules.xml.internal;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Field;
import java.util.List;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import io.werval.modules.xml.UncheckedXMLException;
import org.apache.xerces.util.XMLCatalogResolver;
import org.apache.xerces.xni.XMLResourceIdentifier;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.apache.xml.resolver.CatalogManager;
import org.w3c.dom.ls.LSInput;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import static io.werval.modules.xml.internal.Internal.LOG;
/**
* XML-Catalog Resolver.
*/
public final class CatalogResolver
implements Resolver
{
private static final class Debug
extends org.apache.xml.resolver.helpers.Debug
{
@Override
public void message( int level, String message )
{
if( level <= 2 )
{
LOG.trace( "CatalogResolver({}) {}", level, message );
}
else
{
LOG.debug( "CatalogResolver({}) {}", level, message );
}
}
@Override
public void message( int level, String message, String spec )
{
if( level <= 2 )
{
LOG.trace( "CatalogResolver({}) {}: {}", level, message, spec );
}
else
{
LOG.debug( "CatalogResolver({}) {}: {}", level, message, spec );
}
}
@Override
public void message( int level, String message, String spec1, String spec2 )
{
if( level <= 2 )
{
LOG.trace( "CatalogResolver({}) {}: {}\n {}", level, message, spec1, spec2 );
}
else
{
LOG.debug( "CatalogResolver({}) {}: {}\n {}", level, message, spec1, spec2 );
}
}
}
private final XMLCatalogResolver delegate = new org.apache.xerces.util.XMLCatalogResolver();
private final boolean throwIfNotFound;
public CatalogResolver( boolean throwIfNotFound, List<String> catalogs, boolean preferPublic )
{
this.throwIfNotFound = throwIfNotFound;
delegate.setCatalogList( catalogs.toArray( new String[ catalogs.size() ] ) );
delegate.setPreferPublic( preferPublic );
if( LOG.isDebugEnabled() )
{
try
{
Field catalogManagerField = XMLCatalogResolver.class.getDeclaredField( "fResolverCatalogManager" );
catalogManagerField.setAccessible( true );
CatalogManager catalogManager = (CatalogManager) catalogManagerField.get( delegate );
catalogManager.setVerbosity( Integer.MAX_VALUE );
catalogManager.debug = new Debug();
catalogManagerField.setAccessible( false );
}
catch( NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex )
{
LOG.warn(
"Unable to setup XML-Catalog resolution logging, something is broken, please report the issue!",
ex
);
}
}
}
// StAX or SAX
@Override
public InputSource resolveEntity( String name, String publicId, String baseURI, String systemId )
{
try
{
InputSource source = delegate.resolveEntity( name, publicId, baseURI, systemId );
if( throwIfNotFound && source == null )
{
throw new UncheckedXMLException(
String.format(
"Entity catalog resolution failure, remote blocked (StAX or SAX): "
+ "name=%s publicId=%s baseURI=%s systemId=%s",
name, publicId, baseURI, systemId
)
);
}
if( source == null )
{
LOG.warn(
"Entity catalog resolution failure, remote allowed (StAX or SAX): "
+ "name={} publicId={} baseURI={} systemId={}",
name, publicId, baseURI, systemId
);
}
else
{
LOG.debug(
"Entity catalog resolution success (StAX or SAX): name={} publicId={} baseURI={} systemId={}",
name, publicId, baseURI, systemId
);
}
return source;
}
catch( SAXException ex )
{
throw new UncheckedXMLException( ex );
}
catch( IOException ex )
{
throw new UncheckedIOException( ex );
}
}
// SAX
@Override
public InputSource resolveEntity( String publicId, String systemId )
throws SAXException, IOException
{
LOG.debug(
"Entity resolution attempt (SAX): publicId={} systemId={}",
publicId, systemId
);
InputSource source = delegate.resolveEntity( publicId, systemId );
if( throwIfNotFound && source == null )
{
throw new UncheckedXMLException(
String.format(
"Entity catalog resolution failure, remote blocked (SAX): publicId=%s systemId=%s",
publicId, systemId
)
);
}
if( source == null )
{
LOG.warn(
"Entity catalog resolution failure, remote allowed (SAX): publicId={} systemId={}",
publicId, systemId
);
}
else
{
LOG.debug(
"Entity catalog resolution success (SAX): publicId={} systemId={}",
publicId, systemId
);
}
return source;
}
// SAX2
@Override
public InputSource getExternalSubset( String name, String baseURI )
throws SAXException, IOException
{
// No remote check here as a null return won't trigger another lookup
InputSource source = delegate.getExternalSubset( name, baseURI );
if( source == null )
{
LOG.trace( "External subset catalog resolution failure (SAX): name={} baseURI={}", name, baseURI );
}
else
{
LOG.trace( "External subset catalog resolution success (SAX): name={} baseURI={}", name, baseURI );
}
return source;
}
// DOM
@Override
public LSInput resolveResource( String type, String namespaceURI, String publicId, String systemId, String baseURI )
{
LSInput source = delegate.resolveResource( type, namespaceURI, publicId, systemId, baseURI );
if( throwIfNotFound && source == null )
{
throw new UncheckedXMLException(
String.format(
"Resource catalog resolution failure, remote blocked (DOM): "
+ "type=%s namespace=%s publicId=%s systemId=%s baseURI=%s",
type, namespaceURI, publicId, systemId, baseURI
)
);
}
if( source == null )
{
LOG.warn(
"Resource catalog resolution failure, remote allowed (DOM): "
+ "type={} namespace={} publicId={} systemId={} baseURI={}",
type, namespaceURI, publicId, systemId, baseURI
);
}
else
{
LOG.warn(
"Resource catalog resolution success (DOM): type={} namespace={} publicId={} systemId={} baseURI={}",
type, namespaceURI, publicId, systemId, baseURI
);
}
return source;
}
// XSLT, xsl:include, xsl:import, or document() function
@Override
public Source resolve( String href, String base )
throws TransformerException
{
try
{
InputSource source = delegate.resolveEntity( href, base );
if( throwIfNotFound && source == null )
{
throw new UncheckedXMLException(
String.format(
"URI catalog resolution failure, remote blocked (XSLT): href=%s base=%s",
href, base
)
);
}
if( source == null )
{
LOG.warn( "URI catalog resolution failure, remote allowed (XSLT): href={} base={}", href, base );
return null;
}
LOG.warn( "URI catalog resolution success (XSLT): href={} base={}", href, base );
StreamSource result = new StreamSource( source.getSystemId() );
result.setPublicId( source.getPublicId() );
result.setReader( source.getCharacterStream() );
result.setInputStream( source.getByteStream() );
return result;
}
catch( SAXException | IOException ex )
{
throw new TransformerException( ex );
}
}
// Xerces XNI
@Override
public XMLInputSource resolveEntity( XMLResourceIdentifier resourceIdentifier )
throws XNIException, IOException
{
XMLInputSource source = delegate.resolveEntity( resourceIdentifier );
if( throwIfNotFound && source == null )
{
throw new UncheckedXMLException(
String.format(
"Entity catalog resolution failure, remote blocked (Xerces XNI): resourceIdentifier=%s",
resourceIdentifier
)
);
}
if( source == null )
{
LOG.warn(
"Entity catalog resolution failure, remote allowed (Xerces XNI): resourceIdentifier={}",
resourceIdentifier
);
}
else
{
LOG.debug( "Entity catalog resolution success (Xerces XNI): resourceIdentifier={}", resourceIdentifier );
}
return source;
}
}