TestEntityResolver.java
package wstxtest.sax;
import java.io.*;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamException;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import com.ctc.wstx.exc.WstxException;
import com.ctc.wstx.sax.WstxSAXParserFactory;
import com.ctc.wstx.stax.WstxInputFactory;
import wstxtest.BaseWstxTest;
import org.junit.jupiter.api.Test;
/**
* Simple unit tests to verify that most fundamental parsing functionality
* works via Woodstox SAX implementation.
*/
public class TestEntityResolver
extends BaseWstxTest
{
@Test
public void testWithDummyExtSubset()
throws Exception
{
final String XML =
"<!DOCTYPE root PUBLIC '//some//public//id' 'no-such-thing.dtd'>\n"
+"<root />"
;
SAXParserFactory spf = new WstxSAXParserFactory();
spf.setNamespaceAware(true);
SAXParser sp = spf.newSAXParser();
DefaultHandler h = new DefaultHandler();
/* First: let's verify that we get an exception for
* unresolved reference...
*/
try {
sp.parse(new InputSource(new StringReader(XML)), h);
fail("Should not pass");
} catch (SAXException e) {
Throwable cause = e.getCause();
assertNotNull(cause);
assertTrue(cause instanceof WstxException);
// [woodstox-core#84]: actual message varies by OS so only verify
// the file name appears (locale-independent)
verifyException(e, "no-such-thing.dtd");
// [woodstox-core#231]: previously also verified " file " substring,
// but that part of the message is localized by the JVM/OS.
// The "(was java.io.FileNotFoundException)" tag prepended by
// Woodstox is locale-independent, so check for that instead.
verifyException(e, FileNotFoundException.class.getName());
}
// And then with dummy resolver; should work ok now
sp = spf.newSAXParser();
sp.getXMLReader().setEntityResolver(new MyResolver(" "));
h = new DefaultHandler();
try {
sp.parse(new InputSource(new StringReader(XML)), h);
} catch (SAXException e) {
fail("Should not have failed with entity resolver, got ("+e.getClass()+"): "+e.getMessage());
}
}
/**
* Test for [woodstox-core#226]: the {@link XMLResolver} configured on the
* underlying {@link WstxInputFactory} should be used by the SAX parser
* when no SAX EntityResolver is explicitly set on the parser itself.
*/
@Test
public void testFactoryXMLResolverIsInherited()
throws Exception
{
final String XML =
"<!DOCTYPE root PUBLIC '//some//public//id' 'no-such-thing.dtd'>\n"
+"<root />"
;
WstxInputFactory inputFactory = new WstxInputFactory();
final boolean[] resolverCalled = new boolean[] { false };
inputFactory.setXMLResolver(new XMLResolver() {
@Override
public Object resolveEntity(String publicID, String systemID, String baseURI, String namespace)
throws XMLStreamException {
resolverCalled[0] = true;
// Return an empty stream, equivalent to an empty DTD subset
return new ByteArrayInputStream(new byte[0]);
}
});
WstxSAXParserFactory spf = new WstxSAXParserFactory(inputFactory);
SAXParser sp = spf.newSAXParser();
DefaultHandler h = new DefaultHandler();
try {
sp.parse(new InputSource(new StringReader(XML)), h);
} catch (SAXException e) {
fail("Should not have failed when XMLResolver is set on factory, got ("
+ e.getClass() + "): " + e.getMessage());
}
assertTrue("Expected XMLResolver configured on factory to be invoked", resolverCalled[0]);
}
/**
* Test for [woodstox-core#226]: when {@code SUPPORT_DTD} is disabled on the
* underlying {@link WstxInputFactory}, the SAX parser should honor that
* setting and not attempt to resolve the external DTD subset.
*/
@Test
public void testFactorySupportDtdIsInherited()
throws Exception
{
final String XML =
"<!DOCTYPE root PUBLIC '//some//public//id' 'no-such-thing.dtd'>\n"
+"<root />"
;
WstxInputFactory inputFactory = new WstxInputFactory();
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
WstxSAXParserFactory spf = new WstxSAXParserFactory(inputFactory);
SAXParser sp = spf.newSAXParser();
DefaultHandler h = new DefaultHandler();
try {
sp.parse(new InputSource(new StringReader(XML)), h);
} catch (SAXException e) {
fail("Should not have failed when SUPPORT_DTD is disabled on factory, got ("
+ e.getClass() + "): " + e.getMessage());
}
}
/**
* Test for [woodstox-core#226]: when a SAX EntityResolver is explicitly
* registered on the parser, it should win over the {@link XMLResolver}
* configured on the factory (the factory resolver is only a fallback).
*/
@Test
public void testSAXEntityResolverWinsOverFactoryResolver()
throws Exception
{
final String XML =
"<!DOCTYPE root PUBLIC '//some//public//id' 'no-such-thing.dtd'>\n"
+"<root />"
;
WstxInputFactory inputFactory = new WstxInputFactory();
final boolean[] factoryResolverCalled = new boolean[] { false };
inputFactory.setXMLResolver(new XMLResolver() {
@Override
public Object resolveEntity(String publicID, String systemID, String baseURI, String namespace)
throws XMLStreamException {
factoryResolverCalled[0] = true;
return new ByteArrayInputStream(new byte[0]);
}
});
WstxSAXParserFactory spf = new WstxSAXParserFactory(inputFactory);
SAXParser sp = spf.newSAXParser();
final boolean[] saxResolverCalled = new boolean[] { false };
sp.getXMLReader().setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) {
saxResolverCalled[0] = true;
return new InputSource(new StringReader(""));
}
});
DefaultHandler h = new DefaultHandler();
try {
sp.parse(new InputSource(new StringReader(XML)), h);
} catch (SAXException e) {
fail("Parsing failed: ("+e.getClass()+"): "+e.getMessage());
}
assertTrue("Expected SAX EntityResolver to be invoked", saxResolverCalled[0]);
assertFalse("Factory XMLResolver must not be consulted when SAX EntityResolver resolves",
factoryResolverCalled[0]);
}
/*
///////////////////////////////////////////////////////
// Helper classes
///////////////////////////////////////////////////////
*/
static class MyResolver
implements EntityResolver
{
final String mContents;
public MyResolver(String c) {
mContents = c;
}
@Override
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new StringReader(mContents));
}
}
}