You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2026/08/26 12:21:42 UTC
(commons-xml) 01/08: Sort members
This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-xml.git
commit f6583c76edb116407510971ba27c09abb14956a5
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed Aug 26 07:23:51 2026 -0400
Sort members
---
.../commons/xml/FallbackIgnoreEntityResolver2.java | 50 +--
.../xml/FallbackIgnoreLSResourceResolver.java | 14 +-
.../commons/xml/FallbackIgnoreURIResolver.java | 8 +-
.../commons/xml/FallbackIgnoreXMLResolver.java | 8 +-
.../commons/xml/HardeningDocumentBuilder.java | 42 +--
.../xml/HardeningDocumentBuilderFactory.java | 10 +-
.../org/apache/commons/xml/HardeningException.java | 30 +-
.../org/apache/commons/xml/HardeningSAXParser.java | 34 +-
.../commons/xml/HardeningSAXParserFactory.java | 10 +-
.../apache/commons/xml/HardeningSchemaFactory.java | 62 ++--
.../org/apache/commons/xml/HardeningTemplates.java | 14 +-
.../commons/xml/HardeningTemplatesHandler.java | 64 ++--
.../apache/commons/xml/HardeningTransformer.java | 52 +--
.../commons/xml/HardeningTransformerFactory.java | 142 ++++----
.../commons/xml/HardeningTransformerHandler.java | 98 +++---
.../commons/xml/HardeningValidatorHandler.java | 88 ++---
.../commons/xml/HardeningXMLInputFactory.java | 168 +++++-----
.../org/apache/commons/xml/HardeningXMLReader.java | 20 +-
.../org/apache/commons/xml/HardeningXPath.java | 12 +-
.../apache/commons/xml/HardeningXPathFactory.java | 12 +-
.../java/org/apache/commons/xml/SaxonProvider.java | 42 +--
.../commons/xml/AssociatedStylesheetTest.java | 16 +-
.../org/apache/commons/xml/AttackTestSupport.java | 38 +--
.../org/apache/commons/xml/DenyUnresolvedTest.java | 10 +-
.../commons/xml/EntityResolverFloorTest.java | 372 ++++++++++-----------
.../org/apache/commons/xml/ResetHardeningTest.java | 28 +-
.../apache/commons/xml/SaxSurfaceTestSupport.java | 26 +-
.../commons/xml/SaxonAlternateFactoryTest.java | 18 +-
.../apache/commons/xml/SchemaLocationDomTest.java | 52 +--
.../commons/xml/SchemaLocationPropertyTest.java | 76 ++---
.../apache/commons/xml/SchemaLocationSaxTest.java | 56 ++--
.../apache/commons/xml/ShadingFootprintTest.java | 120 +++----
.../apache/commons/xml/TemplatesHandlerTest.java | 14 +-
.../apache/commons/xml/TransformerHandlerTest.java | 20 +-
.../xml/UnsupportedXmlImplementationTest.java | 30 +-
.../java/org/apache/commons/xml/XIncludeTest.java | 194 +++++------
.../java/org/apache/commons/xml/XMLFilterTest.java | 48 +--
.../apache/commons/xml/XPathInputSourceTest.java | 14 +-
.../org/apache/commons/xml/XmlFactoriesTest.java | 44 +--
39 files changed, 1078 insertions(+), 1078 deletions(-)
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
index dc04506..0b036b9 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
@@ -52,11 +52,6 @@
*/
class FallbackIgnoreEntityResolver2 extends DefaultHandler2 {
- /**
- * Caller-supplied resolver consulted first, or {@code null} for a pure ignore-all floor.
- */
- private EntityResolver delegate;
-
/**
* Resolves {@code systemId} against {@code baseURI}.
*
@@ -76,16 +71,12 @@ private static String absolutize(final String baseURI, final String systemId) {
}
}
- FallbackIgnoreEntityResolver2(final EntityResolver delegate) {
- this.delegate = delegate;
- }
-
/**
- * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls.
- *
- * @param delegate The caller-supplied resolver, or {@code null} for a pure ignore-all floor.
+ * Caller-supplied resolver consulted first, or {@code null} for a pure ignore-all floor.
*/
- final void setDelegate(final EntityResolver delegate) {
+ private EntityResolver delegate;
+
+ FallbackIgnoreEntityResolver2(final EntityResolver delegate) {
this.delegate = delegate;
}
@@ -93,18 +84,6 @@ final EntityResolver getDelegate() {
return delegate;
}
- @Override
- public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
- return resolveEntity(null, publicId, null, systemId);
- }
-
- @Override
- public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId)
- throws SAXException, IOException {
- final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId);
- return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId);
- }
-
/**
* Outcome when neither the caller delegate nor this resolver provides the entity. Resolves to empty content by default, so the external resource is neither
* fetched nor leaked and the parse continues with no replacement text. The returned source echoes the requested identifiers (with {@code systemId}
@@ -129,6 +108,18 @@ protected InputSource onUnresolved(final String name, final String publicId, fin
return empty;
}
+ @Override
+ public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
+ return resolveEntity(null, publicId, null, systemId);
+ }
+
+ @Override
+ public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId)
+ throws SAXException, IOException {
+ final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId);
+ return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId);
+ }
+
private InputSource resolveWithDelegate(final String name, final String publicId, final String baseURI,
final String systemId) throws SAXException, IOException {
if (delegate != null) {
@@ -138,4 +129,13 @@ private InputSource resolveWithDelegate(final String name, final String publicId
}
return null;
}
+
+ /**
+ * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls.
+ *
+ * @param delegate The caller-supplied resolver, or {@code null} for a pure ignore-all floor.
+ */
+ final void setDelegate(final EntityResolver delegate) {
+ this.delegate = delegate;
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java
index ab70517..3b98643 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java
@@ -38,12 +38,6 @@ final class FallbackIgnoreLSResourceResolver implements LSResourceResolver {
/** DOM Level 3 Load/Save implementation used to build the empty input for unresolved lookups. */
private static final DOMImplementationLS DOM_LS = domImplementationLS();
- private LSResourceResolver delegate;
-
- FallbackIgnoreLSResourceResolver(final LSResourceResolver delegate) {
- this.delegate = delegate;
- }
-
private static DOMImplementationLS domImplementationLS() {
try {
return (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
@@ -52,7 +46,9 @@ private static DOMImplementationLS domImplementationLS() {
}
}
- void setDelegate(final LSResourceResolver delegate) {
+ private LSResourceResolver delegate;
+
+ FallbackIgnoreLSResourceResolver(final LSResourceResolver delegate) {
this.delegate = delegate;
}
@@ -80,4 +76,8 @@ public LSInput resolveResource(final String type, final String namespaceURI, fin
empty.setBaseURI(baseURI);
return empty;
}
+
+ void setDelegate(final LSResourceResolver delegate) {
+ this.delegate = delegate;
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java
index b903606..a462872 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java
@@ -78,10 +78,6 @@ private static Document newEmptyDocument() {
this.emptySource = emptySource != null ? emptySource : () -> new DOMSource(EMPTY_DOCUMENT);
}
- void setDelegate(final URIResolver delegate) {
- this.delegate = delegate;
- }
-
URIResolver getDelegate() {
return delegate;
}
@@ -98,4 +94,8 @@ public Source resolve(final String href, final String base) throws TransformerEx
}
return emptySource.get();
}
+
+ void setDelegate(final URIResolver delegate) {
+ this.delegate = delegate;
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java
index 1242be1..94c24c8 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java
@@ -45,10 +45,6 @@ final class FallbackIgnoreXMLResolver implements XMLResolver {
this.delegate = delegate;
}
- void setDelegate(final XMLResolver delegate) {
- this.delegate = delegate;
- }
-
XMLResolver getDelegate() {
return delegate;
}
@@ -64,4 +60,8 @@ public Object resolveEntity(final String publicID, final String systemID, final
}
return EMPTY;
}
+
+ void setDelegate(final XMLResolver delegate) {
+ this.delegate = delegate;
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java
index 8343921..b9bf44e 100644
--- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java
+++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java
@@ -48,22 +48,15 @@ final class HardeningDocumentBuilder extends DocumentBuilder {
}
@Override
- public void setEntityResolver(final EntityResolver resolver) {
- floor.setDelegate(resolver);
+ public DOMImplementation getDOMImplementation() {
+ return delegate.getDOMImplementation();
}
@Override
- public void reset() {
- delegate.reset();
- floor.setDelegate(null);
- delegate.setEntityResolver(floor);
- }
-
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
- @Override
- public Document parse(final InputSource is) throws SAXException, IOException {
- return delegate.parse(is);
+ public Schema getSchema() {
+ return delegate.getSchema();
}
+ // </editor-fold>
@Override
public boolean isNamespaceAware() {
@@ -81,23 +74,30 @@ public boolean isXIncludeAware() {
}
@Override
- public void setErrorHandler(final ErrorHandler eh) {
- delegate.setErrorHandler(eh);
+ public Document newDocument() {
+ return delegate.newDocument();
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public Document newDocument() {
- return delegate.newDocument();
+ public Document parse(final InputSource is) throws SAXException, IOException {
+ return delegate.parse(is);
}
@Override
- public DOMImplementation getDOMImplementation() {
- return delegate.getDOMImplementation();
+ public void reset() {
+ delegate.reset();
+ floor.setDelegate(null);
+ delegate.setEntityResolver(floor);
}
@Override
- public Schema getSchema() {
- return delegate.getSchema();
+ public void setEntityResolver(final EntityResolver resolver) {
+ floor.setDelegate(resolver);
+ }
+
+ @Override
+ public void setErrorHandler(final ErrorHandler eh) {
+ delegate.setErrorHandler(eh);
}
- // </editor-fold>
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
index 2ae105f..6a18dbd 100644
--- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
@@ -39,11 +39,6 @@ final class HardeningDocumentBuilderFactory extends DocumentBuilderFactory {
this.delegate = delegate;
}
- @Override
- public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
- return new HardeningDocumentBuilder(delegate.newDocumentBuilder());
- }
-
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public Object getAttribute(final String name) {
@@ -95,6 +90,11 @@ public boolean isXIncludeAware() {
return delegate.isXIncludeAware();
}
+ @Override
+ public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+ return new HardeningDocumentBuilder(delegate.newDocumentBuilder());
+ }
+
@Override
public void setAttribute(final String name, final Object value) {
delegate.setAttribute(name, value);
diff --git a/src/main/java/org/apache/commons/xml/HardeningException.java b/src/main/java/org/apache/commons/xml/HardeningException.java
index bc7c1de..4dd123c 100644
--- a/src/main/java/org/apache/commons/xml/HardeningException.java
+++ b/src/main/java/org/apache/commons/xml/HardeningException.java
@@ -35,8 +35,19 @@ final class HardeningException extends IllegalStateException {
private static final long serialVersionUID = 1L;
- HardeningException(final String message, final Throwable cause) {
- super(message, cause);
+ /**
+ * Builds the standard "forbidden" message shared by every resolver floor when {@link #throwOnUnresolved()} rejects an unresolved reference.
+ *
+ * @param type the resource kind, or {@code null} if not applicable.
+ * @param namespace the namespace (or, for Woodstox, the entity name), or {@code null}.
+ * @param publicId the public identifier, or {@code null} if none.
+ * @param systemId the system identifier of the denied resource.
+ * @param baseURI the base URI for relative resolution, or {@code null}.
+ * @return the message naming the denied lookup and the enabling property.
+ */
+ static String forbidden(final String type, final String namespace, final String publicId, final String systemId, final String baseURI) {
+ return String.format("External resource fetch forbidden by %s: type=%s, namespace=%s, publicId=%s, systemId=%s, baseURI=%s",
+ XmlFactories.THROW_ON_UNRESOLVED, type, namespace, publicId, systemId, baseURI);
}
/**
@@ -63,18 +74,7 @@ static boolean throwOnUnresolved() {
return Boolean.getBoolean(XmlFactories.THROW_ON_UNRESOLVED);
}
- /**
- * Builds the standard "forbidden" message shared by every resolver floor when {@link #throwOnUnresolved()} rejects an unresolved reference.
- *
- * @param type the resource kind, or {@code null} if not applicable.
- * @param namespace the namespace (or, for Woodstox, the entity name), or {@code null}.
- * @param publicId the public identifier, or {@code null} if none.
- * @param systemId the system identifier of the denied resource.
- * @param baseURI the base URI for relative resolution, or {@code null}.
- * @return the message naming the denied lookup and the enabling property.
- */
- static String forbidden(final String type, final String namespace, final String publicId, final String systemId, final String baseURI) {
- return String.format("External resource fetch forbidden by %s: type=%s, namespace=%s, publicId=%s, systemId=%s, baseURI=%s",
- XmlFactories.THROW_ON_UNRESOLVED, type, namespace, publicId, systemId, baseURI);
+ HardeningException(final String message, final Throwable cause) {
+ super(message, cause);
}
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningSAXParser.java b/src/main/java/org/apache/commons/xml/HardeningSAXParser.java
index 5a79e74..5e176b7 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSAXParser.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSAXParser.java
@@ -49,14 +49,6 @@ final class HardeningSAXParser extends SAXParser {
this.delegate = delegate;
}
- @Override
- public XMLReader getXMLReader() throws SAXException {
- if (hardenedReader == null) {
- hardenedReader = SAXParserHardener.hardenReader(delegate.getXMLReader());
- }
- return hardenedReader;
- }
-
@Override
@SuppressWarnings("deprecation")
public Parser getParser() throws SAXException {
@@ -68,15 +60,6 @@ public Parser getParser() throws SAXException {
return hardenedParser;
}
- @Override
- public void reset() {
- delegate.reset();
- // The JAXP reset contract reverts the delegate to its just-created state, which strips the post-creation reader hardening.
- // We reset the cached readers, so hardening can be applied again.
- hardenedReader = null;
- hardenedParser = null;
- }
-
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
@@ -88,6 +71,14 @@ public Schema getSchema() {
return delegate.getSchema();
}
+ @Override
+ public XMLReader getXMLReader() throws SAXException {
+ if (hardenedReader == null) {
+ hardenedReader = SAXParserHardener.hardenReader(delegate.getXMLReader());
+ }
+ return hardenedReader;
+ }
+
@Override
public boolean isNamespaceAware() {
return delegate.isNamespaceAware();
@@ -103,6 +94,15 @@ public boolean isXIncludeAware() {
return delegate.isXIncludeAware();
}
+ @Override
+ public void reset() {
+ delegate.reset();
+ // The JAXP reset contract reverts the delegate to its just-created state, which strips the post-creation reader hardening.
+ // We reset the cached readers, so hardening can be applied again.
+ hardenedReader = null;
+ hardenedParser = null;
+ }
+
@Override
public void setProperty(final String name, final Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
delegate.setProperty(name, value);
diff --git a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
index e0ea507..19f79a9 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
@@ -42,11 +42,6 @@ final class HardeningSAXParserFactory extends SAXParserFactory {
this.delegate = delegate;
}
- @Override
- public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
- return new HardeningSAXParser(delegate.newSAXParser());
- }
-
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public boolean getFeature(final String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
@@ -73,6 +68,11 @@ public boolean isXIncludeAware() {
return delegate.isXIncludeAware();
}
+ @Override
+ public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
+ return new HardeningSAXParser(delegate.newSAXParser());
+ }
+
@Override
public void setFeature(final String name, final boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
delegate.setFeature(name, value);
diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
index a21ee58..31cc9c0 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
@@ -53,37 +53,6 @@
*/
final class HardeningSchemaFactory extends SchemaFactory {
- private final SchemaFactory delegate;
-
- private final FallbackIgnoreLSResourceResolver floor = new FallbackIgnoreLSResourceResolver(null);
-
- HardeningSchemaFactory(final SchemaFactory delegate) {
- this.delegate = delegate;
- // Compile-time block for xs:import/include/redefine; the wrappers carry the rest (per-product resolver, source rewriting, limits via the reader).
- delegate.setResourceResolver(floor);
- }
-
- @Override
- public void setResourceResolver(final LSResourceResolver resourceResolver) {
- // Route a caller resolver through the floor instead of replacing it, so the ignore-all lower bound cannot be removed.
- floor.setDelegate(resourceResolver);
- }
-
- @Override
- public LSResourceResolver getResourceResolver() {
- return floor.getDelegate();
- }
-
- @Override
- public Schema newSchema() throws SAXException {
- return new HardeningSchema(delegate.newSchema());
- }
-
- @Override
- public Schema newSchema(final Source[] schemas) throws SAXException {
- return new HardeningSchema(delegate.newSchema(harden(schemas)));
- }
-
private static Source[] harden(final Source[] schemas) throws SAXException {
final Source[] hardened = new Source[schemas.length];
try {
@@ -96,6 +65,16 @@ private static Source[] harden(final Source[] schemas) throws SAXException {
return hardened;
}
+ private final SchemaFactory delegate;
+
+ private final FallbackIgnoreLSResourceResolver floor = new FallbackIgnoreLSResourceResolver(null);
+
+ HardeningSchemaFactory(final SchemaFactory delegate) {
+ this.delegate = delegate;
+ // Compile-time block for xs:import/include/redefine; the wrappers carry the rest (per-product resolver, source rewriting, limits via the reader).
+ delegate.setResourceResolver(floor);
+ }
+
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public ErrorHandler getErrorHandler() {
@@ -112,11 +91,26 @@ public Object getProperty(final String name) throws SAXNotRecognizedException, S
return delegate.getProperty(name);
}
+ @Override
+ public LSResourceResolver getResourceResolver() {
+ return floor.getDelegate();
+ }
+
@Override
public boolean isSchemaLanguageSupported(final String schemaLanguage) {
return delegate.isSchemaLanguageSupported(schemaLanguage);
}
+ @Override
+ public Schema newSchema() throws SAXException {
+ return new HardeningSchema(delegate.newSchema());
+ }
+
+ @Override
+ public Schema newSchema(final Source[] schemas) throws SAXException {
+ return new HardeningSchema(delegate.newSchema(harden(schemas)));
+ }
+
@Override
public void setErrorHandler(final ErrorHandler errorHandler) {
delegate.setErrorHandler(errorHandler);
@@ -132,4 +126,10 @@ public void setProperty(final String name, final Object object) throws SAXNotRec
delegate.setProperty(name, object);
}
// </editor-fold>
+
+ @Override
+ public void setResourceResolver(final LSResourceResolver resourceResolver) {
+ // Route a caller resolver through the floor instead of replacing it, so the ignore-all lower bound cannot be removed.
+ floor.setDelegate(resourceResolver);
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningTemplates.java b/src/main/java/org/apache/commons/xml/HardeningTemplates.java
index f598aa8..af8ceeb 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTemplates.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTemplates.java
@@ -60,6 +60,13 @@ Templates getDelegate() {
return delegate;
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+ @Override
+ public Properties getOutputProperties() {
+ return delegate.getOutputProperties();
+ }
+ // </editor-fold>
+
@Override
public Transformer newTransformer() throws TransformerConfigurationException {
final Transformer transformer = delegate.newTransformer();
@@ -68,11 +75,4 @@ public Transformer newTransformer() throws TransformerConfigurationException {
}
return new HardeningTransformer(transformer, uriResolver, emptySource);
}
-
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
- @Override
- public Properties getOutputProperties() {
- return delegate.getOutputProperties();
- }
- // </editor-fold>
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java b/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java
index dfcddaa..9600ac1 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java
@@ -53,76 +53,76 @@ final class HardeningTemplatesHandler implements TemplatesHandler {
}
@Override
- public Templates getTemplates() {
- // Null before the stylesheet's endDocument (and on a failed compile in some implementations).
- final Templates templates = delegate.getTemplates();
- return templates == null ? null : new HardeningTemplates(templates, uriResolver, emptySource);
+ public void characters(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.characters(ch, start, length);
}
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public void setSystemId(final String systemID) {
- delegate.setSystemId(systemID);
+ public void endDocument() throws SAXException {
+ delegate.endDocument();
}
@Override
- public String getSystemId() {
- return delegate.getSystemId();
+ public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+ delegate.endElement(uri, localName, qName);
}
@Override
- public void setDocumentLocator(final Locator locator) {
- delegate.setDocumentLocator(locator);
+ public void endPrefixMapping(final String prefix) throws SAXException {
+ delegate.endPrefixMapping(prefix);
}
@Override
- public void startDocument() throws SAXException {
- delegate.startDocument();
+ public String getSystemId() {
+ return delegate.getSystemId();
}
@Override
- public void endDocument() throws SAXException {
- delegate.endDocument();
+ public Templates getTemplates() {
+ // Null before the stylesheet's endDocument (and on a failed compile in some implementations).
+ final Templates templates = delegate.getTemplates();
+ return templates == null ? null : new HardeningTemplates(templates, uriResolver, emptySource);
}
@Override
- public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
- delegate.startPrefixMapping(prefix, uri);
+ public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.ignorableWhitespace(ch, start, length);
}
@Override
- public void endPrefixMapping(final String prefix) throws SAXException {
- delegate.endPrefixMapping(prefix);
+ public void processingInstruction(final String target, final String data) throws SAXException {
+ delegate.processingInstruction(target, data);
}
@Override
- public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
- delegate.startElement(uri, localName, qName, atts);
+ public void setDocumentLocator(final Locator locator) {
+ delegate.setDocumentLocator(locator);
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public void endElement(final String uri, final String localName, final String qName) throws SAXException {
- delegate.endElement(uri, localName, qName);
+ public void setSystemId(final String systemID) {
+ delegate.setSystemId(systemID);
}
@Override
- public void characters(final char[] ch, final int start, final int length) throws SAXException {
- delegate.characters(ch, start, length);
+ public void skippedEntity(final String name) throws SAXException {
+ delegate.skippedEntity(name);
}
+ // </editor-fold>
@Override
- public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
- delegate.ignorableWhitespace(ch, start, length);
+ public void startDocument() throws SAXException {
+ delegate.startDocument();
}
@Override
- public void processingInstruction(final String target, final String data) throws SAXException {
- delegate.processingInstruction(target, data);
+ public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+ delegate.startElement(uri, localName, qName, atts);
}
@Override
- public void skippedEntity(final String name) throws SAXException {
- delegate.skippedEntity(name);
+ public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+ delegate.startPrefixMapping(prefix, uri);
}
- // </editor-fold>
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformer.java b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
index e406138..beda636 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformer.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
@@ -53,32 +53,6 @@ final class HardeningTransformer extends Transformer {
delegate.setURIResolver(floor);
}
- @Override
- public void reset() {
- delegate.reset();
- floor.setDelegate(uriResolver);
- delegate.setURIResolver(floor);
- }
-
- @Override
- public void setURIResolver(final URIResolver resolver) {
- floor.setDelegate(resolver);
- }
-
- @Override
- public URIResolver getURIResolver() {
- return floor.getDelegate();
- }
-
- @Override
- public void transform(final Source xmlSource, final Result outputTarget) throws TransformerException {
- try {
- delegate.transform(SAXParserHardener.hardenSource(xmlSource), outputTarget);
- } catch (final TransformerConfigurationException e) {
- throw new TransformerException(e);
- }
- }
-
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public void clearParameters() {
@@ -105,6 +79,18 @@ public Object getParameter(final String name) {
return delegate.getParameter(name);
}
+ @Override
+ public URIResolver getURIResolver() {
+ return floor.getDelegate();
+ }
+
+ @Override
+ public void reset() {
+ delegate.reset();
+ floor.setDelegate(uriResolver);
+ delegate.setURIResolver(floor);
+ }
+
@Override
public void setErrorListener(final ErrorListener listener) {
delegate.setErrorListener(listener);
@@ -125,4 +111,18 @@ public void setParameter(final String name, final Object value) {
delegate.setParameter(name, value);
}
// </editor-fold>
+
+ @Override
+ public void setURIResolver(final URIResolver resolver) {
+ floor.setDelegate(resolver);
+ }
+
+ @Override
+ public void transform(final Source xmlSource, final Result outputTarget) throws TransformerException {
+ try {
+ delegate.transform(SAXParserHardener.hardenSource(xmlSource), outputTarget);
+ } catch (final TransformerConfigurationException e) {
+ throw new TransformerException(e);
+ }
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
index cc25ddd..84775d4 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
@@ -71,6 +71,46 @@
*/
final class HardeningTransformerFactory extends SAXTransformerFactory {
+ /**
+ * Parses a reader-less source into a DOM through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link DOMSource}
+ * carrying its system id, so the consumer walks the tree instead of provisioning its own reader. Any other source is left to
+ * {@link SAXParserHardener#hardenSource(Source)}.
+ *
+ * @param source The source to scan for an associated stylesheet.
+ * @return A {@link DOMSource} for a reader-less source, otherwise the result of {@link SAXParserHardener#hardenSource(Source)}.
+ * @throws TransformerConfigurationException if the source cannot be parsed.
+ */
+ private static Source hardenSourceToDom(final Source source) throws TransformerConfigurationException {
+ if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
+ final InputSource inputSource = SAXSource.sourceToInputSource(source);
+ if (inputSource != null) {
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ factory.setNamespaceAware(true);
+ final Document document = factory.newDocumentBuilder().parse(inputSource);
+ return new DOMSource(document, inputSource.getSystemId());
+ } catch (final ParserConfigurationException | SAXException | IOException e) {
+ throw new TransformerConfigurationException("Failed to parse the source for associated-stylesheet lookup", e);
+ }
+ }
+ }
+ return SAXParserHardener.hardenSource(source);
+ }
+
+ /**
+ * Whether the delegate is Apache Xalan (either its interpretive or its XSLTC factory), whose {@code getAssociatedStylesheet} ignores a SAXSource reader.
+ *
+ * @param factory The delegate factory.
+ * @return Whether the delegate is an {@code org.apache.xalan.} implementation.
+ */
+ private static boolean isXalan(final SAXTransformerFactory factory) {
+ return factory.getClass().getName().startsWith("org.apache.xalan.");
+ }
+
+ private static Templates unwrap(final Templates templates) {
+ return templates instanceof HardeningTemplates ? ((HardeningTemplates) templates).getDelegate() : templates;
+ }
+
private final SAXTransformerFactory delegate;
/** Empty-{@link Source} supplier for the resolver floor, threaded onto every produced Templates/Transformer; {@code null} means the default empty DOM. */
@@ -91,57 +131,36 @@ final class HardeningTransformerFactory extends SAXTransformerFactory {
}
@Override
- public void setURIResolver(final URIResolver resolver) {
- floor.setDelegate(resolver);
+ public Source getAssociatedStylesheet(final Source source, final String media, final String title, final String charset)
+ throws TransformerConfigurationException {
+ // Xalan's getAssociatedStylesheet drops a SAXSource's reader and self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849).
+ final Source hardened = isXalan(delegate) ? hardenSourceToDom(source) : SAXParserHardener.hardenSource(source);
+ return delegate.getAssociatedStylesheet(hardened, media, title, charset);
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public URIResolver getURIResolver() {
- return floor.getDelegate();
+ public Object getAttribute(final String name) {
+ return delegate.getAttribute(name);
}
@Override
- public Source getAssociatedStylesheet(final Source source, final String media, final String title, final String charset)
- throws TransformerConfigurationException {
- // Xalan's getAssociatedStylesheet drops a SAXSource's reader and self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849).
- final Source hardened = isXalan(delegate) ? hardenSourceToDom(source) : SAXParserHardener.hardenSource(source);
- return delegate.getAssociatedStylesheet(hardened, media, title, charset);
+ public ErrorListener getErrorListener() {
+ return delegate.getErrorListener();
}
- /**
- * Whether the delegate is Apache Xalan (either its interpretive or its XSLTC factory), whose {@code getAssociatedStylesheet} ignores a SAXSource reader.
- *
- * @param factory The delegate factory.
- * @return Whether the delegate is an {@code org.apache.xalan.} implementation.
- */
- private static boolean isXalan(final SAXTransformerFactory factory) {
- return factory.getClass().getName().startsWith("org.apache.xalan.");
+ @Override
+ public boolean getFeature(final String name) {
+ return delegate.getFeature(name);
}
- /**
- * Parses a reader-less source into a DOM through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link DOMSource}
- * carrying its system id, so the consumer walks the tree instead of provisioning its own reader. Any other source is left to
- * {@link SAXParserHardener#hardenSource(Source)}.
- *
- * @param source The source to scan for an associated stylesheet.
- * @return A {@link DOMSource} for a reader-less source, otherwise the result of {@link SAXParserHardener#hardenSource(Source)}.
- * @throws TransformerConfigurationException if the source cannot be parsed.
- */
- private static Source hardenSourceToDom(final Source source) throws TransformerConfigurationException {
- if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
- final InputSource inputSource = SAXSource.sourceToInputSource(source);
- if (inputSource != null) {
- try {
- final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
- factory.setNamespaceAware(true);
- final Document document = factory.newDocumentBuilder().parse(inputSource);
- return new DOMSource(document, inputSource.getSystemId());
- } catch (final ParserConfigurationException | SAXException | IOException e) {
- throw new TransformerConfigurationException("Failed to parse the source for associated-stylesheet lookup", e);
- }
- }
- }
- return SAXParserHardener.hardenSource(source);
+ @Override
+ public URIResolver getURIResolver() {
+ return floor.getDelegate();
+ }
+
+ private TransformerHandler hardenHandler(final TransformerHandler handler) {
+ return handler == null ? null : new HardeningTransformerHandler(handler, getURIResolver(), emptySource);
}
@Override
@@ -150,6 +169,12 @@ public Templates newTemplates(final Source source) throws TransformerConfigurati
return templates == null ? null : new HardeningTemplates(templates, getURIResolver(), emptySource);
}
+ @Override
+ public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
+ final TemplatesHandler handler = delegate.newTemplatesHandler();
+ return handler == null ? null : new HardeningTemplatesHandler(handler, getURIResolver(), emptySource);
+ }
+
@Override
public Transformer newTransformer() throws TransformerConfigurationException {
// Identity transformer: still parses runtime sources, so wrap it to harden Transformer.transform(Source, Result).
@@ -179,12 +204,6 @@ public TransformerHandler newTransformerHandler(final Templates templates) throw
return hardenHandler(delegate.newTransformerHandler(unwrap(templates)));
}
- @Override
- public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
- final TemplatesHandler handler = delegate.newTemplatesHandler();
- return handler == null ? null : new HardeningTemplatesHandler(handler, getURIResolver(), emptySource);
- }
-
@Override
public XMLFilter newXMLFilter(final Source source) throws TransformerConfigurationException {
final Templates templates = newTemplates(source);
@@ -197,30 +216,6 @@ public XMLFilter newXMLFilter(final Templates templates) throws TransformerConfi
: new HardeningTemplates(templates, getURIResolver(), emptySource));
}
- private TransformerHandler hardenHandler(final TransformerHandler handler) {
- return handler == null ? null : new HardeningTransformerHandler(handler, getURIResolver(), emptySource);
- }
-
- private static Templates unwrap(final Templates templates) {
- return templates instanceof HardeningTemplates ? ((HardeningTemplates) templates).getDelegate() : templates;
- }
-
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
- @Override
- public Object getAttribute(final String name) {
- return delegate.getAttribute(name);
- }
-
- @Override
- public ErrorListener getErrorListener() {
- return delegate.getErrorListener();
- }
-
- @Override
- public boolean getFeature(final String name) {
- return delegate.getFeature(name);
- }
-
@Override
public void setAttribute(final String name, final Object value) {
delegate.setAttribute(name, value);
@@ -236,4 +231,9 @@ public void setFeature(final String name, final boolean value) throws Transforme
delegate.setFeature(name, value);
}
// </editor-fold>
+
+ @Override
+ public void setURIResolver(final URIResolver resolver) {
+ floor.setDelegate(resolver);
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java b/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java
index 6281802..6c44417 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java
@@ -51,44 +51,39 @@ final class HardeningTransformerHandler implements TransformerHandler {
}
@Override
- public Transformer getTransformer() {
- return transformer;
- }
-
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
- @Override
- public void setResult(final Result result) {
- delegate.setResult(result);
+ public void characters(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.characters(ch, start, length);
}
@Override
- public void setSystemId(final String systemID) {
- delegate.setSystemId(systemID);
+ public void comment(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.comment(ch, start, length);
}
+ // </editor-fold>
@Override
- public String getSystemId() {
- return delegate.getSystemId();
+ public void endCDATA() throws SAXException {
+ delegate.endCDATA();
}
@Override
- public void setDocumentLocator(final Locator locator) {
- delegate.setDocumentLocator(locator);
+ public void endDocument() throws SAXException {
+ delegate.endDocument();
}
@Override
- public void startDocument() throws SAXException {
- delegate.startDocument();
+ public void endDTD() throws SAXException {
+ delegate.endDTD();
}
@Override
- public void endDocument() throws SAXException {
- delegate.endDocument();
+ public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+ delegate.endElement(uri, localName, qName);
}
@Override
- public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
- delegate.startPrefixMapping(prefix, uri);
+ public void endEntity(final String name) throws SAXException {
+ delegate.endEntity(name);
}
@Override
@@ -97,23 +92,23 @@ public void endPrefixMapping(final String prefix) throws SAXException {
}
@Override
- public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
- delegate.startElement(uri, localName, qName, atts);
+ public String getSystemId() {
+ return delegate.getSystemId();
}
@Override
- public void endElement(final String uri, final String localName, final String qName) throws SAXException {
- delegate.endElement(uri, localName, qName);
+ public Transformer getTransformer() {
+ return transformer;
}
@Override
- public void characters(final char[] ch, final int start, final int length) throws SAXException {
- delegate.characters(ch, start, length);
+ public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.ignorableWhitespace(ch, start, length);
}
@Override
- public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
- delegate.ignorableWhitespace(ch, start, length);
+ public void notationDecl(final String name, final String publicId, final String systemId) throws SAXException {
+ delegate.notationDecl(name, publicId, systemId);
}
@Override
@@ -122,53 +117,58 @@ public void processingInstruction(final String target, final String data) throws
}
@Override
- public void skippedEntity(final String name) throws SAXException {
- delegate.skippedEntity(name);
+ public void setDocumentLocator(final Locator locator) {
+ delegate.setDocumentLocator(locator);
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public void notationDecl(final String name, final String publicId, final String systemId) throws SAXException {
- delegate.notationDecl(name, publicId, systemId);
+ public void setResult(final Result result) {
+ delegate.setResult(result);
}
@Override
- public void unparsedEntityDecl(final String name, final String publicId, final String systemId, final String notationName) throws SAXException {
- delegate.unparsedEntityDecl(name, publicId, systemId, notationName);
+ public void setSystemId(final String systemID) {
+ delegate.setSystemId(systemID);
}
@Override
- public void startDTD(final String name, final String publicId, final String systemId) throws SAXException {
- delegate.startDTD(name, publicId, systemId);
+ public void skippedEntity(final String name) throws SAXException {
+ delegate.skippedEntity(name);
}
@Override
- public void endDTD() throws SAXException {
- delegate.endDTD();
+ public void startCDATA() throws SAXException {
+ delegate.startCDATA();
}
@Override
- public void startEntity(final String name) throws SAXException {
- delegate.startEntity(name);
+ public void startDocument() throws SAXException {
+ delegate.startDocument();
}
@Override
- public void endEntity(final String name) throws SAXException {
- delegate.endEntity(name);
+ public void startDTD(final String name, final String publicId, final String systemId) throws SAXException {
+ delegate.startDTD(name, publicId, systemId);
}
@Override
- public void startCDATA() throws SAXException {
- delegate.startCDATA();
+ public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+ delegate.startElement(uri, localName, qName, atts);
}
@Override
- public void endCDATA() throws SAXException {
- delegate.endCDATA();
+ public void startEntity(final String name) throws SAXException {
+ delegate.startEntity(name);
}
@Override
- public void comment(final char[] ch, final int start, final int length) throws SAXException {
- delegate.comment(ch, start, length);
+ public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+ delegate.startPrefixMapping(prefix, uri);
+ }
+
+ @Override
+ public void unparsedEntityDecl(final String name, final String publicId, final String systemId, final String notationName) throws SAXException {
+ delegate.unparsedEntityDecl(name, publicId, systemId, notationName);
}
- // </editor-fold>
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java b/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java
index ea25b5b..8cb9c44 100644
--- a/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java
+++ b/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java
@@ -47,29 +47,28 @@ final class HardeningValidatorHandler extends ValidatorHandler {
}
@Override
- public void setResourceResolver(final LSResourceResolver resourceResolver) {
- floor.setDelegate(resourceResolver);
+ public void characters(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.characters(ch, start, length);
}
@Override
- public LSResourceResolver getResourceResolver() {
- return floor.getDelegate();
+ public void endDocument() throws SAXException {
+ delegate.endDocument();
}
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public void setContentHandler(final ContentHandler receiver) {
- delegate.setContentHandler(receiver);
+ public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+ delegate.endElement(uri, localName, qName);
}
@Override
- public ContentHandler getContentHandler() {
- return delegate.getContentHandler();
+ public void endPrefixMapping(final String prefix) throws SAXException {
+ delegate.endPrefixMapping(prefix);
}
@Override
- public void setErrorHandler(final ErrorHandler errorHandler) {
- delegate.setErrorHandler(errorHandler);
+ public ContentHandler getContentHandler() {
+ return delegate.getContentHandler();
}
@Override
@@ -78,83 +77,84 @@ public ErrorHandler getErrorHandler() {
}
@Override
- public TypeInfoProvider getTypeInfoProvider() {
- return delegate.getTypeInfoProvider();
+ public boolean getFeature(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ return delegate.getFeature(name);
}
@Override
- public boolean getFeature(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
- return delegate.getFeature(name);
+ public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ return delegate.getProperty(name);
}
@Override
- public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
- delegate.setFeature(name, value);
+ public LSResourceResolver getResourceResolver() {
+ return floor.getDelegate();
}
@Override
- public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
- return delegate.getProperty(name);
+ public TypeInfoProvider getTypeInfoProvider() {
+ return delegate.getTypeInfoProvider();
}
@Override
- public void setProperty(final String name, final Object object) throws SAXNotRecognizedException, SAXNotSupportedException {
- delegate.setProperty(name, object);
+ public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.ignorableWhitespace(ch, start, length);
}
@Override
- public void setDocumentLocator(final Locator locator) {
- delegate.setDocumentLocator(locator);
+ public void processingInstruction(final String target, final String data) throws SAXException {
+ delegate.processingInstruction(target, data);
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public void startDocument() throws SAXException {
- delegate.startDocument();
+ public void setContentHandler(final ContentHandler receiver) {
+ delegate.setContentHandler(receiver);
}
@Override
- public void endDocument() throws SAXException {
- delegate.endDocument();
+ public void setDocumentLocator(final Locator locator) {
+ delegate.setDocumentLocator(locator);
}
@Override
- public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
- delegate.startPrefixMapping(prefix, uri);
+ public void setErrorHandler(final ErrorHandler errorHandler) {
+ delegate.setErrorHandler(errorHandler);
}
@Override
- public void endPrefixMapping(final String prefix) throws SAXException {
- delegate.endPrefixMapping(prefix);
+ public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ delegate.setFeature(name, value);
}
@Override
- public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
- delegate.startElement(uri, localName, qName, atts);
+ public void setProperty(final String name, final Object object) throws SAXNotRecognizedException, SAXNotSupportedException {
+ delegate.setProperty(name, object);
}
@Override
- public void endElement(final String uri, final String localName, final String qName) throws SAXException {
- delegate.endElement(uri, localName, qName);
+ public void setResourceResolver(final LSResourceResolver resourceResolver) {
+ floor.setDelegate(resourceResolver);
}
@Override
- public void characters(final char[] ch, final int start, final int length) throws SAXException {
- delegate.characters(ch, start, length);
+ public void skippedEntity(final String name) throws SAXException {
+ delegate.skippedEntity(name);
}
+ // </editor-fold>
@Override
- public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
- delegate.ignorableWhitespace(ch, start, length);
+ public void startDocument() throws SAXException {
+ delegate.startDocument();
}
@Override
- public void processingInstruction(final String target, final String data) throws SAXException {
- delegate.processingInstruction(target, data);
+ public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+ delegate.startElement(uri, localName, qName, atts);
}
@Override
- public void skippedEntity(final String name) throws SAXException {
- delegate.skippedEntity(name);
+ public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+ delegate.startPrefixMapping(prefix, uri);
}
- // </editor-fold>
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
index 94cf4b5..a806018 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
@@ -51,6 +51,17 @@
*/
final class HardeningXMLInputFactory extends XMLInputFactory {
+ private static boolean isResolverProperty(final String name) {
+ return XMLInputFactory.RESOLVER.equals(name)
+ || StaxHardener.WSTX_DTD_RESOLVER.equals(name)
+ || StaxHardener.WSTX_ENTITY_RESOLVER.equals(name)
+ || StaxHardener.WSTX_UNDECLARED_ENTITY_RESOLVER.equals(name);
+ }
+
+ private static XMLResolver unwrap(final XMLResolver resolver) {
+ return resolver instanceof FallbackIgnoreXMLResolver ? ((FallbackIgnoreXMLResolver) resolver).getDelegate() : resolver;
+ }
+
private final XMLInputFactory delegate;
HardeningXMLInputFactory(final XMLInputFactory delegate) {
@@ -59,73 +70,48 @@ final class HardeningXMLInputFactory extends XMLInputFactory {
}
@Override
- public void setXMLResolver(final XMLResolver resolver) {
- setResolverProperty(XMLInputFactory.RESOLVER, resolver);
+ public XMLEventReader createFilteredReader(final XMLEventReader reader, final EventFilter filter) throws XMLStreamException {
+ return delegate.createFilteredReader(reader, filter);
}
@Override
- public XMLResolver getXMLResolver() {
- return unwrap(delegate.getXMLResolver());
+ public XMLStreamReader createFilteredReader(final XMLStreamReader reader, final StreamFilter filter) throws XMLStreamException {
+ return delegate.createFilteredReader(reader, filter);
}
@Override
- public void setProperty(final String name, final Object value) {
- // If a resolver property has a value of the wrong type, pass it to the delegate to generate an appropriate exception.
- if (isResolverProperty(name) && (value == null || value instanceof XMLResolver)) {
- setResolverProperty(name, (XMLResolver) value);
- } else {
- delegate.setProperty(name, value);
- }
+ public XMLEventReader createXMLEventReader(final InputStream stream) throws XMLStreamException {
+ return delegate.createXMLEventReader(stream);
}
@Override
- public Object getProperty(final String name) {
- if (isResolverProperty(name)) {
- return unwrap((XMLResolver) delegate.getProperty(name));
- }
- return delegate.getProperty(name);
+ public XMLEventReader createXMLEventReader(final InputStream stream, final String encoding) throws XMLStreamException {
+ return delegate.createXMLEventReader(stream, encoding);
}
- /**
- * Routes a caller-set resolver for the property {@code name} behind the floor currently installed on that hook.
- *
- * @param name The resolver-valued property being set.
- * @param resolver The caller's resolver, or their own {@link FallbackIgnoreXMLResolver} to take control.
- */
- private void setResolverProperty(final String name, final XMLResolver resolver) {
- if (resolver instanceof FallbackIgnoreXMLResolver) {
- // The caller supplies their own floor: hand it to the delegate as-is.
- delegate.setProperty(name, resolver);
- } else {
- final Object current = delegate.getProperty(name);
- if (current instanceof FallbackIgnoreXMLResolver) {
- ((FallbackIgnoreXMLResolver) current).setDelegate(resolver);
- } else {
- delegate.setProperty(name, new FallbackIgnoreXMLResolver(resolver));
- }
- }
+ @Override
+ public XMLEventReader createXMLEventReader(final Reader reader) throws XMLStreamException {
+ return delegate.createXMLEventReader(reader);
}
- private static boolean isResolverProperty(final String name) {
- return XMLInputFactory.RESOLVER.equals(name)
- || StaxHardener.WSTX_DTD_RESOLVER.equals(name)
- || StaxHardener.WSTX_ENTITY_RESOLVER.equals(name)
- || StaxHardener.WSTX_UNDECLARED_ENTITY_RESOLVER.equals(name);
+ @Override
+ public XMLEventReader createXMLEventReader(final Source source) throws XMLStreamException {
+ return delegate.createXMLEventReader(source);
}
- private static XMLResolver unwrap(final XMLResolver resolver) {
- return resolver instanceof FallbackIgnoreXMLResolver ? ((FallbackIgnoreXMLResolver) resolver).getDelegate() : resolver;
+ @Override
+ public XMLEventReader createXMLEventReader(final String systemId, final InputStream stream) throws XMLStreamException {
+ return delegate.createXMLEventReader(systemId, stream);
}
- // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
- return delegate.createXMLStreamReader(reader);
+ public XMLEventReader createXMLEventReader(final String systemId, final Reader reader) throws XMLStreamException {
+ return delegate.createXMLEventReader(systemId, reader);
}
@Override
- public XMLStreamReader createXMLStreamReader(final Source source) throws XMLStreamException {
- return delegate.createXMLStreamReader(source);
+ public XMLEventReader createXMLEventReader(final XMLStreamReader reader) throws XMLStreamException {
+ return delegate.createXMLEventReader(reader);
}
@Override
@@ -138,64 +124,89 @@ public XMLStreamReader createXMLStreamReader(final InputStream stream, final Str
return delegate.createXMLStreamReader(stream, encoding);
}
+ // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
- public XMLStreamReader createXMLStreamReader(final String systemId, final InputStream stream) throws XMLStreamException {
- return delegate.createXMLStreamReader(systemId, stream);
+ public XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
+ return delegate.createXMLStreamReader(reader);
}
@Override
- public XMLStreamReader createXMLStreamReader(final String systemId, final Reader reader) throws XMLStreamException {
- return delegate.createXMLStreamReader(systemId, reader);
+ public XMLStreamReader createXMLStreamReader(final Source source) throws XMLStreamException {
+ return delegate.createXMLStreamReader(source);
}
@Override
- public XMLEventReader createXMLEventReader(final Reader reader) throws XMLStreamException {
- return delegate.createXMLEventReader(reader);
+ public XMLStreamReader createXMLStreamReader(final String systemId, final InputStream stream) throws XMLStreamException {
+ return delegate.createXMLStreamReader(systemId, stream);
}
@Override
- public XMLEventReader createXMLEventReader(final String systemId, final Reader reader) throws XMLStreamException {
- return delegate.createXMLEventReader(systemId, reader);
+ public XMLStreamReader createXMLStreamReader(final String systemId, final Reader reader) throws XMLStreamException {
+ return delegate.createXMLStreamReader(systemId, reader);
}
@Override
- public XMLEventReader createXMLEventReader(final XMLStreamReader reader) throws XMLStreamException {
- return delegate.createXMLEventReader(reader);
+ public XMLEventAllocator getEventAllocator() {
+ return delegate.getEventAllocator();
}
+ // </editor-fold>
@Override
- public XMLEventReader createXMLEventReader(final Source source) throws XMLStreamException {
- return delegate.createXMLEventReader(source);
+ public Object getProperty(final String name) {
+ if (isResolverProperty(name)) {
+ return unwrap((XMLResolver) delegate.getProperty(name));
+ }
+ return delegate.getProperty(name);
}
@Override
- public XMLEventReader createXMLEventReader(final InputStream stream) throws XMLStreamException {
- return delegate.createXMLEventReader(stream);
+ public XMLReporter getXMLReporter() {
+ return delegate.getXMLReporter();
}
@Override
- public XMLEventReader createXMLEventReader(final InputStream stream, final String encoding) throws XMLStreamException {
- return delegate.createXMLEventReader(stream, encoding);
+ public XMLResolver getXMLResolver() {
+ return unwrap(delegate.getXMLResolver());
}
@Override
- public XMLEventReader createXMLEventReader(final String systemId, final InputStream stream) throws XMLStreamException {
- return delegate.createXMLEventReader(systemId, stream);
+ public boolean isPropertySupported(final String name) {
+ return delegate.isPropertySupported(name);
}
@Override
- public XMLStreamReader createFilteredReader(final XMLStreamReader reader, final StreamFilter filter) throws XMLStreamException {
- return delegate.createFilteredReader(reader, filter);
+ public void setEventAllocator(final XMLEventAllocator allocator) {
+ delegate.setEventAllocator(allocator);
}
@Override
- public XMLEventReader createFilteredReader(final XMLEventReader reader, final EventFilter filter) throws XMLStreamException {
- return delegate.createFilteredReader(reader, filter);
+ public void setProperty(final String name, final Object value) {
+ // If a resolver property has a value of the wrong type, pass it to the delegate to generate an appropriate exception.
+ if (isResolverProperty(name) && (value == null || value instanceof XMLResolver)) {
+ setResolverProperty(name, (XMLResolver) value);
+ } else {
+ delegate.setProperty(name, value);
+ }
}
- @Override
- public XMLReporter getXMLReporter() {
- return delegate.getXMLReporter();
+ /**
+ * Routes a caller-set resolver for the property {@code name} behind the floor currently installed on that hook.
+ *
+ * @param name The resolver-valued property being set.
+ * @param resolver The caller's resolver, or their own {@link FallbackIgnoreXMLResolver} to take control.
+ */
+ private void setResolverProperty(final String name, final XMLResolver resolver) {
+ if (resolver instanceof FallbackIgnoreXMLResolver) {
+ // The caller supplies their own floor: hand it to the delegate as-is.
+ delegate.setProperty(name, resolver);
+ } else {
+ final Object current = delegate.getProperty(name);
+ if (current instanceof FallbackIgnoreXMLResolver) {
+ ((FallbackIgnoreXMLResolver) current).setDelegate(resolver);
+ } else {
+ delegate.setProperty(name, new FallbackIgnoreXMLResolver(resolver));
+ }
+ }
}
@Override
@@ -204,18 +215,7 @@ public void setXMLReporter(final XMLReporter reporter) {
}
@Override
- public boolean isPropertySupported(final String name) {
- return delegate.isPropertySupported(name);
- }
-
- @Override
- public void setEventAllocator(final XMLEventAllocator allocator) {
- delegate.setEventAllocator(allocator);
- }
-
- @Override
- public XMLEventAllocator getEventAllocator() {
- return delegate.getEventAllocator();
+ public void setXMLResolver(final XMLResolver resolver) {
+ setResolverProperty(XMLInputFactory.RESOLVER, resolver);
}
- // </editor-fold>
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLReader.java b/src/main/java/org/apache/commons/xml/HardeningXMLReader.java
index 7621571..7899ed5 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXMLReader.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXMLReader.java
@@ -51,16 +51,6 @@ class HardeningXMLReader implements XMLReader {
delegate.setEntityResolver(floor);
}
- @Override
- public void setEntityResolver(final EntityResolver resolver) {
- floor.setDelegate(resolver);
- }
-
- @Override
- public EntityResolver getEntityResolver() {
- return floor.getDelegate();
- }
-
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public ContentHandler getContentHandler() {
@@ -72,6 +62,11 @@ public DTDHandler getDTDHandler() {
return delegate.getDTDHandler();
}
+ @Override
+ public EntityResolver getEntityResolver() {
+ return floor.getDelegate();
+ }
+
@Override
public ErrorHandler getErrorHandler() {
return delegate.getErrorHandler();
@@ -107,6 +102,11 @@ public void setDTDHandler(final DTDHandler handler) {
delegate.setDTDHandler(handler);
}
+ @Override
+ public void setEntityResolver(final EntityResolver resolver) {
+ floor.setDelegate(resolver);
+ }
+
@Override
public void setErrorHandler(final ErrorHandler handler) {
delegate.setErrorHandler(handler);
diff --git a/src/main/java/org/apache/commons/xml/HardeningXPath.java b/src/main/java/org/apache/commons/xml/HardeningXPath.java
index 8db5586..13cef3a 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXPath.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXPath.java
@@ -77,6 +77,12 @@ static Document parse(final InputSource source) throws XPathExpressionException
this.delegate = Objects.requireNonNull(delegate, "delegate");
}
+ @Override
+ public XPathExpression compile(final String expression) throws XPathExpressionException {
+ final XPathExpression compiled = delegate.compile(expression);
+ return compiled == null ? null : new HardeningXPathExpression(compiled);
+ }
+
@Override
public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
return delegate.evaluate(expression, parse(source));
@@ -87,12 +93,6 @@ public Object evaluate(final String expression, final InputSource source, final
return delegate.evaluate(expression, parse(source), returnType);
}
- @Override
- public XPathExpression compile(final String expression) throws XPathExpressionException {
- final XPathExpression compiled = delegate.compile(expression);
- return compiled == null ? null : new HardeningXPathExpression(compiled);
- }
-
@Override
public String evaluate(final String expression, final Object item) throws XPathExpressionException {
return delegate.evaluate(expression, item);
diff --git a/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java b/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
index c6d4cb0..a697032 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
@@ -40,12 +40,6 @@ final class HardeningXPathFactory extends XPathFactory {
this.delegate = Objects.requireNonNull(delegate, "delegate");
}
- @Override
- public XPath newXPath() {
- final XPath xpath = delegate.newXPath();
- return xpath == null ? null : new HardeningXPath(xpath);
- }
-
@Override
public boolean getFeature(final String name) throws XPathFactoryConfigurationException {
return delegate.getFeature(name);
@@ -56,6 +50,12 @@ public boolean isObjectModelSupported(final String objectModel) {
return delegate.isObjectModelSupported(objectModel);
}
+ @Override
+ public XPath newXPath() {
+ final XPath xpath = delegate.newXPath();
+ return xpath == null ? null : new HardeningXPath(xpath);
+ }
+
@Override
public void setFeature(final String name, final boolean value) throws XPathFactoryConfigurationException {
delegate.setFeature(name, value);
diff --git a/src/main/java/org/apache/commons/xml/SaxonProvider.java b/src/main/java/org/apache/commons/xml/SaxonProvider.java
index 2b600ba..f2af427 100644
--- a/src/main/java/org/apache/commons/xml/SaxonProvider.java
+++ b/src/main/java/org/apache/commons/xml/SaxonProvider.java
@@ -43,18 +43,6 @@
*/
final class SaxonProvider {
- /**
- * Tells whether the factory class is Saxon's, by package prefix, so public subclasses such as {@code net.sf.saxon.BasicTransformerFactory} route to the
- * same locked-down {@link Configuration} as the factory registered for JAXP lookup.
- *
- * @param factoryClass The factory implementation class.
- * @return Whether the class lives in Saxon's open-source or commercial packages.
- */
- static boolean isSaxon(final Class<?> factoryClass) {
- final String name = factoryClass.getName();
- return name.startsWith("net.sf.saxon.") || name.startsWith("com.saxonica.");
- }
-
/**
* A Saxon {@link Configuration} carrying the vendor-specific restrictions that the standard JAXP knobs cannot express.
*
@@ -123,15 +111,6 @@ private static XPathFactory configure(final XPathFactory factory) {
}
}
- /**
- * The empty-{@link Source} shape Saxon's consumers expect, for the {@link FallbackIgnoreURIResolver} floor the TrAX wrapper installs.
- *
- * @return a supplier for Saxon's empty {@link Source}.
- */
- static Supplier<Source> emptySourceSupplier() {
- return EmptySource::getInstance;
- }
-
static TransformerFactory configure(final TransformerFactory factory) {
try {
return SaxonProviderConfigurer.configure(factory);
@@ -156,6 +135,27 @@ static XPathFactory configure(final XPathFactory factory) {
}
}
+ /**
+ * The empty-{@link Source} shape Saxon's consumers expect, for the {@link FallbackIgnoreURIResolver} floor the TrAX wrapper installs.
+ *
+ * @return a supplier for Saxon's empty {@link Source}.
+ */
+ static Supplier<Source> emptySourceSupplier() {
+ return EmptySource::getInstance;
+ }
+
+ /**
+ * Tells whether the factory class is Saxon's, by package prefix, so public subclasses such as {@code net.sf.saxon.BasicTransformerFactory} route to the
+ * same locked-down {@link Configuration} as the factory registered for JAXP lookup.
+ *
+ * @param factoryClass The factory implementation class.
+ * @return Whether the class lives in Saxon's open-source or commercial packages.
+ */
+ static boolean isSaxon(final Class<?> factoryClass) {
+ final String name = factoryClass.getName();
+ return name.startsWith("net.sf.saxon.") || name.startsWith("com.saxonica.");
+ }
+
private SaxonProvider() {
}
}
diff --git a/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java b/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
index c4bb03f..3a553fd 100644
--- a/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
+++ b/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
@@ -41,6 +41,14 @@
@Tag("trax")
class AssociatedStylesheetTest {
+ /** The PI was found (non-null); where the engine exposes a system id, it points at the declared stylesheet. */
+ private static void assertAssociatedStylesheet(final Source associated) {
+ assertNotNull(associated, "expected the associated stylesheet PI to be found");
+ if (associated.getSystemId() != null) {
+ assertTrue(associated.getSystemId().endsWith("included.xsl"), "unexpected associated stylesheet: " + associated.getSystemId());
+ }
+ }
+
private static TransformerFactory hardenedFactory() {
final TransformerFactory factory = XmlFactories.newTransformerFactory();
factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
@@ -65,14 +73,6 @@ void hardenedGetAssociatedStylesheetReturnsStylesheet() throws TransformerConfig
assertAssociatedStylesheet(associated);
}
- /** The PI was found (non-null); where the engine exposes a system id, it points at the declared stylesheet. */
- private static void assertAssociatedStylesheet(final Source associated) {
- assertNotNull(associated, "expected the associated stylesheet PI to be found");
- if (associated.getSystemId() != null) {
- assertTrue(associated.getSystemId().endsWith("included.xsl"), "unexpected associated stylesheet: " + associated.getSystemId());
- }
- }
-
@Test
void unconfiguredGetAssociatedStylesheetFetchesExternalDtd() {
// Leak/discrimination control: the unconfigured engine attempts to fetch the unreachable external DTD and fails. Android's KXmlParser does not fetch
diff --git a/src/test/java/org/apache/commons/xml/AttackTestSupport.java b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
index b7c4811..a5d87b2 100644
--- a/src/test/java/org/apache/commons/xml/AttackTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
@@ -746,11 +746,6 @@ private static String blockedDescription(final String description) {
return "Hardening did not block " + description + "; parse completed successfully.";
}
- /** Parses the payload through the supplied reader and returns the accumulated character data, used by the SAX-based {@code DoesNotLeak} helpers. */
- static String captureCharacters(final XMLReader reader, final String payload) throws Exception {
- return captureCharacters(reader, inputSource(payload));
- }
-
/** Parses the source through the supplied reader, with {@link #STRICT_REPORTER} installed, and returns the accumulated character data. */
static String captureCharacters(final XMLReader reader, final InputSource source) throws Exception {
final StringBuilder text = new StringBuilder();
@@ -759,6 +754,11 @@ static String captureCharacters(final XMLReader reader, final InputSource source
return text.toString();
}
+ /** Parses the payload through the supplied reader and returns the accumulated character data, used by the SAX-based {@code DoesNotLeak} helpers. */
+ static String captureCharacters(final XMLReader reader, final String payload) throws Exception {
+ return captureCharacters(reader, inputSource(payload));
+ }
+
/** Parses the payload through a {@link XMLEventReader} and returns the accumulated character and CDATA data, used by the StAX-based {@code DoesNotLeak} helper. */
static String captureStaxEventText(final XMLInputFactory factory, final String payload) throws Exception {
final StringBuilder text = new StringBuilder();
@@ -883,13 +883,6 @@ private static void liftEntityLimits(final XMLReader reader) {
}
}
- /**
- * Builds a {@link SAXSource} wrapping the payload, without an explicit parser; used by the unconfigured-side TrAX controls.
- */
- private static SAXSource permissiveSaxSource(final String xml) {
- return new SAXSource(permissiveReader(), new InputSource(new StringReader(xml)));
- }
-
/**
* A permissive, namespace-aware {@link XMLReader} with every entity-expansion limit lifted, for the unconfigured-side controls.
*
@@ -907,6 +900,13 @@ static XMLReader permissiveReader() {
return IS_ANDROID ? new PermissiveExpatReader(reader) : reader;
}
+ /**
+ * Builds a {@link SAXSource} wrapping the payload, without an explicit parser; used by the unconfigured-side TrAX controls.
+ */
+ private static SAXSource permissiveSaxSource(final String xml) {
+ return new SAXSource(permissiveReader(), new InputSource(new StringReader(xml)));
+ }
+
private static boolean probeAndroid() {
try {
Class.forName("android.os.Build");
@@ -985,20 +985,20 @@ private static Templates strictTemplates(final TransformerFactory factory, final
}
/**
- * Builds an identity {@link Transformer} from {@code factory} with {@link #STRICT_REPORTER} installed on both the factory and the resulting transformer.
+ * Builds a {@link Transformer} from {@code templates} with {@link #STRICT_REPORTER} installed as its error listener.
*/
- private static Transformer strictTransformer(final TransformerFactory factory) throws TransformerConfigurationException {
- factory.setErrorListener(STRICT_REPORTER);
- final Transformer transformer = factory.newTransformer();
+ private static Transformer strictTransformer(final Templates templates) throws TransformerConfigurationException {
+ final Transformer transformer = templates.newTransformer();
transformer.setErrorListener(STRICT_REPORTER);
return transformer;
}
/**
- * Builds a {@link Transformer} from {@code templates} with {@link #STRICT_REPORTER} installed as its error listener.
+ * Builds an identity {@link Transformer} from {@code factory} with {@link #STRICT_REPORTER} installed on both the factory and the resulting transformer.
*/
- private static Transformer strictTransformer(final Templates templates) throws TransformerConfigurationException {
- final Transformer transformer = templates.newTransformer();
+ private static Transformer strictTransformer(final TransformerFactory factory) throws TransformerConfigurationException {
+ factory.setErrorListener(STRICT_REPORTER);
+ final Transformer transformer = factory.newTransformer();
transformer.setErrorListener(STRICT_REPORTER);
return transformer;
}
diff --git a/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java b/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
index fb84f99..975ab47 100644
--- a/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
+++ b/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
@@ -38,16 +38,16 @@ class DenyUnresolvedTest {
private static final String SYSTEM_ID = "http://invalid.example.invalid/external.dtd";
- @BeforeEach
- void enableThrowOnUnresolved() {
- System.setProperty(XmlFactories.THROW_ON_UNRESOLVED, "true");
- }
-
@AfterEach
void clearThrowOnUnresolved() {
System.clearProperty(XmlFactories.THROW_ON_UNRESOLVED);
}
+ @BeforeEach
+ void enableThrowOnUnresolved() {
+ System.setProperty(XmlFactories.THROW_ON_UNRESOLVED, "true");
+ }
+
@Test
void floorsThrowOnUnresolved() {
assertThrows(SAXException.class, () -> new FallbackIgnoreEntityResolver2(null).resolveEntity(null, SYSTEM_ID),
diff --git a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
index 529f6ea..e8fe90f 100644
--- a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
+++ b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
@@ -74,12 +74,57 @@ class EntityResolverFloorTest {
private static final EntityResolver ENTITY_ALLOW_LIST = (publicId, systemId) ->
ALLOWED.equals(systemId) ? new InputSource(new URL(systemId).openStream()) : null;
+ /**
+ * Allow-all resolver: it denies nothing, resolving whatever {@code systemId} it is handed by opening it as a URL. It nonetheless cannot resolve a bare
+ * relative reference such as {@code referenced.xml}, because a plain {@link EntityResolver} (unlike {@link org.xml.sax.ext.EntityResolver2}) is given no
+ * base URI and the SAX2 contract promises it an already-absolutized {@code systemId}. So the resolution fails not from any deny decision but because the
+ * resolver was never handed the whole URL: it succeeds only if the floor absolutizes the XInclude href against the base before consulting the caller.
+ */
+ private static final EntityResolver RESOLVE_ALL = (publicId, systemId) -> {
+ final InputSource source = new InputSource(new URL(systemId).openStream());
+ source.setSystemId(systemId);
+ return source;
+ };
+
+ /** Absolute URL of the host document whose {@code xi:include} references {@code referenced.xml} by a relative href. */
+ private static final String XINCLUDE_HOST = AttackTestSupport.resourceUrl("with-xinclude.xml").toString();
+
+ /** Resolves only {@link #ALLOWED} to its content stream; returns {@code null} for anything else. */
+ private static final XMLResolver STAX_ALLOW_LIST = (publicID, systemID, baseURI, namespace) -> {
+ if (!ALLOWED.equals(systemID)) {
+ return null;
+ }
+ try {
+ return new URL(systemID).openStream();
+ } catch (final java.io.IOException e) {
+ throw new XMLStreamException(e);
+ }
+ };
+
+ /** Absolute location of the imported schema the allow-list resolver permits. */
+ private static final String ALLOWED_SCHEMA = AttackTestSupport.resourceUrl("included.xsd").toString();
+
+ /** Resolves only the {@code included.xsd} import; returns {@code null} for anything else. */
+ private static final LSResourceResolver SCHEMA_ALLOW_LIST = (type, namespaceURI, publicId, systemId, baseURI) ->
+ systemId != null && systemId.endsWith("included.xsd") ? lsInput(ALLOWED_SCHEMA) : null;
+
+ /** Resolves only the {@code included.xsl} import; returns {@code null} for anything else. */
+ private static final URIResolver XSL_ALLOW_LIST = (href, base) ->
+ href != null && href.endsWith("included.xsl") ? AttackTestSupport.resourceSource("included.xsl") : null;
+
private static String entityPayload(final String entitySystemId) {
return "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE root [\n <!ENTITY xxe SYSTEM \"" + entitySystemId + "\">\n]>\n"
+ "<root>&xxe;</root>";
}
+ private static XMLInputFactory externalEntityStaxFactory() {
+ final XMLInputFactory factory = XmlFactories.newXMLInputFactory();
+ factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
+ factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
+ return factory;
+ }
+
private static DocumentBuilder hardenedBuilder() throws Exception {
final DocumentBuilder builder = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder();
builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
@@ -92,15 +137,52 @@ private static XMLReader hardenedReader() throws Exception {
return reader;
}
- @Test
- @Tag("dom")
- void domResolvesAllowListed() throws Exception {
- Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES, "platform DOM does not resolve user-defined entities");
- final DocumentBuilder builder = hardenedBuilder();
- builder.setEntityResolver(ENTITY_ALLOW_LIST);
- final Document doc = builder.parse(AttackTestSupport.inputSource(entityPayload(ALLOWED)));
- assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
- "allow-listed external entity should resolve through the caller's resolver");
+ /**
+ * A hardened {@link TransformerFactory} with a re-throwing error listener. XSLTC and Xalan enforce the block through the
+ * {@link FallbackIgnoreURIResolver} floor; Saxon enforces it through the ignore-all resolver floor on its {@code Configuration}. Either way a caller-set
+ * resolver that returns {@code null} cannot re-open the fetch. The strict listener turns any reported-and-recovered error into a test failure, so an
+ * implementation cannot quietly recover from a floor resolution while the test asserts clean completion.
+ */
+ private static TransformerFactory hardenedTransformerFactory() {
+ final TransformerFactory factory = XmlFactories.newTransformerFactory();
+ factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
+ return factory;
+ }
+
+ /** An {@link LSInput} naming the resource but carrying no content: a redirect the implementation fetches itself, like an identifier-only {@code InputSource}. */
+ private static LSInput identifierOnlyLsInput(final String systemId) {
+ return assertDoesNotThrow(() -> {
+ final DOMImplementationLS ls = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
+ final LSInput input = ls.createLSInput();
+ input.setSystemId(systemId);
+ return input;
+ }, "Failed to build identifier-only LSInput for " + systemId);
+ }
+
+ private static LSInput lsInput(final String systemId) {
+ return assertDoesNotThrow(() -> {
+ final LSInput input = identifierOnlyLsInput(systemId);
+ input.setByteStream(new URL(systemId).openStream());
+ return input;
+ }, "Failed to build LSInput for " + systemId);
+ }
+
+ private static DocumentBuilder xIncludeAwareBuilder() throws Exception {
+ final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
+ factory.setNamespaceAware(true);
+ AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+ return builder;
+ }
+
+ private static XMLReader xIncludeAwareReader() throws Exception {
+ final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ factory.setNamespaceAware(true);
+ AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
+ final XMLReader reader = factory.newSAXParser().getXMLReader();
+ reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+ return reader;
}
@Test
@@ -120,28 +202,24 @@ void domDoesNotLeakUnlisted() throws Exception {
}
@Test
- @Tag("sax")
- void saxReaderResolvesAllowListed() throws Exception {
- final XMLReader reader = hardenedReader();
- reader.setEntityResolver(ENTITY_ALLOW_LIST);
- final String text = AttackTestSupport.captureCharacters(reader, entityPayload(ALLOWED));
- assertTrue(text.contains(AttackTestSupport.LEAKED_MARKER),
+ @Tag("dom")
+ void domResolvesAllowListed() throws Exception {
+ Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES, "platform DOM does not resolve user-defined entities");
+ final DocumentBuilder builder = hardenedBuilder();
+ builder.setEntityResolver(ENTITY_ALLOW_LIST);
+ final Document doc = builder.parse(AttackTestSupport.inputSource(entityPayload(ALLOWED)));
+ assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
"allow-listed external entity should resolve through the caller's resolver");
}
@Test
- @Tag("sax")
- void saxReaderDoesNotLeakUnlisted() throws Exception {
- final XMLReader reader = hardenedReader();
- reader.setEntityResolver(ENTITY_ALLOW_LIST);
- // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
- final String text;
- try {
- text = AttackTestSupport.captureCharacters(reader, entityPayload(UNLISTED));
- } catch (final SAXException blocked) {
- return; // Acceptable: rejected at parse rather than resolved to empty.
- }
- assertFalse(text.contains(AttackTestSupport.LEAKED_MARKER), "unlisted external entity leaked:\n" + text);
+ @Tag("dom")
+ void domResolvesRelativeXIncludeSibling() throws Exception {
+ final DocumentBuilder builder = xIncludeAwareBuilder();
+ builder.setEntityResolver(RESOLVE_ALL);
+ final Document doc = builder.parse(XINCLUDE_HOST);
+ assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
+ "relative XInclude sibling should resolve through the caller's resolver after the floor absolutizes the href");
}
@Test
@@ -159,26 +237,29 @@ void saxParseWithHandlerDoesNotBypass() throws Exception {
assertFalse(text.toString().contains(AttackTestSupport.LEAKED_MARKER), "parse(source, handler) leaked the external entity:\n" + text);
}
- /**
- * Allow-all resolver: it denies nothing, resolving whatever {@code systemId} it is handed by opening it as a URL. It nonetheless cannot resolve a bare
- * relative reference such as {@code referenced.xml}, because a plain {@link EntityResolver} (unlike {@link org.xml.sax.ext.EntityResolver2}) is given no
- * base URI and the SAX2 contract promises it an already-absolutized {@code systemId}. So the resolution fails not from any deny decision but because the
- * resolver was never handed the whole URL: it succeeds only if the floor absolutizes the XInclude href against the base before consulting the caller.
- */
- private static final EntityResolver RESOLVE_ALL = (publicId, systemId) -> {
- final InputSource source = new InputSource(new URL(systemId).openStream());
- source.setSystemId(systemId);
- return source;
- };
+ @Test
+ @Tag("sax")
+ void saxReaderDoesNotLeakUnlisted() throws Exception {
+ final XMLReader reader = hardenedReader();
+ reader.setEntityResolver(ENTITY_ALLOW_LIST);
+ // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
+ final String text;
+ try {
+ text = AttackTestSupport.captureCharacters(reader, entityPayload(UNLISTED));
+ } catch (final SAXException blocked) {
+ return; // Acceptable: rejected at parse rather than resolved to empty.
+ }
+ assertFalse(text.contains(AttackTestSupport.LEAKED_MARKER), "unlisted external entity leaked:\n" + text);
+ }
@Test
- @Tag("dom")
- void domResolvesRelativeXIncludeSibling() throws Exception {
- final DocumentBuilder builder = xIncludeAwareBuilder();
- builder.setEntityResolver(RESOLVE_ALL);
- final Document doc = builder.parse(XINCLUDE_HOST);
- assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
- "relative XInclude sibling should resolve through the caller's resolver after the floor absolutizes the href");
+ @Tag("sax")
+ void saxReaderResolvesAllowListed() throws Exception {
+ final XMLReader reader = hardenedReader();
+ reader.setEntityResolver(ENTITY_ALLOW_LIST);
+ final String text = AttackTestSupport.captureCharacters(reader, entityPayload(ALLOWED));
+ assertTrue(text.contains(AttackTestSupport.LEAKED_MARKER),
+ "allow-listed external entity should resolve through the caller's resolver");
}
@Test
@@ -191,64 +272,48 @@ void saxResolvesRelativeXIncludeSibling() throws Exception {
"relative XInclude sibling should resolve through the caller's resolver after the floor absolutizes the href");
}
- /** Absolute URL of the host document whose {@code xi:include} references {@code referenced.xml} by a relative href. */
- private static final String XINCLUDE_HOST = AttackTestSupport.resourceUrl("with-xinclude.xml").toString();
-
- private static DocumentBuilder xIncludeAwareBuilder() throws Exception {
- final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
- factory.setNamespaceAware(true);
- AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
- final DocumentBuilder builder = factory.newDocumentBuilder();
- builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
- return builder;
- }
-
- private static XMLReader xIncludeAwareReader() throws Exception {
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
- factory.setNamespaceAware(true);
- AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
- final XMLReader reader = factory.newSAXParser().getXMLReader();
- reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
- return reader;
+ @Test
+ @Tag("schema")
+ void schemaDeniesUnlisted() {
+ assertParseFails(() -> {
+ final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> null);
+ factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
+ }, "Schema import", SAXException.class, SecurityException.class);
}
- /** Resolves only {@link #ALLOWED} to its content stream; returns {@code null} for anything else. */
- private static final XMLResolver STAX_ALLOW_LIST = (publicID, systemID, baseURI, namespace) -> {
- if (!ALLOWED.equals(systemID)) {
- return null;
- }
- try {
- return new URL(systemID).openStream();
- } catch (final java.io.IOException e) {
- throw new XMLStreamException(e);
- }
- };
-
- private static XMLInputFactory externalEntityStaxFactory() {
- final XMLInputFactory factory = XmlFactories.newXMLInputFactory();
- factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
- factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
- return factory;
+ @Test
+ @Tag("schema")
+ void schemaFetchesIdentifierOnlyOptIn() {
+ // A non-null return is an opt-in even without content: the implementation fetches the named resource itself, mirroring the entity floor's contract.
+ assertParseSucceeds(() -> {
+ final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) ->
+ systemId != null && systemId.endsWith("included.xsd") ? identifierOnlyLsInput(ALLOWED_SCHEMA) : null);
+ factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
+ }, "Schema import via identifier-only LSInput");
}
@Test
- @Tag("stax")
- void staxResolvesAllowListed() throws Exception {
- final XMLInputFactory factory = externalEntityStaxFactory();
- factory.setXMLResolver(STAX_ALLOW_LIST);
- assertTrue(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
- "allow-listed external entity should resolve through the caller's resolver");
+ @Tag("schema")
+ void schemaResolvesAllowListed() {
+ // with-import.xsd references an element defined only in the imported included.xsd, so it compiles only if the import is resolved.
+ assertParseSucceeds(() -> {
+ final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ factory.setResourceResolver(SCHEMA_ALLOW_LIST);
+ factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
+ }, "Schema import via caller resolver");
}
@Test
@Tag("stax")
- void staxDoesNotLeakUnlisted() throws Exception {
+ void staxCallerCannotRemoveFloor() throws Exception {
+ // A caller resolver that resolves nothing must not re-open external fetches: the floor still resolves the reference to empty rather than fetching it.
final XMLInputFactory factory = externalEntityStaxFactory();
- factory.setXMLResolver(STAX_ALLOW_LIST);
- // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
+ factory.setXMLResolver((publicID, systemID, baseURI, namespace) -> null);
try {
- assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(UNLISTED)).contains(AttackTestSupport.LEAKED_MARKER),
- "unlisted external entity leaked");
+ assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
+ "floor was bypassed and the entity leaked");
} catch (final XMLStreamException blocked) {
// Acceptable: rejected at parse rather than resolved to empty.
}
@@ -256,13 +321,13 @@ void staxDoesNotLeakUnlisted() throws Exception {
@Test
@Tag("stax")
- void staxCallerCannotRemoveFloor() throws Exception {
- // A caller resolver that resolves nothing must not re-open external fetches: the floor still resolves the reference to empty rather than fetching it.
+ void staxDoesNotLeakUnlisted() throws Exception {
final XMLInputFactory factory = externalEntityStaxFactory();
- factory.setXMLResolver((publicID, systemID, baseURI, namespace) -> null);
+ factory.setXMLResolver(STAX_ALLOW_LIST);
+ // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
try {
- assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
- "floor was bypassed and the entity leaked");
+ assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(UNLISTED)).contains(AttackTestSupport.LEAKED_MARKER),
+ "unlisted external entity leaked");
} catch (final XMLStreamException blocked) {
// Acceptable: rejected at parse rather than resolved to empty.
}
@@ -277,75 +342,13 @@ void staxGetXMLResolverReportsCallerUnwrapped() {
assertSame(caller, factory.getXMLResolver(), "getXMLResolver should report the caller's resolver, not the floor wrapper");
}
- /** Absolute location of the imported schema the allow-list resolver permits. */
- private static final String ALLOWED_SCHEMA = AttackTestSupport.resourceUrl("included.xsd").toString();
-
- /** Resolves only the {@code included.xsd} import; returns {@code null} for anything else. */
- private static final LSResourceResolver SCHEMA_ALLOW_LIST = (type, namespaceURI, publicId, systemId, baseURI) ->
- systemId != null && systemId.endsWith("included.xsd") ? lsInput(ALLOWED_SCHEMA) : null;
-
- private static LSInput lsInput(final String systemId) {
- return assertDoesNotThrow(() -> {
- final LSInput input = identifierOnlyLsInput(systemId);
- input.setByteStream(new URL(systemId).openStream());
- return input;
- }, "Failed to build LSInput for " + systemId);
- }
-
- /** An {@link LSInput} naming the resource but carrying no content: a redirect the implementation fetches itself, like an identifier-only {@code InputSource}. */
- private static LSInput identifierOnlyLsInput(final String systemId) {
- return assertDoesNotThrow(() -> {
- final DOMImplementationLS ls = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
- final LSInput input = ls.createLSInput();
- input.setSystemId(systemId);
- return input;
- }, "Failed to build identifier-only LSInput for " + systemId);
- }
-
- @Test
- @Tag("schema")
- void schemaResolvesAllowListed() {
- // with-import.xsd references an element defined only in the imported included.xsd, so it compiles only if the import is resolved.
- assertParseSucceeds(() -> {
- final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- factory.setResourceResolver(SCHEMA_ALLOW_LIST);
- factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
- }, "Schema import via caller resolver");
- }
-
@Test
- @Tag("schema")
- void schemaDeniesUnlisted() {
- assertParseFails(() -> {
- final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> null);
- factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
- }, "Schema import", SAXException.class, SecurityException.class);
- }
-
- @Test
- @Tag("schema")
- void schemaFetchesIdentifierOnlyOptIn() {
- // A non-null return is an opt-in even without content: the implementation fetches the named resource itself, mirroring the entity floor's contract.
- assertParseSucceeds(() -> {
- final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) ->
- systemId != null && systemId.endsWith("included.xsd") ? identifierOnlyLsInput(ALLOWED_SCHEMA) : null);
- factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
- }, "Schema import via identifier-only LSInput");
- }
-
- /** Resolves only the {@code included.xsl} import; returns {@code null} for anything else. */
- private static final URIResolver XSL_ALLOW_LIST = (href, base) ->
- href != null && href.endsWith("included.xsl") ? AttackTestSupport.resourceSource("included.xsl") : null;
-
- @Test
- @Tag("trax")
- void transformerResolvesAllowListed() {
- // with-import.xsl imports included.xsl, so it compiles only if the import is resolved.
- final TransformerFactory factory = hardenedTransformerFactory();
- factory.setURIResolver(XSL_ALLOW_LIST);
- assertParseSucceeds(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-import.xsl")), "Stylesheet import via caller resolver");
+ @Tag("stax")
+ void staxResolvesAllowListed() throws Exception {
+ final XMLInputFactory factory = externalEntityStaxFactory();
+ factory.setXMLResolver(STAX_ALLOW_LIST);
+ assertTrue(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
+ "allow-listed external entity should resolve through the caller's resolver");
}
@Test
@@ -361,6 +364,20 @@ void transformerDoesNotLeakUnlisted() {
assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "unlisted stylesheet import leaked");
}
+ @Test
+ @Tag("trax")
+ void transformerParsesOptedInDocumentHardened() {
+ // Same contract on the runtime document() channel, which reaches a different internal reader than the compile-time import.
+ final TransformerFactory factory = hardenedTransformerFactory();
+ factory.setURIResolver((href, base) ->
+ href != null && href.endsWith("referenced.xml") ? AttackTestSupport.resourceSource("referenced-with-entity.xml") : null);
+ // Same undeclared-entity outcome as the import above: skipped, never expanded.
+ final StringWriter sink = new StringWriter();
+ assertDoesNotThrow(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-document.xsl")).newTransformer()
+ .transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink)));
+ assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "opted-in document() resource leaked its external entity");
+ }
+
@Test
@Tag("trax")
void transformerParsesOptedInImportHardened() {
@@ -378,27 +395,10 @@ void transformerParsesOptedInImportHardened() {
@Test
@Tag("trax")
- void transformerParsesOptedInDocumentHardened() {
- // Same contract on the runtime document() channel, which reaches a different internal reader than the compile-time import.
+ void transformerResolvesAllowListed() {
+ // with-import.xsl imports included.xsl, so it compiles only if the import is resolved.
final TransformerFactory factory = hardenedTransformerFactory();
- factory.setURIResolver((href, base) ->
- href != null && href.endsWith("referenced.xml") ? AttackTestSupport.resourceSource("referenced-with-entity.xml") : null);
- // Same undeclared-entity outcome as the import above: skipped, never expanded.
- final StringWriter sink = new StringWriter();
- assertDoesNotThrow(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-document.xsl")).newTransformer()
- .transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink)));
- assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "opted-in document() resource leaked its external entity");
- }
-
- /**
- * A hardened {@link TransformerFactory} with a re-throwing error listener. XSLTC and Xalan enforce the block through the
- * {@link FallbackIgnoreURIResolver} floor; Saxon enforces it through the ignore-all resolver floor on its {@code Configuration}. Either way a caller-set
- * resolver that returns {@code null} cannot re-open the fetch. The strict listener turns any reported-and-recovered error into a test failure, so an
- * implementation cannot quietly recover from a floor resolution while the test asserts clean completion.
- */
- private static TransformerFactory hardenedTransformerFactory() {
- final TransformerFactory factory = XmlFactories.newTransformerFactory();
- factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
- return factory;
+ factory.setURIResolver(XSL_ALLOW_LIST);
+ assertParseSucceeds(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-import.xsl")), "Stylesheet import via caller resolver");
}
}
diff --git a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
index 93c0de9..687df35 100644
--- a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
+++ b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
@@ -86,20 +86,6 @@ void saxParserResetKeepsEntityResolverFloor() throws Exception {
assertFalse(text.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked after reset:\n" + text);
}
- @Test
- @Tag("schema")
- void validatorResetKeepsResourceResolverFloor() throws Exception {
- // A Schema built without sources validates against the instance's xsi:schemaLocation hints, so the resolver floor is the only barrier between the
- // validator and the external schema fetch.
- final Validator validator = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema().newValidator();
- AttackTestSupport.assumeDoesNotThrow(validator::reset);
- validator.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
- // schema-location-instance.xml hints at schema-location.xsd, which declares its root: a validator whose floor was stripped fetches it and validates
- // cleanly, while the floor resolves the hint to empty content, which fails the validation.
- AttackTestSupport.assertParseFails(() -> validator.validate(AttackTestSupport.resourceSource("schema-location-instance.xml")),
- "Validator after reset", SAXException.class, SecurityException.class);
- }
-
@Test
@Tag("trax")
void transformerResetKeepsUriResolverFloor() throws Exception {
@@ -115,4 +101,18 @@ void transformerResetKeepsUriResolverFloor() throws Exception {
}
assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() leaked after reset:\n" + sink);
}
+
+ @Test
+ @Tag("schema")
+ void validatorResetKeepsResourceResolverFloor() throws Exception {
+ // A Schema built without sources validates against the instance's xsi:schemaLocation hints, so the resolver floor is the only barrier between the
+ // validator and the external schema fetch.
+ final Validator validator = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema().newValidator();
+ AttackTestSupport.assumeDoesNotThrow(validator::reset);
+ validator.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+ // schema-location-instance.xml hints at schema-location.xsd, which declares its root: a validator whose floor was stripped fetches it and validates
+ // cleanly, while the floor resolves the hint to empty content, which fails the validation.
+ AttackTestSupport.assertParseFails(() -> validator.validate(AttackTestSupport.resourceSource("schema-location-instance.xml")),
+ "Validator after reset", SAXException.class, SecurityException.class);
+ }
}
diff --git a/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java b/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
index 76909d4..3a52ec9 100644
--- a/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
@@ -37,16 +37,21 @@
*/
final class SaxSurfaceTestSupport {
+ /** Feeds the input's SAX events into the handler through a hardened, namespace-aware reader. */
+ static void feed(final ContentHandler handler, final InputSource input) throws Exception {
+ final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ factory.setNamespaceAware(true);
+ final XMLReader reader = factory.newSAXParser().getXMLReader();
+ reader.setContentHandler(handler);
+ reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+ reader.parse(input);
+ }
+
/** The hardened factory, as its runtime {@link SAXTransformerFactory} type. */
static SAXTransformerFactory hardenedFactory() {
return (SAXTransformerFactory) XmlFactories.newTransformerFactory();
}
- /** A benign {@code <root/>} input document. */
- static InputSource rootInput() {
- return new InputSource(new StringReader("<root/>"));
- }
-
/** Opens a fixture under {@code leaked/} as an {@link InputSource} preserving its system id, so relative hrefs resolve normally. */
static InputSource resourceInput(final String name) throws IOException {
final URL url = AttackTestSupport.resourceUrl(name);
@@ -55,14 +60,9 @@ static InputSource resourceInput(final String name) throws IOException {
return input;
}
- /** Feeds the input's SAX events into the handler through a hardened, namespace-aware reader. */
- static void feed(final ContentHandler handler, final InputSource input) throws Exception {
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
- factory.setNamespaceAware(true);
- final XMLReader reader = factory.newSAXParser().getXMLReader();
- reader.setContentHandler(handler);
- reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
- reader.parse(input);
+ /** A benign {@code <root/>} input document. */
+ static InputSource rootInput() {
+ return new InputSource(new StringReader("<root/>"));
}
private SaxSurfaceTestSupport() {
diff --git a/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java b/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java
index 36e15a1..ea21886 100644
--- a/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java
+++ b/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java
@@ -45,15 +45,6 @@ class SaxonAlternateFactoryTest {
private static final String BASIC_FACTORY_CLASS = "net.sf.saxon.BasicTransformerFactory";
- /** Instantiates {@code BasicTransformerFactory} reflectively, so this test compiles and loads without Saxon on the classpath. */
- private static TransformerFactory basicSaxonFactory() {
- try {
- return (TransformerFactory) Class.forName(BASIC_FACTORY_CLASS).getDeclaredConstructor().newInstance();
- } catch (final ReflectiveOperationException e) {
- throw new AssertionError("Cannot instantiate " + BASIC_FACTORY_CLASS, e);
- }
- }
-
private static void assumeSaxonPresent() {
boolean present;
try {
@@ -65,6 +56,15 @@ private static void assumeSaxonPresent() {
Assumptions.assumeTrue(present, "Saxon is not on the classpath");
}
+ /** Instantiates {@code BasicTransformerFactory} reflectively, so this test compiles and loads without Saxon on the classpath. */
+ private static TransformerFactory basicSaxonFactory() {
+ try {
+ return (TransformerFactory) Class.forName(BASIC_FACTORY_CLASS).getDeclaredConstructor().newInstance();
+ } catch (final ReflectiveOperationException e) {
+ throw new AssertionError("Cannot instantiate " + BASIC_FACTORY_CLASS, e);
+ }
+ }
+
/** A {@code collection()} over the test fixtures whose {@code referenced.xml} carries {@link AttackTestSupport#LEAKED_MARKER}. */
private static String collectionStylesheet() {
final String collection = AttackTestSupport.resourceUrl("referenced.xml").toString().replaceFirst("referenced\\.xml$", "?select=referenced.xml");
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java b/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
index 869af34..7cd4c3b 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
@@ -55,32 +55,6 @@ class SchemaLocationDomTest {
private static final String INSTANCE = "schema-location-instance.xml";
- @Test
- void hardenedDoesNotFetchExternalSchema() {
- assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
- final DocumentBuilderFactory factory = enableXsdValidation(XmlFactories.newDocumentBuilderFactory());
- // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
- // parse completes but the schema's default leak attribute is never inlined. Either way the marker must not reach the DOM.
- try {
- final Document document = parse(factory);
- assertNotEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
- "Hardened parse must not inline the external schema's default attribute.");
- } catch (final Exception blocked) {
- // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or inlined.
- }
- }
-
- @Test
- void unconfiguredFetchesExternalSchema() throws Exception {
- assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
- final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
- // Positive control: without hardening the external schema is fetched and its default attribute is inlined into the DOM.
- final Document document = parse(enableXsdValidation(factory));
- assertEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
- "Permissive parse should have fetched the external schema and inlined its default attribute.");
- }
-
private static DocumentBuilderFactory enableXsdValidation(final DocumentBuilderFactory factory) {
factory.setNamespaceAware(true);
factory.setValidating(true);
@@ -109,4 +83,30 @@ private static boolean supportsSchemaLanguage() {
return false;
}
}
+
+ @Test
+ void hardenedDoesNotFetchExternalSchema() {
+ assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+ final DocumentBuilderFactory factory = enableXsdValidation(XmlFactories.newDocumentBuilderFactory());
+ // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
+ // parse completes but the schema's default leak attribute is never inlined. Either way the marker must not reach the DOM.
+ try {
+ final Document document = parse(factory);
+ assertNotEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
+ "Hardened parse must not inline the external schema's default attribute.");
+ } catch (final Exception blocked) {
+ // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or inlined.
+ }
+ }
+
+ @Test
+ void unconfiguredFetchesExternalSchema() throws Exception {
+ assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+ // Positive control: without hardening the external schema is fetched and its default attribute is inlined into the DOM.
+ final Document document = parse(enableXsdValidation(factory));
+ assertEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
+ "Permissive parse should have fetched the external schema and inlined its default attribute.");
+ }
}
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java b/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
index 8fc03db..127c920 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
@@ -68,14 +68,6 @@ class SchemaLocationPropertyTest {
/** Instance whose root, {@code l:leaked}, is declared by {@code included.xsd} in the {@value #LEAKED_NS} namespace. */
private static final String NAMESPACED_INSTANCE = "<l:leaked xmlns:l=\"" + LEAKED_NS + "\">x</l:leaked>";
- private static String noNamespaceLocation() {
- return resourceUrl("no-namespace.xsd").toString();
- }
-
- private static String namespacedLocation() {
- return LEAKED_NS + " " + resourceUrl("included.xsd");
- }
-
/**
* Runs the parser setup, skipping the test (rather than failing it) on parsers that do not accept these
* schema-validation features/properties, such as Android's KXmlParser and Expat.
@@ -99,18 +91,6 @@ private static DocumentBuilder hardenedValidatingDom(final String property, fina
});
}
- private static DocumentBuilder permissiveValidatingDom(final String property, final String value) {
- return configureOrSkip(() -> {
- final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(true);
- factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
- factory.setAttribute(SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI);
- factory.setAttribute(property, value);
- return strictDocumentBuilder(factory);
- });
- }
-
private static XMLReader hardenedValidatingSax(final String property, final String value) {
return configureOrSkip(() -> {
final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
@@ -123,6 +103,26 @@ private static XMLReader hardenedValidatingSax(final String property, final Stri
});
}
+ private static String namespacedLocation() {
+ return LEAKED_NS + " " + resourceUrl("included.xsd");
+ }
+
+ private static String noNamespaceLocation() {
+ return resourceUrl("no-namespace.xsd").toString();
+ }
+
+ private static DocumentBuilder permissiveValidatingDom(final String property, final String value) {
+ return configureOrSkip(() -> {
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(true);
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+ factory.setAttribute(SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ factory.setAttribute(property, value);
+ return strictDocumentBuilder(factory);
+ });
+ }
+
private static XMLReader permissiveValidatingSax(final String property, final String value) {
return configureOrSkip(() -> {
final SAXParserFactory factory = SAXParserFactory.newInstance();
@@ -143,42 +143,42 @@ void hardenedDomRefusesNoNamespaceSchemaLocation() {
assertParseFails(() -> builder.parse(inputSource(NO_NS_INSTANCE)), "DOM external-noNamespaceSchemaLocation", SAXException.class);
}
- @Test
- void permissiveDomFetchesNoNamespaceSchemaLocation() {
- final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_NO_NS, noNamespaceLocation());
- assertParseSucceeds(() -> builder.parse(inputSource(NO_NS_INSTANCE)), "DOM external-noNamespaceSchemaLocation (permissive)");
- }
-
@Test
void hardenedDomRefusesSchemaLocation() {
final DocumentBuilder builder = hardenedValidatingDom(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
assertParseFails(() -> builder.parse(inputSource(NAMESPACED_INSTANCE)), "DOM external-schemaLocation", SAXException.class);
}
- @Test
- void permissiveDomFetchesSchemaLocation() {
- final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
- assertParseSucceeds(() -> builder.parse(inputSource(NAMESPACED_INSTANCE)), "DOM external-schemaLocation (permissive)");
- }
-
@Test
void hardenedSaxRefusesNoNamespaceSchemaLocation() {
final XMLReader reader = hardenedValidatingSax(EXTERNAL_NO_NS, noNamespaceLocation());
assertParseFails(() -> reader.parse(inputSource(NO_NS_INSTANCE)), "SAX external-noNamespaceSchemaLocation", SAXException.class);
}
- @Test
- void permissiveSaxFetchesNoNamespaceSchemaLocation() {
- final XMLReader reader = permissiveValidatingSax(EXTERNAL_NO_NS, noNamespaceLocation());
- assertParseSucceeds(() -> reader.parse(inputSource(NO_NS_INSTANCE)), "SAX external-noNamespaceSchemaLocation (permissive)");
- }
-
@Test
void hardenedSaxRefusesSchemaLocation() {
final XMLReader reader = hardenedValidatingSax(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
assertParseFails(() -> reader.parse(inputSource(NAMESPACED_INSTANCE)), "SAX external-schemaLocation", SAXException.class);
}
+ @Test
+ void permissiveDomFetchesNoNamespaceSchemaLocation() {
+ final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_NO_NS, noNamespaceLocation());
+ assertParseSucceeds(() -> builder.parse(inputSource(NO_NS_INSTANCE)), "DOM external-noNamespaceSchemaLocation (permissive)");
+ }
+
+ @Test
+ void permissiveDomFetchesSchemaLocation() {
+ final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
+ assertParseSucceeds(() -> builder.parse(inputSource(NAMESPACED_INSTANCE)), "DOM external-schemaLocation (permissive)");
+ }
+
+ @Test
+ void permissiveSaxFetchesNoNamespaceSchemaLocation() {
+ final XMLReader reader = permissiveValidatingSax(EXTERNAL_NO_NS, noNamespaceLocation());
+ assertParseSucceeds(() -> reader.parse(inputSource(NO_NS_INSTANCE)), "SAX external-noNamespaceSchemaLocation (permissive)");
+ }
+
@Test
void permissiveSaxFetchesSchemaLocation() {
final XMLReader reader = permissiveValidatingSax(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java b/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
index b7317e1..7d30e35 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
@@ -67,34 +67,6 @@ public void startElement(final String uri, final String localName, final String
private static final String INSTANCE = "schema-location-instance.xml";
- @Test
- void hardenedDoesNotFetchExternalSchema() throws Exception {
- assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
- final SAXParser parser = newValidatingParser(XmlFactories.newSAXParserFactory());
- // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
- // parse completes but the schema's default leak attribute is never augmented onto the element. Either way the marker must not be observed.
- final LeakCapturingHandler handler = new LeakCapturingHandler();
- try {
- parse(parser, handler);
- } catch (final Exception blocked) {
- // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or augmented.
- }
- assertNull(handler.leak, "Hardened parse must not augment the external schema's default attribute onto the element.");
- }
-
- @Test
- void unconfiguredFetchesExternalSchema() throws Exception {
- assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
- final SAXParserFactory factory = SAXParserFactory.newInstance();
- factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
- final SAXParser parser = newValidatingParser(factory);
- // Positive control: without hardening the external schema is fetched and its default attribute is augmented onto the root element.
- final LeakCapturingHandler handler = new LeakCapturingHandler();
- parse(parser, handler);
- assertEquals(LEAKED_MARKER, handler.leak,
- "Permissive parse should have fetched the external schema and augmented its default attribute onto the element.");
- }
-
private static SAXParser newValidatingParser(final SAXParserFactory factory) throws Exception {
factory.setNamespaceAware(true);
factory.setValidating(true);
@@ -124,4 +96,32 @@ private static boolean supportsSchemaLanguage() {
return false;
}
}
+
+ @Test
+ void hardenedDoesNotFetchExternalSchema() throws Exception {
+ assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+ final SAXParser parser = newValidatingParser(XmlFactories.newSAXParserFactory());
+ // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
+ // parse completes but the schema's default leak attribute is never augmented onto the element. Either way the marker must not be observed.
+ final LeakCapturingHandler handler = new LeakCapturingHandler();
+ try {
+ parse(parser, handler);
+ } catch (final Exception blocked) {
+ // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or augmented.
+ }
+ assertNull(handler.leak, "Hardened parse must not augment the external schema's default attribute onto the element.");
+ }
+
+ @Test
+ void unconfiguredFetchesExternalSchema() throws Exception {
+ assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+ final SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+ final SAXParser parser = newValidatingParser(factory);
+ // Positive control: without hardening the external schema is fetched and its default attribute is augmented onto the root element.
+ final LeakCapturingHandler handler = new LeakCapturingHandler();
+ parse(parser, handler);
+ assertEquals(LEAKED_MARKER, handler.leak,
+ "Permissive parse should have fetched the external schema and augmented its default attribute onto the element.");
+ }
}
diff --git a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
index 4972cba..53ad525 100644
--- a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
+++ b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
@@ -100,6 +100,39 @@ class ShadingFootprintTest {
private static Clazzpath clazzpath;
private static Path classesDir;
+ /**
+ * Sums the uncompressed {@code .class} file sizes of a closure's classes, as they would land in a shaded jar.
+ */
+ private static long bytesOf(final Set<String> simpleNames) {
+ long total = 0;
+ for (final String name : simpleNames) {
+ try {
+ total += Files.size(classesDir.resolve("org/apache/commons/xml/" + name + ".class"));
+ } catch (final IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ return total;
+ }
+
+ /**
+ * Transitive class closure of {@code PKG + simpleName}, restricted to this library's own package and reported by simple name.
+ */
+ private static Set<String> closureOf(final String simpleName) {
+ final Clazz entry = clazzpath.getClazz(PKG + simpleName);
+ if (entry == null) {
+ throw new IllegalStateException("Not on the compiled classpath: " + PKG + simpleName);
+ }
+ final Set<String> names = new TreeSet<>();
+ names.add(strip(entry.getName()));
+ for (final Clazz dependency : entry.getTransitiveDependencies()) {
+ if (dependency.getName().startsWith(PKG)) {
+ names.add(strip(dependency.getName()));
+ }
+ }
+ return names;
+ }
+
@BeforeAll
static void indexCompiledClasses() throws Exception {
classesDir = Paths.get(HardeningException.class.getProtectionDomain().getCodeSource().getLocation().toURI());
@@ -123,29 +156,36 @@ static void reportFootprint() {
System.out.print(report);
}
- @Test
- void documentBuilderHardenerFootprint() {
- assertEquals(DOCUMENT_BUILDER_HARDENER, closureOf("DocumentBuilderHardener"));
+ /**
+ * {@link #SAX_PARSER_HARDENER} plus the extra names; used where an entry point's closure is the SAX path plus its own classes.
+ */
+ private static Set<String> saxParsersHardenerPlus(final String... more) {
+ final Set<String> union = new TreeSet<>(SAX_PARSER_HARDENER);
+ union.addAll(Arrays.asList(more));
+ return union;
}
- @Test
- void saxParserHardenerFootprint() {
- assertEquals(SAX_PARSER_HARDENER, closureOf("SAXParserHardener"));
+ private static Set<String> set(final String... names) {
+ return new TreeSet<>(Arrays.asList(names));
+ }
+
+ private static String strip(final String qualifiedName) {
+ return qualifiedName.substring(PKG.length());
}
@Test
- void staxHardenerFootprint() {
- assertEquals(STAX_HARDENER, closureOf("StaxHardener"));
+ void documentBuilderHardenerFootprint() {
+ assertEquals(DOCUMENT_BUILDER_HARDENER, closureOf("DocumentBuilderHardener"));
}
@Test
- void transformerHardenerFootprint() {
- assertEquals(TRANSFORMER_HARDENER, closureOf("TransformerHardener"));
+ void onlyXmlFactoriesPullsTheWholeLibrary() {
+ assertEquals(LIBRARY_CLASS_COUNT, closureOf("XmlFactories").size(), "XmlFactories closure size drifted");
}
@Test
- void xPathHardenerFootprint() {
- assertEquals(XPATH_HARDENER, closureOf("XPathHardener"));
+ void saxParserHardenerFootprint() {
+ assertEquals(SAX_PARSER_HARDENER, closureOf("SAXParserHardener"));
}
@Test
@@ -154,57 +194,17 @@ void schemaHardenerFootprint() {
}
@Test
- void onlyXmlFactoriesPullsTheWholeLibrary() {
- assertEquals(LIBRARY_CLASS_COUNT, closureOf("XmlFactories").size(), "XmlFactories closure size drifted");
- }
-
- /**
- * Transitive class closure of {@code PKG + simpleName}, restricted to this library's own package and reported by simple name.
- */
- private static Set<String> closureOf(final String simpleName) {
- final Clazz entry = clazzpath.getClazz(PKG + simpleName);
- if (entry == null) {
- throw new IllegalStateException("Not on the compiled classpath: " + PKG + simpleName);
- }
- final Set<String> names = new TreeSet<>();
- names.add(strip(entry.getName()));
- for (final Clazz dependency : entry.getTransitiveDependencies()) {
- if (dependency.getName().startsWith(PKG)) {
- names.add(strip(dependency.getName()));
- }
- }
- return names;
- }
-
- /**
- * Sums the uncompressed {@code .class} file sizes of a closure's classes, as they would land in a shaded jar.
- */
- private static long bytesOf(final Set<String> simpleNames) {
- long total = 0;
- for (final String name : simpleNames) {
- try {
- total += Files.size(classesDir.resolve("org/apache/commons/xml/" + name + ".class"));
- } catch (final IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- return total;
- }
-
- private static String strip(final String qualifiedName) {
- return qualifiedName.substring(PKG.length());
+ void staxHardenerFootprint() {
+ assertEquals(STAX_HARDENER, closureOf("StaxHardener"));
}
- private static Set<String> set(final String... names) {
- return new TreeSet<>(Arrays.asList(names));
+ @Test
+ void transformerHardenerFootprint() {
+ assertEquals(TRANSFORMER_HARDENER, closureOf("TransformerHardener"));
}
- /**
- * {@link #SAX_PARSER_HARDENER} plus the extra names; used where an entry point's closure is the SAX path plus its own classes.
- */
- private static Set<String> saxParsersHardenerPlus(final String... more) {
- final Set<String> union = new TreeSet<>(SAX_PARSER_HARDENER);
- union.addAll(Arrays.asList(more));
- return union;
+ @Test
+ void xPathHardenerFootprint() {
+ assertEquals(XPATH_HARDENER, closureOf("XPathHardener"));
}
}
diff --git a/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java b/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java
index 348aa80..360ff6c 100644
--- a/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java
+++ b/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java
@@ -53,13 +53,6 @@ private static String compileAndTransform(final TemplatesHandler handler, final
return sink.toString();
}
- @Test
- void hardenedTemplatesHandlerDoesNotLeakInclude() throws Exception {
- final TemplatesHandler handler = SaxSurfaceTestSupport.hardenedFactory().newTemplatesHandler();
- assertFalse(compileAndTransform(handler, "with-include.xsl").contains(AttackTestSupport.LEAKED_MARKER),
- "xsl:include through TemplatesHandler leaked");
- }
-
@Test
void hardenedTemplatesHandlerDoesNotLeakDocument() throws Exception {
// The f004 product path: the Templates from getTemplates() must produce floored Transformers for runtime document().
@@ -68,6 +61,13 @@ void hardenedTemplatesHandlerDoesNotLeakDocument() throws Exception {
"document() through TemplatesHandler.getTemplates() leaked");
}
+ @Test
+ void hardenedTemplatesHandlerDoesNotLeakInclude() throws Exception {
+ final TemplatesHandler handler = SaxSurfaceTestSupport.hardenedFactory().newTemplatesHandler();
+ assertFalse(compileAndTransform(handler, "with-include.xsl").contains(AttackTestSupport.LEAKED_MARKER),
+ "xsl:include through TemplatesHandler leaked");
+ }
+
@Test
void unconfiguredTemplatesHandlerLeaksDocument() throws Exception {
final TemplatesHandler handler = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTemplatesHandler();
diff --git a/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java b/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java
index 767b4f4..89060cd 100644
--- a/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java
+++ b/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java
@@ -48,6 +48,16 @@ private static String transformViaHandler(final TransformerHandler handler) thro
return sink.toString();
}
+ @Test
+ void hardenedGetTransformerDoesNotLeakDocument() throws Exception {
+ // The f004 bypass: pull the inner Transformer out of the handler and transform directly; the floor must ride along.
+ final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
+ final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
+ final StringWriter sink = new StringWriter();
+ handler.getTransformer().transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink));
+ assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() through getTransformer() leaked");
+ }
+
@Test
void hardenedTransformerHandlerDoesNotLeakDocument() throws Exception {
final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
@@ -65,16 +75,6 @@ void hardenedTransformerHandlerFromTemplatesDoesNotLeakDocument() throws Excepti
"document() through TransformerHandler(Templates) leaked");
}
- @Test
- void hardenedGetTransformerDoesNotLeakDocument() throws Exception {
- // The f004 bypass: pull the inner Transformer out of the handler and transform directly; the floor must ride along.
- final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
- final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
- final StringWriter sink = new StringWriter();
- handler.getTransformer().transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink));
- assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() through getTransformer() leaked");
- }
-
@Test
void unconfiguredTransformerHandlerLeaksDocument() throws Exception {
final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
diff --git a/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java b/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java
index 138eb00..7d9d01e 100644
--- a/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java
+++ b/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java
@@ -43,28 +43,28 @@ class UnsupportedXmlImplementationTest {
public static final class FakeDocumentBuilderFactory extends DocumentBuilderFactory {
@Override
- public DocumentBuilder newDocumentBuilder() {
- throw new UnsupportedOperationException();
+ public Object getAttribute(final String name) {
+ return null;
}
@Override
- public void setAttribute(final String name, final Object value) {
- // no-op
+ public boolean getFeature(final String name) {
+ return false;
}
@Override
- public Object getAttribute(final String name) {
- return null;
+ public DocumentBuilder newDocumentBuilder() {
+ throw new UnsupportedOperationException();
}
@Override
- public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
- throw new ParserConfigurationException("feature not recognized: " + name);
+ public void setAttribute(final String name, final Object value) {
+ // no-op
}
@Override
- public boolean getFeature(final String name) {
- return false;
+ public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
+ throw new ParserConfigurationException("feature not recognized: " + name);
}
}
@@ -73,6 +73,11 @@ public boolean getFeature(final String name) {
*/
public static final class FakeSAXParserFactory extends SAXParserFactory {
+ @Override
+ public boolean getFeature(final String name) throws SAXNotSupportedException {
+ throw new SAXNotSupportedException("feature not recognized: " + name);
+ }
+
@Override
public SAXParser newSAXParser() {
throw new UnsupportedOperationException();
@@ -82,11 +87,6 @@ public SAXParser newSAXParser() {
public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException {
throw new SAXNotRecognizedException("feature not recognized: " + name);
}
-
- @Override
- public boolean getFeature(final String name) throws SAXNotSupportedException {
- throw new SAXNotSupportedException("feature not recognized: " + name);
- }
}
@Test
diff --git a/src/test/java/org/apache/commons/xml/XIncludeTest.java b/src/test/java/org/apache/commons/xml/XIncludeTest.java
index 84a3933..0d4d4a0 100644
--- a/src/test/java/org/apache/commons/xml/XIncludeTest.java
+++ b/src/test/java/org/apache/commons/xml/XIncludeTest.java
@@ -53,15 +53,6 @@
*/
class XIncludeTest {
- /** Absolute URL of the XML fixture pulled in by {@code parse="xml"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
- private static final String REFERENCED_XML = resourceUrl("referenced.xml").toString();
-
- /** Absolute URL of the text fixture pulled in by {@code parse="text"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
- private static final String REFERENCED_TEXT = resourceUrl("referenced.txt").toString();
-
- /** Content the allow-list resolver returns for an allowed include; its presence proves the caller's resolver was consulted. */
- private static final String RESOLVED_MARKER = "XINCLUDE-RESOLVED-905bbbce-16ee-4a0c-b165-d1f8c663934c";
-
/**
* Allow-lists the two fixture URLs, returning the appropriate in-memory content for each: {@link #RESOLVED_MARKER}
* wrapped as XML for {@link #REFERENCED_XML}, and as plain text for {@link #REFERENCED_TEXT}. Anything else returns
@@ -85,17 +76,18 @@ public InputSource resolveEntity(final String publicId, final String systemId) {
}
}
+ /** Absolute URL of the XML fixture pulled in by {@code parse="xml"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
+ private static final String REFERENCED_XML = resourceUrl("referenced.xml").toString();
+
+ /** Absolute URL of the text fixture pulled in by {@code parse="text"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
+ private static final String REFERENCED_TEXT = resourceUrl("referenced.txt").toString();
+
+ /** Content the allow-list resolver returns for an allowed include; its presence proves the caller's resolver was consulted. */
+ private static final String RESOLVED_MARKER = "XINCLUDE-RESOLVED-905bbbce-16ee-4a0c-b165-d1f8c663934c";
+
/** Resolver that resolves nothing, so the hardening's ignore-all floor must empty every lookup and never leak. */
private static final EntityResolver NO_OP_RESOLVER = (publicId, systemId) -> null;
- /** XML wrapper for xi:include in the given {@code parse} mode referencing {@code href}. */
- private static String xiIncludeXml(final String href, final String parseMode) {
- return "<?xml version=\"1.0\"?>\n"
- + "<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
- + " <xi:include href=\"" + href + "\" parse=\"" + parseMode + "\"/>\n"
- + "</root>";
- }
-
/**
* Enables XInclude on the factory under test, skipping the test when the platform refuses.
*
@@ -122,60 +114,68 @@ private static void assumeXIncludeAware(final SAXParserFactory factory) {
}
}
+ /** XML wrapper for xi:include in the given {@code parse} mode referencing {@code href}. */
+ private static String xiIncludeXml(final String href, final String parseMode) {
+ return "<?xml version=\"1.0\"?>\n"
+ + "<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+ + " <xi:include href=\"" + href + "\" parse=\"" + parseMode + "\"/>\n"
+ + "</root>";
+ }
+
//region Baseline: unhardened JAXP resolves the include
@Test
@Tag("dom")
- void baselineDomLeaksParseXml() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+ void baselineDomLeaksParseText() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final Document doc = factory.newDocumentBuilder().parse(input);
final String text = doc.getDocumentElement().getTextContent();
- assertEquals(LEAKED_MARKER, text.trim(),
- "Baseline DOM parse=xml should leak marker; got: " + text);
+ assertTrue(text != null && text.contains(LEAKED_MARKER),
+ "Baseline DOM parse=text should leak marker; got: " + text);
}
@Test
@Tag("dom")
- void baselineDomLeaksParseText() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
+ void baselineDomLeaksParseXml() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final Document doc = factory.newDocumentBuilder().parse(input);
final String text = doc.getDocumentElement().getTextContent();
- assertTrue(text != null && text.contains(LEAKED_MARKER),
- "Baseline DOM parse=text should leak marker; got: " + text);
+ assertEquals(LEAKED_MARKER, text.trim(),
+ "Baseline DOM parse=xml should leak marker; got: " + text);
}
@Test
@Tag("sax")
- void baselineSaxLeaksParseXml() throws Exception {
- final String input = xiIncludeXml(REFERENCED_XML, "xml");
+ void baselineSaxLeaksParseText() throws Exception {
+ final String input = xiIncludeXml(REFERENCED_TEXT, "text");
final SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
- assertEquals(LEAKED_MARKER, captured.trim(),
- "Baseline SAX parse=xml should leak marker; got: " + captured);
+ assertTrue(captured.contains(LEAKED_MARKER),
+ "Baseline SAX parse=text should leak marker; got: " + captured);
}
@Test
@Tag("sax")
- void baselineSaxLeaksParseText() throws Exception {
- final String input = xiIncludeXml(REFERENCED_TEXT, "text");
+ void baselineSaxLeaksParseXml() throws Exception {
+ final String input = xiIncludeXml(REFERENCED_XML, "xml");
final SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
- assertTrue(captured.contains(LEAKED_MARKER),
- "Baseline SAX parse=text should leak marker; got: " + captured);
+ assertEquals(LEAKED_MARKER, captured.trim(),
+ "Baseline SAX parse=xml should leak marker; got: " + captured);
}
//endregion
@@ -184,57 +184,59 @@ void baselineSaxLeaksParseText() throws Exception {
@Test
@Tag("dom")
- void hardenedDomBlocksParseXml() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+ void hardenedDomBlocksParseText() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
- assertThrows(SAXException.class, () -> {
- final DocumentBuilder builder = factory.newDocumentBuilder();
- builder.parse(input);
- }, "Hardened DOM parse=xml should throw");
+ final Document doc = factory.newDocumentBuilder().parse(input);
+ final String text = doc.getDocumentElement().getTextContent();
+ assertFalse(text.contains(LEAKED_MARKER),
+ "Hardened DOM parse=text must resolve the include to empty, not leak; got: " + text);
}
@Test
@Tag("dom")
- void hardenedDomBlocksParseText() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
+ void hardenedDomBlocksParseXml() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
- final Document doc = factory.newDocumentBuilder().parse(input);
- final String text = doc.getDocumentElement().getTextContent();
- assertFalse(text.contains(LEAKED_MARKER),
- "Hardened DOM parse=text must resolve the include to empty, not leak; got: " + text);
+ assertThrows(SAXException.class, () -> {
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.parse(input);
+ }, "Hardened DOM parse=xml should throw");
}
@Test
- @Tag("sax")
- void hardenedSaxBlocksParseXml() throws Exception {
+ @Tag("dom")
+ void hardenedDomNullResolverDoesNotLeak() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
- assertThrows(SAXException.class, () -> {
- final XMLReader reader = factory.newSAXParser().getXMLReader();
- reader.parse(input);
- }, "Hardened SAX parse=xml should throw");
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setEntityResolver(NO_OP_RESOLVER);
+ assertThrows(SAXException.class, () -> builder.parse(input),
+ "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
}
@Test
- @Tag("sax")
- void hardenedSaxBlocksParseText() throws Exception {
- final String input = xiIncludeXml(REFERENCED_TEXT, "text");
+ @Tag("dom")
+ void hardenedDomWithAllowListResolvesParseText() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
- final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
- assertFalse(captured.contains(LEAKED_MARKER),
- "Hardened SAX parse=text must resolve the include to empty, not leak; got: " + captured);
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setEntityResolver(new AllowListResolver());
+ final Document doc = builder.parse(input);
+ assertEquals(RESOLVED_MARKER, doc.getDocumentElement().getTextContent().trim(),
+ "DOM parse=text with allow-list should resolve to the resolver's content");
}
//endregion
@@ -257,47 +259,44 @@ void hardenedDomWithAllowListResolvesParseXml() throws Exception {
}
@Test
- @Tag("dom")
- void hardenedDomWithAllowListResolvesParseText() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
+ @Tag("sax")
+ void hardenedSaxBlocksParseText() throws Exception {
+ final String input = xiIncludeXml(REFERENCED_TEXT, "text");
- final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
+ final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
- final DocumentBuilder builder = factory.newDocumentBuilder();
- builder.setEntityResolver(new AllowListResolver());
- final Document doc = builder.parse(input);
- assertEquals(RESOLVED_MARKER, doc.getDocumentElement().getTextContent().trim(),
- "DOM parse=text with allow-list should resolve to the resolver's content");
+ final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
+ assertFalse(captured.contains(LEAKED_MARKER),
+ "Hardened SAX parse=text must resolve the include to empty, not leak; got: " + captured);
}
@Test
- @Tag("dom")
- void hardenedDomNullResolverDoesNotLeak() throws Exception {
+ @Tag("sax")
+ void hardenedSaxBlocksParseXml() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
- final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
+ final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
- final DocumentBuilder builder = factory.newDocumentBuilder();
- builder.setEntityResolver(NO_OP_RESOLVER);
- assertThrows(SAXException.class, () -> builder.parse(input),
- "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
+ assertThrows(SAXException.class, () -> {
+ final XMLReader reader = factory.newSAXParser().getXMLReader();
+ reader.parse(input);
+ }, "Hardened SAX parse=xml should throw");
}
@Test
@Tag("sax")
- void hardenedSaxWithAllowListResolvesParseXml() throws Exception {
- final String input = xiIncludeXml(REFERENCED_XML, "xml");
+ void hardenedSaxNullResolverDoesNotLeak() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final XMLReader reader = factory.newSAXParser().getXMLReader();
- reader.setEntityResolver(new AllowListResolver());
- final String captured = captureCharacters(reader, input);
- assertEquals(RESOLVED_MARKER, captured.trim(),
- "SAX parse=xml with allow-list should resolve to the resolver's content");
+ reader.setEntityResolver(NO_OP_RESOLVER);
+ assertThrows(SAXException.class, () -> reader.parse(input),
+ "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
}
@Test
@@ -317,16 +316,17 @@ void hardenedSaxWithAllowListResolvesParseText() throws Exception {
@Test
@Tag("sax")
- void hardenedSaxNullResolverDoesNotLeak() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+ void hardenedSaxWithAllowListResolvesParseXml() throws Exception {
+ final String input = xiIncludeXml(REFERENCED_XML, "xml");
final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final XMLReader reader = factory.newSAXParser().getXMLReader();
- reader.setEntityResolver(NO_OP_RESOLVER);
- assertThrows(SAXException.class, () -> reader.parse(input),
- "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
+ reader.setEntityResolver(new AllowListResolver());
+ final String captured = captureCharacters(reader, input);
+ assertEquals(RESOLVED_MARKER, captured.trim(),
+ "SAX parse=xml with allow-list should resolve to the resolver's content");
}
//endregion
@@ -335,16 +335,17 @@ void hardenedSaxNullResolverDoesNotLeak() throws Exception {
@Test
@Tag("sax")
- void hardenReaderBlocksParseXml() throws Exception {
- final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+ void hardenReaderAllowListResolvesParseXml() throws Exception {
+ final String input = xiIncludeXml(REFERENCED_XML, "xml");
- // Reader from an unhardened factory that already has XInclude enabled
final SAXParserFactory unhardenedFactory = SAXParserFactory.newInstance();
unhardenedFactory.setNamespaceAware(true);
assumeXIncludeAware(unhardenedFactory);
final XMLReader reader = SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
- assertThrows(SAXException.class, () -> reader.parse(input),
- "hardenReader should block XInclude parse=xml on reader with XInclude already enabled");
+ reader.setEntityResolver(new AllowListResolver());
+ final String captured = captureCharacters(reader, input);
+ assertEquals(RESOLVED_MARKER, captured.trim(),
+ "hardenReader + allow-list should resolve to the resolver's content on a reader with XInclude already enabled");
}
@Test
@@ -363,17 +364,16 @@ void hardenReaderBlocksParseText() throws Exception {
@Test
@Tag("sax")
- void hardenReaderAllowListResolvesParseXml() throws Exception {
- final String input = xiIncludeXml(REFERENCED_XML, "xml");
+ void hardenReaderBlocksParseXml() throws Exception {
+ final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+ // Reader from an unhardened factory that already has XInclude enabled
final SAXParserFactory unhardenedFactory = SAXParserFactory.newInstance();
unhardenedFactory.setNamespaceAware(true);
assumeXIncludeAware(unhardenedFactory);
final XMLReader reader = SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
- reader.setEntityResolver(new AllowListResolver());
- final String captured = captureCharacters(reader, input);
- assertEquals(RESOLVED_MARKER, captured.trim(),
- "hardenReader + allow-list should resolve to the resolver's content on a reader with XInclude already enabled");
+ assertThrows(SAXException.class, () -> reader.parse(input),
+ "hardenReader should block XInclude parse=xml on reader with XInclude already enabled");
}
//endregion
diff --git a/src/test/java/org/apache/commons/xml/XMLFilterTest.java b/src/test/java/org/apache/commons/xml/XMLFilterTest.java
index 5b91a2d..0a33878 100644
--- a/src/test/java/org/apache/commons/xml/XMLFilterTest.java
+++ b/src/test/java/org/apache/commons/xml/XMLFilterTest.java
@@ -63,12 +63,30 @@ private static String filterAndCapture(final XMLFilter filter, final String inpu
return text.toString();
}
+ /**
+ * On Android, hand the unconfigured filter a permissive parent so Xalan's {@code TrAXFilter} uses it instead of self-provisioning an Expat reader on which
+ * it enables {@code namespace-prefixes}, a feature Android's libexpat accepts but fails on mid-parse. The parent stays permissive (no floor), so the leak
+ * these controls assert still occurs.
+ */
+ private static void setPermissiveParentOnAndroid(final XMLFilter filter) {
+ if (AttackTestSupport.IS_ANDROID) {
+ filter.setParent(AttackTestSupport.permissiveReader());
+ }
+ }
+
@Test
void hardenedFilterDoesNotLeakDocument() throws Exception {
final XMLFilter filter = SaxSurfaceTestSupport.hardenedFactory().newXMLFilter(AttackTestSupport.resourceSource("with-document.xsl"));
assertFalse(filterAndCapture(filter, "<root/>").contains(AttackTestSupport.LEAKED_MARKER), "document() through XMLFilter leaked");
}
+ @Test
+ void hardenedFilterDoesNotLeakExternalEntity() throws Exception {
+ // The f003 vector: with no caller-set parent, the input must be parsed by a hardened reader, not a self-provisioned permissive one.
+ final XMLFilter filter = SaxSurfaceTestSupport.hardenedFactory().newXMLFilter(AttackTestSupport.streamSource(IDENTITY_XSLT));
+ assertFalse(filterAndCapture(filter, entityPayload()).contains(AttackTestSupport.LEAKED_MARKER), "external entity through XMLFilter leaked");
+ }
+
@Test
void hardenedFilterFromTemplatesDoesNotLeakDocument() throws Exception {
final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
@@ -79,10 +97,12 @@ void hardenedFilterFromTemplatesDoesNotLeakDocument() throws Exception {
}
@Test
- void hardenedFilterDoesNotLeakExternalEntity() throws Exception {
- // The f003 vector: with no caller-set parent, the input must be parsed by a hardened reader, not a self-provisioned permissive one.
- final XMLFilter filter = SaxSurfaceTestSupport.hardenedFactory().newXMLFilter(AttackTestSupport.streamSource(IDENTITY_XSLT));
- assertFalse(filterAndCapture(filter, entityPayload()).contains(AttackTestSupport.LEAKED_MARKER), "external entity through XMLFilter leaked");
+ void unconfiguredFilterLeaksDocument() throws Exception {
+ final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
+ final XMLFilter filter = factory.newXMLFilter(AttackTestSupport.resourceSource("with-document.xsl"));
+ setPermissiveParentOnAndroid(filter);
+ assertTrue(filterAndCapture(filter, "<root/>").contains(AttackTestSupport.LEAKED_MARKER),
+ "unconfigured XMLFilter should resolve document()");
}
@Test
@@ -96,24 +116,4 @@ void unconfiguredFilterLeaksExternalEntity() throws Exception {
assertTrue(filterAndCapture(filter, entityPayload()).contains(AttackTestSupport.LEAKED_MARKER),
"unconfigured XMLFilter should resolve the input's external entity");
}
-
- @Test
- void unconfiguredFilterLeaksDocument() throws Exception {
- final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
- final XMLFilter filter = factory.newXMLFilter(AttackTestSupport.resourceSource("with-document.xsl"));
- setPermissiveParentOnAndroid(filter);
- assertTrue(filterAndCapture(filter, "<root/>").contains(AttackTestSupport.LEAKED_MARKER),
- "unconfigured XMLFilter should resolve document()");
- }
-
- /**
- * On Android, hand the unconfigured filter a permissive parent so Xalan's {@code TrAXFilter} uses it instead of self-provisioning an Expat reader on which
- * it enables {@code namespace-prefixes}, a feature Android's libexpat accepts but fails on mid-parse. The parent stays permissive (no floor), so the leak
- * these controls assert still occurs.
- */
- private static void setPermissiveParentOnAndroid(final XMLFilter filter) {
- if (AttackTestSupport.IS_ANDROID) {
- filter.setParent(AttackTestSupport.permissiveReader());
- }
- }
}
diff --git a/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java b/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
index 2c18720..a16bc6a 100644
--- a/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
+++ b/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
@@ -57,13 +57,6 @@ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
}
- @Test
- void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
- // Same declared-entity outcome as above on the compiled-expression entry point.
- final String result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
- assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the compiled XPath result: " + result);
- }
-
@Test
void hardenedXPathEvaluatesPlainDocument() throws Exception {
// Positive control: the hardened pre-parse still evaluates an entity-free document end to end.
@@ -72,6 +65,13 @@ void hardenedXPathEvaluatesPlainDocument() throws Exception {
assertEquals("plain text", result, "hardened XPath should evaluate a plain document");
}
+ @Test
+ void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
+ // Same declared-entity outcome as above on the compiled-expression entry point.
+ final String result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
+ assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the compiled XPath result: " + result);
+ }
+
@Test
void unconfiguredXPathEvaluateLeaks() throws Exception {
// Leak control: the unconfigured engine's internal parser resolves the entity, which is exactly what the wrapper exists to prevent.
diff --git a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java b/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
index e7d133a..8a016b9 100644
--- a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
+++ b/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
@@ -49,12 +49,10 @@ class XmlFactoriesTest {
"<?xml version=\"1.0\"?>\n<root><child>hello</child></root>\n";
@Test
- void newDocumentBuilderFactoryReturnsFreshInstance() {
- final DocumentBuilderFactory a = XmlFactories.newDocumentBuilderFactory();
- final DocumentBuilderFactory b = XmlFactories.newDocumentBuilderFactory();
- assertNotNull(a);
- assertNotNull(b);
- assertNotSame(a, b);
+ void benignDocumentParses() throws Exception {
+ final Document doc = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(new InputSource(new StringReader(BENIGN_XML)));
+ assertNotNull(doc);
+ assertNotNull(doc.getDocumentElement());
}
@Test
@@ -72,10 +70,12 @@ void newDocumentBuilderFactoryEnablesSecureProcessing() throws Exception {
}
@Test
- void benignDocumentParses() throws Exception {
- final Document doc = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(new InputSource(new StringReader(BENIGN_XML)));
- assertNotNull(doc);
- assertNotNull(doc.getDocumentElement());
+ void newDocumentBuilderFactoryReturnsFreshInstance() {
+ final DocumentBuilderFactory a = XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory b = XmlFactories.newDocumentBuilderFactory();
+ assertNotNull(a);
+ assertNotNull(b);
+ assertNotSame(a, b);
}
@Test
@@ -88,12 +88,11 @@ void newSAXParserFactoryReturnsFreshInstance() {
}
@Test
- void newXMLInputFactoryReturnsFreshInstance() {
- final XMLInputFactory a = XmlFactories.newXMLInputFactory();
- final XMLInputFactory b = XmlFactories.newXMLInputFactory();
+ void newSchemaFactoryReturnsFreshInstance() throws Exception {
+ final SchemaFactory a = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ final SchemaFactory b = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
assertNotSame(a, b);
- assertEquals(Boolean.TRUE, a.getProperty(XMLInputFactory.SUPPORT_DTD));
- assertEquals(Boolean.FALSE, a.getProperty(XMLInputFactory.IS_VALIDATING));
+ assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
}
@Test
@@ -104,17 +103,18 @@ void newTransformerFactoryReturnsFreshInstance() {
}
@Test
- void newXPathFactoryReturnsFreshInstance() throws Exception {
- final XPathFactory a = XmlFactories.newXPathFactory();
- final XPathFactory b = XmlFactories.newXPathFactory();
+ void newXMLInputFactoryReturnsFreshInstance() {
+ final XMLInputFactory a = XmlFactories.newXMLInputFactory();
+ final XMLInputFactory b = XmlFactories.newXMLInputFactory();
assertNotSame(a, b);
- assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+ assertEquals(Boolean.TRUE, a.getProperty(XMLInputFactory.SUPPORT_DTD));
+ assertEquals(Boolean.FALSE, a.getProperty(XMLInputFactory.IS_VALIDATING));
}
@Test
- void newSchemaFactoryReturnsFreshInstance() throws Exception {
- final SchemaFactory a = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- final SchemaFactory b = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ void newXPathFactoryReturnsFreshInstance() throws Exception {
+ final XPathFactory a = XmlFactories.newXPathFactory();
+ final XPathFactory b = XmlFactories.newXPathFactory();
assertNotSame(a, b);
assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
}