This is an automated email from the ASF dual-hosted git repository.
ppkarwasz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-xml.git
The following commit(s) were added to refs/heads/main by this push:
new 86d6166 Refactor XmlFactories into their hardening target factories.
(#53)
86d6166 is described below
commit 86d6166d64ec7c8758291509fd844ce13d8f569c
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Aug 27 12:56:18 2026 -0400
Refactor XmlFactories into their hardening target factories. (#53)
Missing Java 8 methods will be added in a follow up PR.
---
RELEASE-NOTES.txt | 2 +-
pom.xml | 2 +-
src/changes/changes.xml | 6 +-
.../commons/xml/FallbackIgnoreEntityResolver2.java | 2 +-
.../xml/HardeningDocumentBuilderFactory.java | 34 +++-
.../org/apache/commons/xml/HardeningException.java | 17 +-
.../commons/xml/HardeningSAXParserFactory.java | 31 ++-
.../apache/commons/xml/HardeningSchemaFactory.java | 39 +++-
.../commons/xml/HardeningTransformerFactory.java | 36 +++-
.../commons/xml/HardeningXMLInputFactory.java | 21 +-
.../apache/commons/xml/HardeningXPathFactory.java | 23 ++-
.../apache/commons/xml/TransformerHardener.java | 2 +-
.../java/org/apache/commons/xml/XmlFactories.java | 213 ---------------------
.../java/org/apache/commons/xml/package-info.java | 47 ++++-
src/site/markdown/index.md | 36 ++--
src/site/markdown/threat_model.md | 22 +--
.../commons/xml/AssociatedStylesheetTest.java | 2 +-
.../org/apache/commons/xml/AttackTestSupport.java | 98 +++++-----
.../org/apache/commons/xml/DenyUnresolvedTest.java | 6 +-
.../commons/xml/EntityResolverFloorTest.java | 22 +--
...sTest.java => HardeningFactoriesSmokeTest.java} | 48 +++--
.../org/apache/commons/xml/ResetHardeningTest.java | 8 +-
.../apache/commons/xml/SaxSurfaceTestSupport.java | 4 +-
.../commons/xml/SchemaFactoryLanguageTest.java | 4 +-
.../apache/commons/xml/SchemaLocationDomTest.java | 2 +-
.../commons/xml/SchemaLocationPropertyTest.java | 4 +-
.../apache/commons/xml/SchemaLocationSaxTest.java | 2 +-
.../apache/commons/xml/ShadingFootprintTest.java | 16 +-
.../java/org/apache/commons/xml/XIncludeTest.java | 22 +--
.../commons/xml/XMLFilterParseStringTest.java | 2 +-
.../apache/commons/xml/XPathInputSourceTest.java | 6 +-
31 files changed, 382 insertions(+), 397 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 04922b1..16be4d5 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -29,7 +29,7 @@ First release.
New features
------------
-* Secure-by-default JAXP factory creation via XmlFactories,
with implementation-specific hardening recipes for the
+* Secure-by-default JAXP factory creation via
org.apache.commons.xml, with implementation-specific hardening recipes for the
stock JDK, Android, Apache Xalan, Apache Xerces, Woodstox,
and Saxon-HE.
diff --git a/pom.xml b/pom.xml
index db96027..d9fdfc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -149,7 +149,7 @@ limitations under the License.
<artifactId>maven-surefire-plugin</artifactId>
<!--
Make the underlying JAXP factories as permissive as the JDK allows.
Hardened factories produced by
- XmlFactories should still block every attack the test suite throws
at them; if a test passes only
+ org.apache.commons.xml should still block every attack the test
suite throws at them; if a test passes only
because the JDK's defaults intervened, we want it to fail here so
the gap shows up.
Inherited by every execution defined below.
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index a7b57e8..09bbc39 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -28,14 +28,14 @@ The <action> type attribute can be add, update, fix, or
remove.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/changes/2.0.0
https://maven.apache.org/xsd/changes-2.0.0.xsd">
<properties>
- <title>Apache Commons XML - Changes</title>
+ <title>Apache Commons XML Changes</title>
</properties>
<body>
<release version="1.0.0" date="YYYY-MM-DD" description="First release,
requires Java 8 or above.">
<!-- SPECIAL -->
- <action type="add" dev="ggregory" due-to="Piotr P. Karwasz, Gary
Gregory">This is the first release.</action>
+ <action type="add" dev="ggregory" due-to="Piotr P. Karwasz, Gary
Gregory">This is the first release of Apache Commons XML.</action>
<!-- ADD -->
- <action type="add" dev="ppkarwasz" due-to="Piotr P. Karwasz, Gary
Gregory">Add secure-by-default JAXP factory creation via XmlFactories, donated
from the copernik-xml-factory project
(https://github.com/copernik-eu/copernik-xml-factory) and covering the stock
JDK, Android, Apache Xalan, Apache Xerces, Woodstox, and Saxon-HE.</action>
+ <action type="add" dev="ppkarwasz" due-to="Piotr P. Karwasz, Gary
Gregory">Add secure-by-default JAXP factory creation via
org.apache.commons.xml, donated from the copernik-xml-factory project
(https://github.com/copernik-eu/copernik-xml-factory) and covering the stock
JDK, Android, Apache Xalan, Apache Xerces, Woodstox, and Saxon-HE.</action>
<action type="add" dev="ppkarwasz" due-to="Piotr P. Karwasz, Gary
Gregory" issue="COMMONSXML-9">Install a non-removable resolver floor on every
resolver channel (EntityResolver, LSResourceResolver, URIResolver, and
XMLResolver), routing caller-supplied resolvers through it as
allow-lists.</action>
<action type="add" dev="ppkarwasz" due-to="Piotr P. Karwasz, Gary
Gregory">Harden the SAXTransformerFactory extension surface
(TransformerHandler, TemplatesHandler, and XMLFilter) and
TransformerFactory.getAssociatedStylesheet.</action>
<action type="add" dev="ppkarwasz" due-to="Piotr P. Karwasz, Jarek
Potiuk, Gary Gregory">Document the threat model on the project site, including
the denied-fetch contract and the supported runtime floor (OpenJDK 8 and
Android API 33 or later).</action>
diff --git
a/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
b/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
index 0b7b774..bda79e2 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
@@ -109,7 +109,7 @@ final EntityResolver getDelegate() {
* @param baseURI The base URI for relative resolution, or {@code null}.
* @param systemId The system identifier of the unresolved entity.
* @return An empty {@link InputSource} carrying the requested identifiers.
- * @throws SAXException when {@value XmlFactories#THROW_ON_UNRESOLVED} is
set: unresolved references are rejected instead of resolved to empty.
+ * @throws SAXException when {@value
HardeningException#THROW_ON_UNRESOLVED} is set: unresolved references are
rejected instead of resolved to empty.
* @throws IOException never by the default implementation.
*/
protected InputSource onUnresolved(final String name, final String
publicId, final String baseURI, final String systemId) throws SAXException,
IOException {
diff --git
a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
index 19a6ec3..fc6aeb5 100644
--- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
@@ -19,8 +19,10 @@
import java.util.Objects;
+import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;
@@ -28,12 +30,36 @@
/**
* {@link DocumentBuilderFactory} wrapper that keeps an ignore-all {@link
EntityResolver} floor on every {@link DocumentBuilder} produced.
- *
- * <p>Wraps each produced builder in a {@link HardeningDocumentBuilder};
required when the underlying factory carries no resolver of its own and does
not honor
+ * <p>
+ * Wraps each produced builder in a {@link HardeningDocumentBuilder}; required
when the underlying factory carries no resolver of its own and does not honor
* JAXP 1.5 {@code ACCESS_EXTERNAL_*} (e.g. the external Xerces distribution).
A caller-set resolver is routed through the floor rather than replacing it. Kept
- * as a standalone wrapper so any hardener can reuse the floor.</p>
+ * as a standalone wrapper so any hardener can reuse the floor.
+ * </p>
+ *
+ * @see org.apache.commons.xml
*/
-final class HardeningDocumentBuilderFactory extends DocumentBuilderFactory {
+public final class HardeningDocumentBuilderFactory extends
DocumentBuilderFactory {
+
+ /**
+ * Returns a new, hardened {@link DocumentBuilderFactory}.
+ * <p>
+ * Beyond the three universal guarantees on {@link
org.apache.commons.xml}, XInclude resolution is denied by default. When
+ * {@link DocumentBuilderFactory#setXIncludeAware(boolean)
setXIncludeAware(true)} is called on the returned factory, the parser will
process
+ * {@code xi:include} elements but every external resource lookup is
rejected. To permit specific trusted resources, install an
+ * {@link org.xml.sax.EntityResolver EntityResolver} on the {@link
DocumentBuilder} that allow-lists them; any href the resolver does not
explicitly allow
+ * stays blocked.
+ * </p>
+ *
+ * @return A hardened factory.
+ * @throws IllegalStateException Thrown if a required hardening
setting cannot be applied to the underlying implementation.
+ * @throws IllegalStateException Thrown if a (non-Andoid) factory
cannot support the secure processing feature
+ * {@link
XMLConstants#FEATURE_SECURE_PROCESSING}.
+ * @throws FactoryConfigurationError Thrown from a factory in case of a
{@link java.util.ServiceConfigurationError service configuration error} or if
the
+ * implementation is not available or
cannot be instantiated.
+ */
+ public static DocumentBuilderFactory newInstance() {
+ return
DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+ }
private final DocumentBuilderFactory delegate;
diff --git a/src/main/java/org/apache/commons/xml/HardeningException.java
b/src/main/java/org/apache/commons/xml/HardeningException.java
index 4dd123c..163ead0 100644
--- a/src/main/java/org/apache/commons/xml/HardeningException.java
+++ b/src/main/java/org/apache/commons/xml/HardeningException.java
@@ -47,7 +47,7 @@ final class HardeningException extends IllegalStateException {
*/
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.THROW_ON_UNRESOLVED, type, namespace,
publicId, systemId, baseURI);
}
/**
@@ -66,14 +66,23 @@ static HardeningException settingFailed(final String kind,
final String name, fi
/**
* Whether unresolved external references must be rejected instead of
resolved to empty content.
*
- * <p>Read per resolution, so the {@value
XmlFactories#THROW_ON_UNRESOLVED} system property also toggles factories that
already exist.</p>
+ * <p>Read per resolution, so the {@value
HardeningException#THROW_ON_UNRESOLVED} system property also toggles factories
that already exist.</p>
*
- * @return {@code true} when the {@value XmlFactories#THROW_ON_UNRESOLVED}
system property is set.
+ * @return {@code true} when the {@value
HardeningException#THROW_ON_UNRESOLVED} system property is set.
*/
static boolean throwOnUnresolved() {
- return Boolean.getBoolean(XmlFactories.THROW_ON_UNRESOLVED);
+ return Boolean.getBoolean(HardeningException.THROW_ON_UNRESOLVED);
}
+ /**
+ * System property that switches unresolved external references from the
default empty resolution to a thrown exception.
+ * <p>
+ * How to enable: set {@code
-Dorg.apache.commons.xml.throwOnUnresolved=true}. The property is read at
resolution time, so it also applies to factories
+ * created before it was set; references resolved by a caller-supplied
resolver are unaffected.
+ * </p>
+ */
+ static final String THROW_ON_UNRESOLVED =
"org.apache.commons.xml.throwOnUnresolved";
+
HardeningException(final String message, final Throwable cause) {
super(message, cause);
}
diff --git
a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
index 0454f15..abc3521 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
@@ -19,6 +19,7 @@
import java.util.Objects;
+import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -31,12 +32,33 @@
/**
* Universal SAX factory wrapper that funnels every produced parser through
{@link SAXParserHardener#hardenReader(XMLReader)}.
+ * <p>
+ * {@link SAXParserFactory} exposes only a feature API and no property API, so
the per-parse hardening (limits, entity blocking, implementation-specific
fixups)
+ * has to run on each {@link XMLReader} the factory produces. This wrapper
returns a {@link HardeningSAXParser}, which applies that hardening lazily to
both the
+ * SAX 2 {@link XMLReader} and the SAX 1 {@link org.xml.sax.Parser} it exposes.
+ * </p>
*
- * <p>{@link SAXParserFactory} exposes only a feature API and no property API,
so the per-parse hardening (limits, entity blocking, implementation-specific
- * fixups) has to run on each {@link XMLReader} the factory produces. This
wrapper returns a {@link HardeningSAXParser}, which applies that hardening
lazily to
- * both the SAX 2 {@link XMLReader} and the SAX 1 {@link org.xml.sax.Parser}
it exposes.</p>
+ * @see org.apache.commons.xml
*/
-final class HardeningSAXParserFactory extends SAXParserFactory {
+public final class HardeningSAXParserFactory extends SAXParserFactory {
+
+ /**
+ * Returns a new, hardened {@link SAXParserFactory}.
+ * <p>
+ * Beyond the three universal guarantees on {@link
org.apache.commons.xml}, XInclude resolution is denied by default. When
+ * {@link SAXParserFactory#setXIncludeAware(boolean)
setXIncludeAware(true)} is called on the returned factory, the parser will
process {@code xi:include}
+ * elements but every external resource lookup is rejected. To permit
specific trusted resources, install an {@link org.xml.sax.EntityResolver
+ * EntityResolver} on the {@link org.xml.sax.XMLReader} that allow-lists
them; any href the resolver does not explicitly allow stays blocked.
+ * </p>
+ *
+ * @return A hardened factory.
+ * @throws IllegalStateException Thrown if a required hardening
setting cannot be applied to the underlying implementation.
+ * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory}
in case of a {@link java.util.ServiceConfigurationError service configuration
+ * error} or if the implementation is
not available or cannot be instantiated.
+ */
+ public static SAXParserFactory newInstance() {
+ return SAXParserHardener.harden(SAXParserFactory.newInstance());
+ }
private final SAXParserFactory delegate;
@@ -104,5 +126,4 @@ public void setValidating(final boolean validating) {
public void setXIncludeAware(final boolean state) {
delegate.setXIncludeAware(state);
}
-
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
index c55fe91..48c3c1a 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
@@ -24,6 +24,7 @@
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.SchemaFactoryConfigurationError;
import javax.xml.validation.Validator;
import org.w3c.dom.ls.LSResourceResolver;
@@ -34,7 +35,7 @@
/**
* Capability-driven hardening wrapper for any {@link SchemaFactory} on the
classpath, the same recipe for every implementation. It is the entry point
reached
- * by {@link XmlFactories#newSchemaFactory(String)}; there is no
per-implementation branching, no {@code FEATURE_SECURE_PROCESSING} and no limit
configuration on the
+ * by {@link HardeningSchemaFactory#newInstance(String)}; there is no
per-implementation branching, no {@code FEATURE_SECURE_PROCESSING} and no limit
configuration on the
* factory itself.
*
* <p>Three layers cooperate:</p>
@@ -47,14 +48,18 @@
* <li>{@link HardeningValidator} rewrites the Source on every {@link
Validator#validate(Source)} call.</li>
* </ol>
*
- * <p>The hardened reader supplied by {@link
SAXParserHardener#hardenSource(Source)} already carries {@code
FEATURE_SECURE_PROCESSING} and the processing limits, so a
+ * <p>
+ * The hardened reader supplied by {@link
SAXParserHardener#hardenSource(Source)} already carries {@code
FEATURE_SECURE_PROCESSING} and the processing limits, so a
* DOCTYPE, external entity or Billion Laughs payload in the schema or
instance document is bounded there rather than on this factory. The JAXP 1.5
* {@code ACCESS_EXTERNAL_*} properties are deliberately not set: the resolver
floor already blocks the same fetches on every implementation, and the JDK 8
* {@code SchemaFactory} has a bug whereby those properties keep blocking even
when a caller's own resolver would grant the access. The floor is a
non-removable
* lower bound: a caller-set {@link LSResourceResolver} is routed through it
(opting a specific lookup in by returning a non-{@code null} result) rather than
- * replacing it, so hardening cannot be dropped by swapping the resolver.</p>
+ * replacing it, so hardening cannot be dropped by swapping the resolver.
+ * </p>
+ *
+ * @see org.apache.commons.xml
*/
-final class HardeningSchemaFactory extends SchemaFactory {
+public final class HardeningSchemaFactory extends SchemaFactory {
/**
* Hardens every schema source through {@link
SAXParserHardener#hardenSource(Source)}.
@@ -77,6 +82,30 @@ private static Source[] harden(final Source[] schemas)
throws SAXException {
return hardened;
}
+ /**
+ * Returns a new, hardened {@link SchemaFactory} for the given schema
language.
+ * <p>
+ * Beyond the three universal guarantees on {@link org.apache.commons.xml}:
+ * </p>
+ * <ul>
+ * <li>{@code xs:import}, {@code xs:include} and {@code xs:redefine}
schemaLocation URIs are not resolved during schema compilation, and</li>
+ * <li>{@code xsi:schemaLocation} / {@code xsi:noNamespaceSchemaLocation}
hints in instance documents are not resolved during validation.</li>
+ * </ul>
+ * <p>
+ * The same guarantees apply to {@link javax.xml.validation.Validator} and
{@link javax.xml.validation.ValidatorHandler} instances produced from the
+ * resulting {@link javax.xml.validation.Schema}.
+ * </p>
+ *
+ * @param schemaLanguage The schema language, as accepted by {@link
SchemaFactory#newInstance(String)}.
+ * @return A hardened factory.
+ * @throws IllegalArgumentException Thrown if no implementation of
the schema language is available.
+ * @throws NullPointerException Thrown if {@code
schemaLanguage} is {@code null}.
+ * @throws SchemaFactoryConfigurationError Thrown if a configuration error
is encountered.
+ */
+ public static SchemaFactory newInstance(final String schemaLanguage) {
+ return new
HardeningSchemaFactory(SchemaFactory.newInstance(schemaLanguage));
+ }
+
private final SchemaFactory delegate;
private final FallbackIgnoreLSResourceResolver floor = new
FallbackIgnoreLSResourceResolver(null);
@@ -144,12 +173,12 @@ public void setFeature(final String name, final boolean
value) throws SAXNotReco
delegate.setFeature(name, value);
}
+
@Override
public void setProperty(final String name, final Object object) throws
SAXNotRecognizedException, SAXNotSupportedException {
delegate.setProperty(name, object);
}
-
@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.
diff --git
a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
index 948c5b9..b64bcfc 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
@@ -30,6 +30,7 @@
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
@@ -50,7 +51,7 @@
*
* <p>Used by providers whose underlying TrAX implementation pulls a new
{@code SAXParserFactory.newInstance()} for any Source that is not already a
* {@link SAXSource} carrying its own {@link XMLReader}, and only sets {@link
javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING FSP} on the resulting reader.
- * Wrapping the factory and rewriting the Source upstream guarantees the parse
runs through an {@link XmlFactories}-hardened reader instead.</p>
+ * Wrapping the factory and rewriting the Source upstream guarantees the parse
runs through an {@link org.apache.commons.xml}-hardened reader instead.</p>
*
* <p>Three layers cooperate:</p>
* <ol>
@@ -69,11 +70,13 @@
* <h2>Caveats</h2>
* <ul>
* <li>A {@link SAXSource} that carries its own {@link XMLReader} is trusted
as-is: the caller is expected to supply a hardened reader (via
- * {@link XmlFactories#newSAXParserFactory()}) in that case. The same
applies to the SAX events a caller feeds into a handler, and to a parent reader
a
+ * {@link HardeningSAXParserFactory#newInstance()}) in that case. The
same applies to the SAX events a caller feeds into a handler, and to a parent
reader a
* caller sets on a returned {@link XMLFilter}.</li>
* </ul>
+ *
+ * @see org.apache.commons.xml
*/
-final class HardeningTransformerFactory extends SAXTransformerFactory {
+public 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}
@@ -288,4 +291,31 @@ public void setFeature(final String name, final boolean
value) throws Transforme
public void setURIResolver(final URIResolver resolver) {
floor.setDelegate(resolver);
}
+
+ /**
+ * Returns a new, hardened {@link TransformerFactory}.
+ * <p>
+ * Beyond the three universal guarantees on {@link
org.apache.commons.xml}: {@code xsl:import}, {@code xsl:include} and {@code
document()} URIs are not resolved.
+ * </p>
+ * <p>
+ * The guarantees govern what the transform reads, not what it writes: an
output instruction like {@code xsl:result-document} still writes wherever the
+ * stylesheet directs, so an untrusted stylesheet's output destinations
must be restricted outside the library.
+ * </p>
+ * <p>
+ * The guarantees apply to every parser the factory creates internally for
the standard {@link TransformerFactory} entry points: stylesheet compilation
+ * ({@link TransformerFactory#newTemplates(javax.xml.transform.Source)
newTemplates(Source)},
+ * {@link TransformerFactory#newTransformer(javax.xml.transform.Source)
newTransformer(Source)}) and source-document reading at
+ * {@code Transformer.transform(Source, Result)} time.
+ * </p>
+ * <p>
+ * The {@link javax.xml.transform.sax.SAXTransformerFactory} extension
methods ({@code newTransformerHandler(..)}, {@code newTemplatesHandler()},
+ * {@code newXMLFilter(..)}), if reachable by casting the returned
factory, produce objects carrying the same guarantees.
+ * </p>
+ *
+ * @return A hardened factory.
+ * @throws IllegalStateException if a required hardening setting cannot be
applied to the underlying implementation.
+ */
+ public static TransformerFactory newInstance() {
+ return TransformerHardener.harden(TransformerFactory.newInstance());
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
index a0635b6..63d21d8 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
@@ -21,6 +21,7 @@
import java.io.Reader;
import java.util.Objects;
+import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.stream.EventFilter;
import javax.xml.stream.StreamFilter;
import javax.xml.stream.XMLEventReader;
@@ -49,8 +50,10 @@
* wrapped in a new floor. This matters because Woodstox does not chain
resolvers: when a resolver returns {@code null}, {@code DefaultInputResolver}
falls
* through to fetching the systemId URL itself, so a caller-set resolver that
returns {@code null} must still land behind the floor. {@link
#getXMLResolver()} and
* {@code getProperty} report the caller's resolver unwrapped.</p>
+ *
+ * @see org.apache.commons.xml
*/
-final class HardeningXMLInputFactory extends XMLInputFactory {
+public final class HardeningXMLInputFactory extends XMLInputFactory {
private static boolean isResolverProperty(final String name) {
return XMLInputFactory.RESOLVER.equals(name)
@@ -59,6 +62,20 @@ private static boolean isResolverProperty(final String name)
{
|| StaxHardener.WSTX_UNDECLARED_ENTITY_RESOLVER.equals(name);
}
+ /**
+ * Returns a new, hardened {@link XMLInputFactory}.
+ * <p>
+ * The three universal guarantees on {@link org.apache.commons.xml} apply;
StAX exposes no additional vectors beyond them.
+ * </p>
+ *
+ * @return A hardened factory.
+ * @throws IllegalStateException Thrown if a required hardening
setting cannot be applied to the underlying implementation.
+ * @throws FactoryConfigurationError Thrown if an instance of this factory
cannot be loaded.
+ */
+ public static XMLInputFactory newInstance() {
+ return StaxHardener.harden(XMLInputFactory.newInstance());
+ }
+
private static XMLResolver unwrap(final XMLResolver resolver) {
return resolver instanceof FallbackIgnoreXMLResolver ?
((FallbackIgnoreXMLResolver) resolver).getDelegate() : resolver;
}
@@ -151,12 +168,12 @@ public XMLStreamReader createXMLStreamReader(final String
systemId, final Reader
return delegate.createXMLStreamReader(systemId, reader);
}
+
@Override
public XMLEventAllocator getEventAllocator() {
return delegate.getEventAllocator();
}
-
@Override
public Object getProperty(final String name) {
if (isResolverProperty(name)) {
diff --git a/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
b/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
index 6668057..feccab9 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
@@ -31,8 +31,10 @@
* <p>Required because {@link
javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the factory governs only
the XPath engine: the stock JDK and Apache Xalan
* implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry
points by provisioning an internal document parser the feature does not reach.
* The wrapper performs that document build itself through a hardened parser
instead; see {@link HardeningXPath}.</p>
+ *
+ * @see org.apache.commons.xml
*/
-final class HardeningXPathFactory extends XPathFactory {
+public final class HardeningXPathFactory extends XPathFactory {
private final XPathFactory delegate;
@@ -76,4 +78,23 @@ public void setXPathFunctionResolver(final
XPathFunctionResolver resolver) {
public void setXPathVariableResolver(final XPathVariableResolver resolver)
{
delegate.setXPathVariableResolver(resolver);
}
+
+ /**
+ * Returns a new, hardened {@link XPathFactory} for the default XPath
object model.
+ * <p>
+ * Beyond the three universal guarantees on {@link
org.apache.commons.xml}, URI-fetching XPath 3.1+ functions ({@code doc()},
{@code collection()},
+ * {@code unparsed-text()}) are not resolved.
+ * </p>
+ * <p>
+ * The guarantees also cover the document parse behind {@code
XPath.evaluate(String, InputSource)} and {@code
XPathExpression.evaluate(InputSource)}: the
+ * input document is built through a hardened, namespace-aware {@link
javax.xml.parsers.DocumentBuilder} instead of the engine's internal parser.
+ * </p>
+ *
+ * @return A hardened factory.
+ * @throws IllegalStateException Thrown if a required hardening setting
cannot be applied to the underlying implementation.
+ * @throws RuntimeException Thrown if there is a failure in creating
an {@link XPathFactory} for the default object model.
+ */
+ public static XPathFactory newXPathFactory() {
+ return XPathHardener.harden(XPathFactory.newInstance());
+ }
}
diff --git a/src/main/java/org/apache/commons/xml/TransformerHardener.java
b/src/main/java/org/apache/commons/xml/TransformerHardener.java
index 5b937b5..9945c24 100644
--- a/src/main/java/org/apache/commons/xml/TransformerHardener.java
+++ b/src/main/java/org/apache/commons/xml/TransformerHardener.java
@@ -41,7 +41,7 @@
* routed through the floor rather than replacing it, so a caller can
opt a specific URI in but cannot reopen the fetch.</li>
* <li><strong>{@link HardeningTransformerFactory}</strong>: required.
Both implementations fall back to {@code SAXParserFactory.newInstance()} to
parse a
* stylesheet or source document that does not carry its own reader,
and only set FSP on it; wrapping the factory rewrites every {@link Source}
through an
- * {@link XmlFactories}-hardened reader instead.</li>
+ * {@link org.apache.commons.xml}-hardened reader instead.</li>
* </ul>
*/
final class TransformerHardener {
diff --git a/src/main/java/org/apache/commons/xml/XmlFactories.java
b/src/main/java/org/apache/commons/xml/XmlFactories.java
deleted file mode 100644
index 0330be0..0000000
--- a/src/main/java/org/apache/commons/xml/XmlFactories.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.xml;
-
-import javax.xml.XMLConstants;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.FactoryConfigurationError;
-import javax.xml.parsers.SAXParserFactory;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.SchemaFactoryConfigurationError;
-import javax.xml.xpath.XPathFactory;
-
-/**
- * Entry point for obtaining hardened JAXP factories.
- *
- * <p>Every method on this class returns a <em>new, hardened</em> factory
instance. No caching or pooling is performed; callers on a hot path are
responsible
- * for their own caching.</p>
- *
- * <h2>Hardening guarantees</h2>
- *
- * <p>Every factory returned by this class makes the same three guarantees,
regardless of which JAXP implementation is on the classpath:</p>
- *
- * <ul>
- * <li><strong>External DTDs are not fetched.</strong></li>
- * <li><strong>External entities are not resolved.</strong></li>
- * <li><strong>Internal entity expansion is bounded</strong> by the
platform's secure-processing limit, so DoS payloads such as Billion Laughs are
rejected
- * before they exhaust resources.</li>
- * </ul>
- *
- * <p>These guarantees are defined on OpenJDK 8 or later (and JDK
distributions built from it). No version of Android supports
- * {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING}, so on Android
(API level 19 or later) the hardening is applied as best-effort without a
guarantee,
- * tested as complete starting with API level 33; see the threat model's
"Assumptions about the environment".</p>
- *
- * <p>The guarantees hold whether or not the caller opts into DTD validation
- * ({@link javax.xml.parsers.DocumentBuilderFactory#setValidating(boolean)
setValidating(true)}) or attaches a compiled XSD via
- * {@link
javax.xml.parsers.DocumentBuilderFactory#setSchema(javax.xml.validation.Schema)
setSchema}: every external resource the validation would otherwise
- * fetch (the DTD itself, an {@code xsi:schemaLocation} hint, an external
entity referenced from the DTD) remains blocked.</p>
- *
- * <p>Each method on this class adds factory-specific guarantees on top of the
three above, documented on the corresponding {@code newXxxFactory()} method.</p>
- *
- * <p>An unresolved external reference resolves to empty content by default,
so the parse continues without the resource. To reject it with an exception
- * instead, set the system property {@code
org.apache.commons.xml.throwOnUnresolved} to {@code true}; the property is read
at resolution time, and references
- * resolved by a caller-supplied resolver are unaffected.</p>
- *
- * <h2>Caller-supplied URIs</h2>
- *
- * <p>A top-level URI passed directly by the caller is fetched as-is: {@code
StreamSource(systemId)}, {@code DocumentBuilder.parse(String)}, or a
- * {@code SAXSource} built from a system id all cause the JAXP implementation
to open that URI without consulting the hardening layer. Use a
- * {@link javax.xml.transform.URIResolver} or {@link
org.xml.sax.EntityResolver} if you need to restrict the top-level fetch.</p>
- *
- * <h2>Thread safety</h2>
- *
- * <p>The returned factories inherit the thread-safety properties of the
underlying JAXP implementation, which in practice means they are <strong>not
- * guaranteed to be thread-safe</strong>. Create a new factory per thread or
synchronize externally.</p>
- *
- * <p>This class itself is thread-safe: all methods are static and
stateless.</p>
- */
-public final class XmlFactories {
-
- /**
- * System property that switches unresolved external references from the
default empty resolution to a thrown exception.
- * <p>
- * How to enable: set {@code
-Dorg.apache.commons.xml.throwOnUnresolved=true}. The property is read at
resolution time, so it also applies to factories
- * created before it was set; references resolved by a caller-supplied
resolver are unaffected.
- * </p>
- */
- static final String THROW_ON_UNRESOLVED =
"org.apache.commons.xml.throwOnUnresolved";
-
- /**
- * Returns a new, hardened {@link DocumentBuilderFactory}.
- * <p>
- * Beyond the three universal guarantees on {@link XmlFactories}, XInclude
resolution is denied by default. When
- * {@link DocumentBuilderFactory#setXIncludeAware(boolean)
setXIncludeAware(true)} is called on the returned factory, the parser will
process
- * {@code xi:include} elements but every external resource lookup is
rejected. To permit specific trusted resources, install an
- * {@link org.xml.sax.EntityResolver EntityResolver} on the {@link
DocumentBuilder} that allow-lists them; any href the resolver does not
explicitly allow
- * stays blocked.
- * </p>
- *
- * @return A hardened factory.
- * @throws IllegalStateException Thrown if a required hardening
setting cannot be applied to the underlying implementation.
- * @throws IllegalStateException Thrown if a (non-Andoid) factory
cannot support the secure processing feature
- * {@link
XMLConstants#FEATURE_SECURE_PROCESSING}.
- * @throws FactoryConfigurationError Thrown from a factory in case of a
{@link java.util.ServiceConfigurationError service configuration error} or if
the
- * implementation is not available or
cannot be instantiated.
- */
- public static DocumentBuilderFactory newDocumentBuilderFactory() {
- return
DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
- }
-
- /**
- * Returns a new, hardened {@link SAXParserFactory}.
- * <p>
- * Beyond the three universal guarantees on {@link XmlFactories}, XInclude
resolution is denied by default. When
- * {@link SAXParserFactory#setXIncludeAware(boolean)
setXIncludeAware(true)} is called on the returned factory, the parser will
process {@code xi:include}
- * elements but every external resource lookup is rejected. To permit
specific trusted resources, install an {@link org.xml.sax.EntityResolver
- * EntityResolver} on the {@link org.xml.sax.XMLReader} that allow-lists
them; any href the resolver does not explicitly allow stays blocked.
- * </p>
- *
- * @return A hardened factory.
- * @throws IllegalStateException Thrown if a required hardening
setting cannot be applied to the underlying implementation.
- * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory}
in case of a {@link java.util.ServiceConfigurationError service configuration
- * error} or if the implementation is
not available or cannot be instantiated.
- */
- public static SAXParserFactory newSAXParserFactory() {
- return SAXParserHardener.harden(SAXParserFactory.newInstance());
- }
-
- /**
- * Returns a new, hardened {@link SchemaFactory} for the given schema
language.
- * <p>
- * Beyond the three universal guarantees on {@link XmlFactories}:
- * </p>
- * <ul>
- * <li>{@code xs:import}, {@code xs:include} and {@code xs:redefine}
schemaLocation URIs are not resolved during schema compilation, and</li>
- * <li>{@code xsi:schemaLocation} / {@code xsi:noNamespaceSchemaLocation}
hints in instance documents are not resolved during validation.</li>
- * </ul>
- * <p>
- * The same guarantees apply to {@link javax.xml.validation.Validator} and
{@link javax.xml.validation.ValidatorHandler} instances produced from the
- * resulting {@link javax.xml.validation.Schema}.
- * </p>
- *
- * @param schemaLanguage The schema language, as accepted by {@link
SchemaFactory#newInstance(String)}.
- * @return A hardened factory.
- * @throws IllegalArgumentException Thrown if no implementation of
the schema language is available.
- * @throws NullPointerException Thrown if {@code
schemaLanguage} is {@code null}.
- * @throws SchemaFactoryConfigurationError Thrown if a configuration error
is encountered.
- */
- public static SchemaFactory newSchemaFactory(final String schemaLanguage) {
- return new
HardeningSchemaFactory(SchemaFactory.newInstance(schemaLanguage));
- }
-
- /**
- * Returns a new, hardened {@link TransformerFactory}.
- * <p>
- * Beyond the three universal guarantees on {@link XmlFactories}: {@code
xsl:import}, {@code xsl:include} and {@code document()} URIs are not resolved.
- * </p>
- * <p>
- * The guarantees govern what the transform reads, not what it writes: an
output instruction like {@code xsl:result-document} still writes wherever the
- * stylesheet directs, so an untrusted stylesheet's output destinations
must be restricted outside the library.
- * </p>
- * <p>
- * The guarantees apply to every parser the factory creates internally for
the standard {@link TransformerFactory} entry points: stylesheet compilation
- * ({@link TransformerFactory#newTemplates(javax.xml.transform.Source)
newTemplates(Source)},
- * {@link TransformerFactory#newTransformer(javax.xml.transform.Source)
newTransformer(Source)}) and source-document reading at
- * {@code Transformer.transform(Source, Result)} time.
- * </p>
- * <p>
- * The {@link javax.xml.transform.sax.SAXTransformerFactory} extension
methods ({@code newTransformerHandler(..)}, {@code newTemplatesHandler()},
- * {@code newXMLFilter(..)}), if reachable by casting the returned
factory, produce objects carrying the same guarantees.
- * </p>
- *
- * @return A hardened factory.
- * @throws IllegalStateException if a required hardening setting cannot be
applied to the underlying implementation.
- */
- public static TransformerFactory newTransformerFactory() {
- return TransformerHardener.harden(TransformerFactory.newInstance());
- }
-
- /**
- * Returns a new, hardened {@link XMLInputFactory}.
- * <p>
- * The three universal guarantees on {@link XmlFactories} apply; StAX
exposes no additional vectors beyond them.
- * </p>
- *
- * @return A hardened factory.
- * @throws IllegalStateException Thrown if a required hardening
setting cannot be applied to the underlying implementation.
- * @throws FactoryConfigurationError Thrown if an instance of this factory
cannot be loaded.
- */
- public static XMLInputFactory newXMLInputFactory() {
- return StaxHardener.harden(XMLInputFactory.newInstance());
- }
-
- /**
- * Returns a new, hardened {@link XPathFactory} for the default XPath
object model.
- * <p>
- * Beyond the three universal guarantees on {@link XmlFactories},
URI-fetching XPath 3.1+ functions ({@code doc()}, {@code collection()},
- * {@code unparsed-text()}) are not resolved.
- * </p>
- * <p>
- * The guarantees also cover the document parse behind {@code
XPath.evaluate(String, InputSource)} and {@code
XPathExpression.evaluate(InputSource)}: the
- * input document is built through a hardened, namespace-aware {@link
javax.xml.parsers.DocumentBuilder} instead of the engine's internal parser.
- * </p>
- *
- * @return A hardened factory.
- * @throws IllegalStateException Thrown if a required hardening setting
cannot be applied to the underlying implementation.
- * @throws RuntimeException Thrown if there is a failure in creating
an {@link XPathFactory} for the default object model.
- */
- public static XPathFactory newXPathFactory() {
- return XPathHardener.harden(XPathFactory.newInstance());
- }
-
- private XmlFactories() {
- // static only
- }
-}
diff --git a/src/main/java/org/apache/commons/xml/package-info.java
b/src/main/java/org/apache/commons/xml/package-info.java
index 7cb62d3..c9e3f7a 100644
--- a/src/main/java/org/apache/commons/xml/package-info.java
+++ b/src/main/java/org/apache/commons/xml/package-info.java
@@ -14,10 +14,53 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
/**
* Apache Commons XML provides secure-by-default JAXP factory creation for
Java. A single method call returns a hardened JAXP factory that can be used to
- * <em>safely</em> parse XML files. The entry point is the class {@link
org.apache.commons.xml.XmlFactories}.
+ * <em>safely</em> parse XML files.
+ * <p>
+ * Every method returns <em>new, hardened</em> factory instances. No caching
or pooling is performed; callers on a hot path are responsible for their own
+ * caching.
+ * </p>
+ * <h2>Hardening guarantees</h2>
+ * <p>
+ * Every factory returned by makes the same three guarantees, regardless of
which JAXP implementation is on the classpath:
+ * </p>
+ * <ul>
+ * <li><strong>External DTDs are not fetched.</strong></li>
+ * <li><strong>External entities are not resolved.</strong></li>
+ * <li><strong>Internal entity expansion is bounded</strong> by the platform's
secure-processing limit, so DoS payloads such as Billion Laughs are rejected
+ * before they exhaust resources.</li>
+ * </ul>
+ * <p>
+ * These guarantees are defined on OpenJDK 8 or later (and JDK distributions
built from it). No version of Android supports
+ * {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING}, so on Android
(API level 19 or later) the hardening is applied as best-effort without a
guarantee,
+ * tested as complete starting with API level 33; see the threat model's
"Assumptions about the environment".
+ * </p>
+ * <p>
+ * The guarantees hold whether or not the caller opts into DTD validation
({@link javax.xml.parsers.DocumentBuilderFactory#setValidating(boolean)
+ * setValidating(true)}) or attaches a compiled XSD via {@link
javax.xml.parsers.DocumentBuilderFactory#setSchema(javax.xml.validation.Schema)
setSchema}: every
+ * external resource the validation would otherwise fetch (the DTD itself, an
{@code xsi:schemaLocation} hint, an external entity referenced from the DTD)
+ * remains blocked.
+ * </p>
+ * <p>
+ * Each method adds factory-specific guarantees on top of the three above,
documented on the corresponding {@code newXxxFactory()} method.
+ * </p>
+ * <p>
+ * An unresolved external reference resolves to empty content by default, so
the parse continues without the resource. To reject it with an exception
instead,
+ * set the system property {@code org.apache.commons.xml.throwOnUnresolved} to
{@code true}; the property is read at resolution time, and references resolved
by
+ * a caller-supplied resolver are unaffected.
+ * </p>
+ * <h2>Caller-supplied URIs</h2>
+ * <p>
+ * A top-level URI passed directly by the caller is fetched as-is: {@code
StreamSource(systemId)}, {@code DocumentBuilder.parse(String)}, or a {@code
SAXSource}
+ * built from a system id all cause the JAXP implementation to open that URI
without consulting the hardening layer. Use a
+ * {@link javax.xml.transform.URIResolver} or {@link
org.xml.sax.EntityResolver} if you need to restrict the top-level fetch.
+ * </p>
+ * <h2>Thread safety</h2>
+ * <p>
+ * The returned factories inherit the thread-safety properties of the
underlying JAXP implementation, which in practice means they are <strong>not
guaranteed to
+ * be thread-safe</strong>. Create a new factory per thread or synchronize
externally.
+ * </p>
*/
package org.apache.commons.xml;
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 0a88d1b..42eea29 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -40,7 +40,7 @@ such as standalone Xerces, Woodstox, or Saxon's TrAX, need
further configuration
library author has no control over which implementation is on the classpath at
runtime, so the effective security
posture of their code depends on a deployment decision made elsewhere.
-This library provides that baseline. Each `XmlFactories` call returns a new
factory hardened by an
+This library provides that baseline. Each `org.apache.commons.xml` factory
call returns a new factory hardened by an
implementation-specific recipe, so the returned object behaves the same way
security-wise regardless of which JAXP
implementation resolved. Security becomes a property of the call, not of the
classpath, and there is one place to
update when a new hardening setting becomes available or a default changes.
@@ -57,7 +57,7 @@ Add the library to your build:
</dependency>
```
-Every method on `XmlFactories` returns a new, hardened factory.
+Every factory method in `org.apache.commons.xml` returns a new, hardened
factory.
Pick the one that matches the API you already use;
no other configuration is required.
On hardened factories an external resource reference (DTD, entity, schema,
stylesheet) is never fetched:
@@ -80,7 +80,7 @@ it is not a JAXP API.
### Supported implementations
Out of the box the library recognizes the stock JDK JAXP implementations,
Apache Xerces 2.x, Woodstox, and Saxon-HE. If
-a factory resolves to an implementation not covered by any bundled hardening
recipe, every `XmlFactories` method throws
+a factory resolves to an implementation not covered by any bundled hardening
recipe, every `org.apache.commons.xml` factory method throws
`IllegalStateException` with a message naming the unsupported class. Adding
support for a new JAXP implementation
requires a code change to this library.
@@ -88,26 +88,26 @@ requires a code change to this library.
```java
import org.w3c.dom.Document;
-import org.apache.commons.xml.XmlFactories;
+import org.apache.commons.xml.HardeningDocumentBuilderFactory;
-Document doc =
XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(inputStream);
+Document doc =
HardeningDocumentBuilderFactory().newInstance().parse(inputStream);
```
**SAX parsing** via `SAXParserFactory`:
```java
-import org.apache.commons.xml.XmlFactories;
+import org.apache.commons.xml.HardeningSAXParserFactory;
-XmlFactories.newSAXParserFactory().newSAXParser().parse(inputStream,
myDefaultHandler);
+HardeningSAXParserFactory().newInstance().parse(inputStream, myDefaultHandler);
```
**Streaming (StAX) parsing** via `XMLInputFactory`:
```java
import javax.xml.stream.XMLStreamReader;
-import org.apache.commons.xml.XmlFactories;
+import org.apache.commons.xml.HardeningXMLInputFactory;
-XMLStreamReader reader =
XmlFactories.newXMLInputFactory().createXMLStreamReader(inputStream);
+XMLStreamReader reader =
HardeningXMLInputFactory.newInstance().createXMLStreamReader(inputStream);
```
**XSLT transforms** via `TransformerFactory`:
@@ -115,9 +115,9 @@ XMLStreamReader reader =
XmlFactories.newXMLInputFactory().createXMLStreamReader
```java
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
-import org.apache.commons.xml.XmlFactories;
+import org.apache.commons.xml.HardeningTransformerFactory;
-XmlFactories.newTransformerFactory()
+HardeningTransformerFactory.newInstance()
.newTransformer(new StreamSource(stylesheet))
.transform(new StreamSource(inputStream), new
StreamResult(outputStream));
```
@@ -127,9 +127,9 @@ XmlFactories.newTransformerFactory()
```java
import javax.xml.xpath.XPathConstants;
import org.w3c.dom.NodeList;
-import org.apache.commons.xml.XmlFactories;
+import org.apache.commons.xml.HardeningXPathFactory;
-NodeList hits = (NodeList) XmlFactories.newXPathFactory()
+NodeList hits = (NodeList) HardeningXPathFactory.newInstance()
.newXPath()
.evaluate("//item", doc, XPathConstants.NODESET);
```
@@ -139,9 +139,9 @@ NodeList hits = (NodeList) XmlFactories.newXPathFactory()
```java
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
-import org.apache.commons.xml.XmlFactories;
+import org.apache.commons.xml.HardeningSchemaFactory;
-XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI)
+HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
.newSchema(new StreamSource(xsdStream))
.newValidator()
.validate(new StreamSource(inputStream));
@@ -152,7 +152,7 @@
XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI)
The hardening applies to documents parsed through the returned factory.
Stylesheets given to
`TransformerFactory.newTransformer(Source)` and schemas given to
`SchemaFactory.newSchema(Source)` are read by a parser
the implementation picks internally, and that parser may not be hardened
(Saxon's TrAX is one such case, see Building
-below). Treat stylesheets and schemas as trusted input, or pre-parse them
through a hardened `XmlFactories` parser and
+below). Treat stylesheets and schemas as trusted input, or pre-parse them
through a hardened `org.apache.commons.xml` parser and
pass the result as a `DOMSource` or `SAXSource`.
A stylesheet also chooses where the transform writes (`xsl:result-document`):
the hardening governs reads only,
@@ -162,7 +162,7 @@ so restrict output destinations yourself when running an
untrusted stylesheet
### Transformer handlers and filters
The `SAXTransformerFactory` extension methods, `newTransformerHandler(...)`,
`newTemplatesHandler()` and `newXMLFilter(...)`,
-if reachable by casting the factory from
`XmlFactories.newTransformerFactory()`,
+if reachable by casting the factory from
`HardeningTransformerFactory.newInstance()`,
produce handlers, filters and `Templates` carrying the same hardening as the
standard entry points:
runtime `document()` resolves to empty content,
and a filter with no caller-set parent parses its input through a hardened
reader.
@@ -172,7 +172,7 @@ See the [Threat Model](threat_model.html) for the exact
scope.
### Caching and thread-safety
-There is no caching or pooling inside `XmlFactories`; callers on a hot path
are responsible for their own caching. The
+There is no caching or pooling inside `org.apache.commons.xml`; callers on a
hot path are responsible for their own caching. The
returned factories inherit the thread-safety properties of the underlying JAXP
implementation, which in practice means
they are not thread-safe. Create a new factory per thread or synchronize
externally.
diff --git a/src/site/markdown/threat_model.md
b/src/site/markdown/threat_model.md
index 1a688c1..0a350bd 100644
--- a/src/site/markdown/threat_model.md
+++ b/src/site/markdown/threat_model.md
@@ -40,12 +40,12 @@ a finding that falls under [What is out of
scope](#what-is-out-of-scope) will be
### Scope and intended use
-This library is a helper for **safely creating JAXP factories**. Each
`XmlFactories.newXxxFactory()` method returns a
+This library is a helper for **safely creating JAXP factories**. Each
`XxxFactory.newYyy()` method returns a
new, hardened factory whose parsers reject the common XML attacks (external
entity / DTD resolution, XXE, SSRF through
external references, and entity-expansion denial of service such as Billion
Laughs). The exact guarantee each factory
makes is documented in the Javadoc:
-https://commons.apache.org/sandbox/commons-xml/apidocs/org/apache/commons/xml/factory/XmlFactories.html
+https://commons.apache.org/sandbox/commons-xml/apidocs/org/apache/commons/xml/package-summary.html
The hardening applies to the factory and to the parsers, readers,
transformers, validators, schemas and XPath objects it produces.
It governs what those objects read;
@@ -59,7 +59,7 @@ document tries to reach through an entity, DTD, schema,
stylesheet, or XInclude
exists to stop that untrusted document from reading local resources, reaching
the network, or exhausting
memory or CPU.
-The trust boundary is the factory as returned by `XmlFactories`. The XML
handed to a parser, reader,
+The trust boundary is the factory as returned by `org.apache.commons.xml`. The
XML handed to a parser, reader,
transformer, validator or schema produced by that factory is **untrusted**;
the configuration of the factory
is **trusted**, and keeping it as delivered is the caller's responsibility. A
caller running in the same
process can always reconfigure or replace the factory, so such a caller is not
an adversary this model
@@ -77,7 +77,7 @@ because your reader's settings are indistinguishable from
configuration you chos
### What is in scope
-- The hardening recipes applied by `XmlFactories`.
+- The hardening recipes applied by `org.apache.commons.xml`.
Every implementation of JAXP 1.4 or later is in scope,
as long as it respects the contract of the features, attributes, and
properties the recipes use.
An implementation that cannot accept a required setting makes the factory
method throw
@@ -85,7 +85,7 @@ because your reader's settings are indistinguishable from
configuration you chos
The recipes for Android's Expat/KXmlParser are applied as best-effort and
carry no guarantee
(see **Supported runtimes** under [Assumptions about the
environment](#assumptions-about-the-environment)).
-- A factory returned by `XmlFactories`, used as delivered, that fails to
provide a guarantee the Javadoc states it
+- A factory returned by `org.apache.commons.xml`, used as delivered, that
fails to provide a guarantee the Javadoc states it
provides. The guarantee covers the documented entry points of each returned
factory type,
including the `SAXTransformerFactory` extension methods when the returned
`TransformerFactory` exposes them.
@@ -95,7 +95,7 @@ The library does not open network connections,
spawn processes,
install signal handlers,
or read environment variables of its own:
-each `XmlFactories` method only configures and returns a JAXP factory.
+each `org.apache.commons.xml` factory method only configures and returns a
JAXP factory.
Which hardening recipe applies depends on the JAXP implementation present on
the classpath.
**Supported runtimes**
@@ -234,13 +234,13 @@ and reports against a factory reconfigured in any of the
ways below are out of s
`StreamSource(systemId)`, a `SAXSource` built from a system id) is fetched
as-is by the JAXP implementation without
consulting the hardening layer. Restrict it yourself if the URI is untrusted.
- **Caller-supplied parser instances.**
- A parser built outside `XmlFactories` and handed to a produced instance is
used as configured:
+ A parser built outside `org.apache.commons.xml` and handed to a produced
instance is used as configured:
a `SAXSource` carrying its own `XMLReader`,
a `StAXSource` carrying a stream or event reader,
or a `DOMSource` holding a document parsed elsewhere.
Its settings are yours, including permissive ones.
To parse with your own reader under the hardening guarantees,
- obtain it from `XmlFactories.newSAXParserFactory()`
+ obtain it from `HardeningSAXParserFactory.newInstance()`
before wrapping it in a `SAXSource`.
- The behavior of a JAXP implementation that does not respect the contract of
the settings a hardening recipe requires
(the factory method throws rather than returning an unhardened factory),
@@ -270,7 +270,7 @@ re-establishing any protection you remove.
XML-security scanners and static analyzers routinely flag the parsers this
library produces. The following
are **not** vulnerabilities under this model:
-- A claim that a factory or instance produced by `XmlFactories` is unsafe,
without showing that a reserved
+- A claim that a factory or instance produced by `org.apache.commons.xml` is
unsafe, without showing that a reserved
setting was loosened, a resolver was installed, or an untrusted top-level
URI was passed (see
[Assumptions about the environment](#assumptions-about-the-environment) and
[What is out of scope](#what-is-out-of-scope)). As delivered, the instance
is hardened; the bare presence
@@ -296,7 +296,7 @@ are **not** vulnerabilities under this model:
instruction of a stylesheet
(see **Transform output destinations** under [What is out of
scope](#what-is-out-of-scope)).
- Reports in a JAXP implementation that does not respect the contract of the
settings a hardening recipe
- requires: `XmlFactories` throws rather than returning an unhardened factory,
so there is no instance to attack.
+ requires: `org.apache.commons.xml` factory method throws rather than
returning an unhardened factory, so there is no instance to attack.
### Triage dispositions
@@ -314,7 +314,7 @@ A report judged against this model receives exactly one of:
### Conditions that would change this model
Revise this model when any of the following change:
-a new `XmlFactories` factory method or other public surface;
+a new `org.apache.commons.xml` factory or other public surface;
support for a JAXP implementation beyond those listed under [What is in
scope](#what-is-in-scope);
a change to the supported runtimes (see **Supported runtimes** under
[Assumptions about the environment](#assumptions-about-the-environment));
a new reserved setting;
diff --git a/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
b/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
index 3a553fd..4976ed9 100644
--- a/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
+++ b/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
@@ -50,7 +50,7 @@ private static void assertAssociatedStylesheet(final Source
associated) {
}
private static TransformerFactory hardenedFactory() {
- final TransformerFactory factory =
XmlFactories.newTransformerFactory();
+ final TransformerFactory factory =
HardeningTransformerFactory.newInstance();
factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
return factory;
}
diff --git a/src/test/java/org/apache/commons/xml/AttackTestSupport.java
b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
index a5d87b2..0e3ea63 100644
--- a/src/test/java/org/apache/commons/xml/AttackTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
@@ -61,7 +61,7 @@
* <p>The hardened-side helpers come in three flavors, distinguished by their
suffix:</p>
*
* <ul>
- * <li>{@code assert*Blocks(...)} runs the payload through a hardened
factory from {@link XmlFactories} and asserts the parse throws. Used when the
hardening
+ * <li>{@code assert*Blocks(...)} runs the payload through a hardened
factory from {@link org.apache.commons.xml} and asserts the parse throws. Used
when the hardening
* layer is expected to reject the attack outright.</li>
* <li>{@code assert*DoesNotLeak(...)} runs the payload through a hardened
factory and asserts the parse completes without throwing and without producing
the
* {@link #LEAKED_MARKER} string. Used when the hardening contract
guarantees the parse succeeds but never resolves the external resource (for
example,
@@ -207,10 +207,10 @@ public void warning(final TransformerException exception)
{
/**
* Asserts a hardened DOM parse of the payload throws.
*
- * <p>{@link DocumentBuilder#parse(InputSource)} via {@link
XmlFactories#newDocumentBuilderFactory()}; only a thrown exception passes.</p>
+ * <p>{@link DocumentBuilder#parse(InputSource)} via {@link
HardeningDocumentBuilderFactory#newInstance()}; only a thrown exception
passes.</p>
*/
static void assertDomBlocks(final String payload) {
- assertParseFails(() ->
strictDocumentBuilder(XmlFactories.newDocumentBuilderFactory()).parse(inputSource(payload)),
"DOM", SAXException.class);
+ assertParseFails(() ->
strictDocumentBuilder(HardeningDocumentBuilderFactory.newInstance()).parse(inputSource(payload)),
"DOM", SAXException.class);
}
/**
@@ -226,7 +226,7 @@ static void assertDomBlocksOrDoesNotLeak(final String
payload) {
/**
* Asserts a hardened DOM parse completes without throwing and without
leaked content.
*
- * <p>{@link DocumentBuilder#parse(InputSource)} via {@link
XmlFactories#newDocumentBuilderFactory()}; use this when the hardening
guarantee is "the parse
+ * <p>{@link DocumentBuilder#parse(InputSource)} via {@link
HardeningDocumentBuilderFactory#newInstance()}; use this when the hardening
guarantee is "the parse
* succeeds but never resolves the external resource", for example, when
the ignore-all resolver floor resolves the external subset to empty content.</p>
*/
static void assertDomDoesNotLeak(final String payload) {
@@ -236,10 +236,10 @@ static void assertDomDoesNotLeak(final String payload) {
/**
* Asserts a hardened DOM parse succeeds.
*
- * <p>{@link DocumentBuilder#parse(InputSource)} via {@link
XmlFactories#newDocumentBuilderFactory()}; positive control for DOCTYPE-only
payloads.</p>
+ * <p>{@link DocumentBuilder#parse(InputSource)} via {@link
HardeningDocumentBuilderFactory#newInstance()}; positive control for
DOCTYPE-only payloads.</p>
*/
static void assertDomParses(final String payload) {
- assertParseSucceeds(() ->
strictDocumentBuilder(XmlFactories.newDocumentBuilderFactory()).parse(inputSource(payload)),
"DOM");
+ assertParseSucceeds(() ->
strictDocumentBuilder(HardeningDocumentBuilderFactory.newInstance()).parse(inputSource(payload)),
"DOM");
}
/**
@@ -444,45 +444,45 @@ static void assertPermissiveValidatorValidates(final
String xml) {
/**
* Asserts a hardened SAX parse of the payload throws.
*
- * <p>{@link XMLReader#parse(InputSource)} on a parser from {@link
XmlFactories#newSAXParserFactory()}; only a thrown exception passes.</p>
+ * <p>{@link XMLReader#parse(InputSource)} on a parser from {@link
HardeningSAXParserFactory#newInstance()}; only a thrown exception passes.</p>
*/
static void assertSaxBlocks(final String payload) {
- assertParseFails(() ->
consumeXmlReader(strictXMLReader(XmlFactories.newSAXParserFactory()), payload),
"SAX", SAXException.class);
+ assertParseFails(() ->
consumeXmlReader(strictXMLReader(HardeningSAXParserFactory.newInstance()),
payload), "SAX", SAXException.class);
}
/**
* Asserts a hardened SAX parse either blocks at parse or completes
without leaked content. See {@link #assertDomBlocksOrDoesNotLeak(String)}.
*/
static void assertSaxBlocksOrDoesNotLeak(final String payload) {
- assertNoLeakOrThrows(() ->
captureCharacters(strictXMLReader(XmlFactories.newSAXParserFactory()),
payload), "SAX", SAXException.class);
+ assertNoLeakOrThrows(() ->
captureCharacters(strictXMLReader(HardeningSAXParserFactory.newInstance()),
payload), "SAX", SAXException.class);
}
/**
* Asserts a hardened SAX parse completes without throwing and without
leaked content.
*
- * <p>{@link XMLReader#parse(InputSource)} on a parser from {@link
XmlFactories#newSAXParserFactory()}; use this when the hardening guarantee is
"the parse
+ * <p>{@link XMLReader#parse(InputSource)} on a parser from {@link
HardeningSAXParserFactory#newInstance()}; use this when the hardening guarantee
is "the parse
* succeeds but never resolves the external resource", for example, when
the ignore-all resolver floor resolves the external subset to empty content.</p>
*/
static void assertSaxDoesNotLeak(final String payload) {
- assertNoLeakStrict(() ->
captureCharacters(strictXMLReader(XmlFactories.newSAXParserFactory()),
payload), "SAX");
+ assertNoLeakStrict(() ->
captureCharacters(strictXMLReader(HardeningSAXParserFactory.newInstance()),
payload), "SAX");
}
/**
* Asserts a hardened SAX parse succeeds.
*
- * <p>{@link XMLReader#parse(InputSource)} on a parser from {@link
XmlFactories#newSAXParserFactory()}; positive control for DOCTYPE-only
payloads.</p>
+ * <p>{@link XMLReader#parse(InputSource)} on a parser from {@link
HardeningSAXParserFactory#newInstance()}; positive control for DOCTYPE-only
payloads.</p>
*/
static void assertSaxParses(final String payload) {
- assertParseSucceeds(() ->
consumeXmlReader(strictXMLReader(XmlFactories.newSAXParserFactory()), payload),
"SAX");
+ assertParseSucceeds(() ->
consumeXmlReader(strictXMLReader(HardeningSAXParserFactory.newInstance()),
payload), "SAX");
}
/**
* Asserts a hardened Schema compilation throws.
*
- * <p>{@link SchemaFactory#newSchema(Source)} via {@link
XmlFactories#newSchemaFactory(String)}; only a thrown exception passes.</p>
+ * <p>{@link SchemaFactory#newSchema(Source)} via {@link
HardeningSchemaFactory#newInstance(String)}; only a thrown exception passes.</p>
*/
static void assertSchemaBlocks(final Source xsd) {
- assertParseFails(() ->
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd), "Schema compile", SAXException.class, SecurityException.class);
+ assertParseFails(() ->
strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd), "Schema compile", SAXException.class, SecurityException.class);
}
/**
@@ -491,7 +491,7 @@ static void assertSchemaBlocks(final Source xsd) {
*/
static void assertSchemaBlocksOrDoesNotLeak(final Source xsd) {
assertNoLeakOrThrows(() -> {
-
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd);
+
strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd);
return "";
}, "Schema compile", SAXException.class, SecurityException.class);
}
@@ -499,73 +499,73 @@ static void assertSchemaBlocksOrDoesNotLeak(final Source
xsd) {
/**
* Asserts a hardened Schema compilation succeeds.
*
- * <p>{@link SchemaFactory#newSchema(Source)} via {@link
XmlFactories#newSchemaFactory(String)}; positive control for DOCTYPE-only
payloads.</p>
+ * <p>{@link SchemaFactory#newSchema(Source)} via {@link
HardeningSchemaFactory#newInstance(String)}; positive control for DOCTYPE-only
payloads.</p>
*/
static void assertSchemaCompiles(final Source xsd) {
- assertParseSucceeds(() ->
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd), "Schema compile");
+ assertParseSucceeds(() ->
strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd), "Schema compile");
}
/**
* Asserts a hardened Schema compilation completes without throwing.
*
- * <p>{@link SchemaFactory#newSchema(Source)} via {@link
XmlFactories#newSchemaFactory(String)}; use this when the hardening contract
guarantees the compile
+ * <p>{@link SchemaFactory#newSchema(Source)} via {@link
HardeningSchemaFactory#newInstance(String)}; use this when the hardening
contract guarantees the compile
* succeeds but never resolves the external resource (for example, {@code
XERCES_LOAD_EXTERNAL_DTD=false} silently skipping the external subset, with the
body's
* undeclared entity reference dropped per XML 1.0 §4.1).</p>
*/
static void assertSchemaDoesNotLeak(final Source xsd) {
- assertParseSucceeds(() ->
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd), "Schema compile");
+ assertParseSucceeds(() ->
strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
xsd), "Schema compile");
}
/**
* Asserts a hardened StAX parse of the payload throws.
*
- * <p>{@link XMLStreamReader} and {@link XMLEventReader} from {@link
XmlFactories#newXMLInputFactory()}; both flavors are exercised and either must
+ * <p>{@link XMLStreamReader} and {@link XMLEventReader} from {@link
HardeningXMLInputFactory#newInstance()}; both flavors are exercised and either
must
* throw.</p>
*/
static void assertStaxBlocks(final String payload) {
- assertParseFails(() ->
consumeStreamReader(XmlFactories.newXMLInputFactory(), payload), "StAX stream",
XMLStreamException.class);
- assertParseFails(() ->
consumeEventReader(XmlFactories.newXMLInputFactory(), payload), "StAX event",
XMLStreamException.class);
+ assertParseFails(() ->
consumeStreamReader(HardeningXMLInputFactory.newInstance(), payload), "StAX
stream", XMLStreamException.class);
+ assertParseFails(() ->
consumeEventReader(HardeningXMLInputFactory.newInstance(), payload), "StAX
event", XMLStreamException.class);
}
/**
* Asserts a hardened StAX parse (stream and event) either blocks at parse
or completes without leaked content. See {@link
#assertDomBlocksOrDoesNotLeak(String)}.
*/
static void assertStaxBlocksOrDoesNotLeak(final String payload) {
- assertNoLeakOrThrows(() ->
captureStaxStreamText(XmlFactories.newXMLInputFactory(), payload), "StAX
stream", XMLStreamException.class);
- assertNoLeakOrThrows(() ->
captureStaxEventText(XmlFactories.newXMLInputFactory(), payload), "StAX event",
XMLStreamException.class);
+ assertNoLeakOrThrows(() ->
captureStaxStreamText(HardeningXMLInputFactory.newInstance(), payload), "StAX
stream", XMLStreamException.class);
+ assertNoLeakOrThrows(() ->
captureStaxEventText(HardeningXMLInputFactory.newInstance(), payload), "StAX
event", XMLStreamException.class);
}
/**
* Asserts a hardened StAX parse completes without throwing and without
leaked content.
*
- * <p>{@link XMLStreamReader} and {@link XMLEventReader} from {@link
XmlFactories#newXMLInputFactory()}; both flavors are exercised. Use this when
the
+ * <p>{@link XMLStreamReader} and {@link XMLEventReader} from {@link
HardeningXMLInputFactory#newInstance()}; both flavors are exercised. Use this
when the
* hardening guarantee is "the parse succeeds but never resolves the
external resource", for example, when the JDK's {@code ignore-external-dtd}
property silently
* skips the external subset.</p>
*/
static void assertStaxDoesNotLeak(final String payload) {
- assertNoLeakStrict(() ->
captureStaxStreamText(XmlFactories.newXMLInputFactory(), payload), "StAX
stream");
- assertNoLeakStrict(() ->
captureStaxEventText(XmlFactories.newXMLInputFactory(), payload), "StAX event");
+ assertNoLeakStrict(() ->
captureStaxStreamText(HardeningXMLInputFactory.newInstance(), payload), "StAX
stream");
+ assertNoLeakStrict(() ->
captureStaxEventText(HardeningXMLInputFactory.newInstance(), payload), "StAX
event");
}
/**
* Asserts a hardened StAX parse succeeds.
*
- * <p>{@link XMLStreamReader} and {@link XMLEventReader} from {@link
XmlFactories#newXMLInputFactory()}; positive control for DOCTYPE-only
payloads.</p>
+ * <p>{@link XMLStreamReader} and {@link XMLEventReader} from {@link
HardeningXMLInputFactory#newInstance()}; positive control for DOCTYPE-only
payloads.</p>
*/
static void assertStaxParses(final String payload) {
- assertParseSucceeds(() ->
consumeStreamReader(XmlFactories.newXMLInputFactory(), payload), "StAX stream");
- assertParseSucceeds(() ->
consumeEventReader(XmlFactories.newXMLInputFactory(), payload), "StAX event");
+ assertParseSucceeds(() ->
consumeStreamReader(HardeningXMLInputFactory.newInstance(), payload), "StAX
stream");
+ assertParseSucceeds(() ->
consumeEventReader(HardeningXMLInputFactory.newInstance(), payload), "StAX
event");
}
/**
* Asserts a hardened Templates compile-and-transform throws.
*
- * <p>{@link TransformerFactory#newTemplates(Source)} via {@link
XmlFactories#newTransformerFactory()} followed by transform; either step
throwing
+ * <p>{@link TransformerFactory#newTemplates(Source)} via {@link
HardeningTransformerFactory#newInstance()} followed by transform; either step
throwing
* passes.</p>
*/
static void assertTemplatesBlocks(final Source xslt) {
assertParseFails(() -> {
- final Templates templates =
strictTemplates(XmlFactories.newTransformerFactory(), xslt);
+ final Templates templates =
strictTemplates(HardeningTransformerFactory.newInstance(), xslt);
// Xalan returns `null` if the template fails
if (templates == null) {
throw new TransformerException("Transformer factory returned
null");
@@ -584,7 +584,7 @@ static void assertTemplatesBlocksOrDoesNotLeak(final Source
xslt) {
/**
* Asserts a hardened Templates compile-and-transform succeeds.
*
- * <p>{@link TransformerFactory#newTemplates(Source)} via {@link
XmlFactories#newTransformerFactory()} followed by transform; positive control
for
+ * <p>{@link TransformerFactory#newTemplates(Source)} via {@link
HardeningTransformerFactory#newInstance()} followed by transform; positive
control for
* DOCTYPE-only payloads.</p>
*/
static void assertTemplatesCompiles(final Source xslt) {
@@ -594,7 +594,7 @@ static void assertTemplatesCompiles(final Source xslt) {
/**
* Asserts a hardened Templates compile-and-transform completes without
throwing and without leaked content.
*
- * <p>{@link TransformerFactory#newTemplates(Source)} via {@link
XmlFactories#newTransformerFactory()} followed by transform; use this when the
hardening
+ * <p>{@link TransformerFactory#newTemplates(Source)} via {@link
HardeningTransformerFactory#newInstance()} followed by transform; use this when
the hardening
* contract guarantees the compile and transform succeed but never resolve
the external resource.</p>
*/
static void assertTemplatesDoesNotLeak(final Source xslt) {
@@ -604,12 +604,12 @@ static void assertTemplatesDoesNotLeak(final Source xslt)
{
/**
* Asserts a hardened identity Transformer of the payload throws.
*
- * <p>{@link Transformer#transform(Source, javax.xml.transform.Result)} on
the identity transformer from {@link XmlFactories#newTransformerFactory()}; only
+ * <p>{@link Transformer#transform(Source, javax.xml.transform.Result)} on
the identity transformer from {@link
HardeningTransformerFactory#newInstance()}; only
* a thrown exception passes.</p>
*/
static void assertTransformerBlocks(final String payload) {
assertParseFails(
- () ->
strictTransformer(XmlFactories.newTransformerFactory()).transform(streamSource(payload),
new StreamResult(new StringWriter())),
+ () ->
strictTransformer(HardeningTransformerFactory.newInstance()).transform(streamSource(payload),
new StreamResult(new StringWriter())),
"Transformer", TransformerException.class);
}
@@ -623,7 +623,7 @@ static void assertTransformerBlocksOrDoesNotLeak(final
String payload) {
/**
* Asserts a hardened identity Transformer completes without throwing and
without leaked content.
*
- * <p>{@link Transformer#transform(Source, javax.xml.transform.Result)}
via {@link XmlFactories#newTransformerFactory()}; use this when the hardening
+ * <p>{@link Transformer#transform(Source, javax.xml.transform.Result)}
via {@link HardeningTransformerFactory#newInstance()}; use this when the
hardening
* contract guarantees the transform succeeds but never resolves the
external resource.</p>
*/
static void assertTransformerDoesNotLeak(final String payload) {
@@ -633,7 +633,7 @@ static void assertTransformerDoesNotLeak(final String
payload) {
/**
* Asserts a hardened identity Transformer succeeds.
*
- * <p>{@link Transformer#transform(Source, javax.xml.transform.Result)} on
the identity transformer from {@link XmlFactories#newTransformerFactory()};
+ * <p>{@link Transformer#transform(Source, javax.xml.transform.Result)} on
the identity transformer from {@link HardeningTransformerFactory#newInstance()};
* positive control for DOCTYPE-only payloads.</p>
*/
static void assertTransformerTransforms(final String payload) {
@@ -643,12 +643,12 @@ static void assertTransformerTransforms(final String
payload) {
/**
* Asserts a hardened Validator validation throws.
*
- * <p>{@link Validator#validate(Source)} on a validator from {@link
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory(String)};
only a thrown
+ * <p>{@link Validator#validate(Source)} on a validator from {@link
#BENIGN_SCHEMA} compiled via {@link
HardeningSchemaFactory#newInstance(String)}; only a thrown
* exception passes (the schema is benign; the attack lives in the
instance document).</p>
*/
static void assertValidatorBlocks(final String xml) {
assertParseFails(
- () ->
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
+ () ->
strictValidator(strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
"Validator", SAXException.class, SecurityException.class);
}
@@ -659,7 +659,7 @@ static void assertValidatorBlocks(final String xml) {
*/
static void assertValidatorBlocksOrDoesNotLeak(final String xml) {
assertNoLeakOrThrows(() -> {
-
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml));
+
strictValidator(strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml));
return "";
}, "Validator", SAXException.class, SecurityException.class,
IOException.class);
}
@@ -667,24 +667,24 @@ static void assertValidatorBlocksOrDoesNotLeak(final
String xml) {
/**
* Asserts a hardened Validator validation completes without throwing.
*
- * <p>{@link Validator#validate(Source)} on a validator from {@link
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory(String)}; use
this when the
+ * <p>{@link Validator#validate(Source)} on a validator from {@link
#BENIGN_SCHEMA} compiled via {@link
HardeningSchemaFactory#newInstance(String)}; use this when the
* hardening contract guarantees the validate succeeds but never resolves
the external resource.</p>
*/
static void assertValidatorDoesNotLeak(final String xml) {
assertParseSucceeds(
- () ->
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
+ () ->
strictValidator(strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
"Validator");
}
/**
* Asserts a hardened Validator validation succeeds.
*
- * <p>{@link Validator#validate(Source)} on a validator from {@link
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory(String)};
positive control
+ * <p>{@link Validator#validate(Source)} on a validator from {@link
#BENIGN_SCHEMA} compiled via {@link
HardeningSchemaFactory#newInstance(String)}; positive control
* for DOCTYPE-only payloads.</p>
*/
static void assertValidatorValidates(final String xml) {
assertParseSucceeds(
- () ->
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
+ () ->
strictValidator(strictSchema(HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
"Validator");
}
@@ -834,7 +834,7 @@ private static void consumeXmlReader(final XMLReader
reader, final String payloa
}
private static String domParseAndCaptureText(final String payload) throws
Exception {
- final Document doc =
strictDocumentBuilder(XmlFactories.newDocumentBuilderFactory()).parse(inputSource(payload));
+ final Document doc =
strictDocumentBuilder(HardeningDocumentBuilderFactory.newInstance()).parse(inputSource(payload));
if (doc.getDocumentElement() == null) {
return "";
}
@@ -845,7 +845,7 @@ private static String domParseAndCaptureText(final String
payload) throws Except
private static String identityTransformAndCapture(final String payload)
throws TransformerException {
final StringWriter sink = new StringWriter();
-
strictTransformer(XmlFactories.newTransformerFactory()).transform(streamSource(payload),
new StreamResult(sink));
+
strictTransformer(HardeningTransformerFactory.newInstance()).transform(streamSource(payload),
new StreamResult(sink));
return sink.toString();
}
@@ -1042,7 +1042,7 @@ private static void suppressException(final Executable
action) {
private static String templatesCompileAndTransform(final Source xslt)
throws TransformerException {
final StringWriter sink = new StringWriter();
- final Templates templates =
strictTemplates(XmlFactories.newTransformerFactory(), xslt);
+ final Templates templates =
strictTemplates(HardeningTransformerFactory.newInstance(), xslt);
// Xalan returns `null` if the template fails
if (templates != null) {
strictTransformer(templates).transform(streamSource("<root/>"),
new StreamResult(sink));
diff --git a/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
b/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
index 975ab47..1e91add 100644
--- a/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
+++ b/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
@@ -29,7 +29,7 @@
import org.xml.sax.SAXException;
/**
- * Checks that the resolver floors reject unresolved references when the
{@value XmlFactories#THROW_ON_UNRESOLVED} system property is set.
+ * Checks that the resolver floors reject unresolved references when the
{@value HardeningException#THROW_ON_UNRESOLVED} system property is set.
*
* <p>The floors are exercised directly: with the property set and no caller
delegate, each must throw its hook's exception instead of resolving to empty
* content. The property is read at resolution time, so setting it around a
single test cannot leak into the rest of the suite.</p>
@@ -40,12 +40,12 @@ class DenyUnresolvedTest {
@AfterEach
void clearThrowOnUnresolved() {
- System.clearProperty(XmlFactories.THROW_ON_UNRESOLVED);
+ System.clearProperty(HardeningException.THROW_ON_UNRESOLVED);
}
@BeforeEach
void enableThrowOnUnresolved() {
- System.setProperty(XmlFactories.THROW_ON_UNRESOLVED, "true");
+ System.setProperty(HardeningException.THROW_ON_UNRESOLVED, "true");
}
@Test
diff --git a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
index e8fe90f..22a943a 100644
--- a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
+++ b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
@@ -119,20 +119,20 @@ private static String entityPayload(final String
entitySystemId) {
}
private static XMLInputFactory externalEntityStaxFactory() {
- final XMLInputFactory factory = XmlFactories.newXMLInputFactory();
+ final XMLInputFactory factory = HardeningXMLInputFactory.newInstance();
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();
+ final DocumentBuilder builder =
HardeningDocumentBuilderFactory.newInstance().newDocumentBuilder();
builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
return builder;
}
private static XMLReader hardenedReader() throws Exception {
- final XMLReader reader =
XmlFactories.newSAXParserFactory().newSAXParser().getXMLReader();
+ final XMLReader reader =
HardeningSAXParserFactory.newInstance().newSAXParser().getXMLReader();
reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
return reader;
}
@@ -144,7 +144,7 @@ private static XMLReader hardenedReader() throws Exception {
* implementation cannot quietly recover from a floor resolution while the
test asserts clean completion.
*/
private static TransformerFactory hardenedTransformerFactory() {
- final TransformerFactory factory =
XmlFactories.newTransformerFactory();
+ final TransformerFactory factory =
HardeningTransformerFactory.newInstance();
factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
return factory;
}
@@ -168,7 +168,7 @@ private static LSInput lsInput(final String systemId) {
}
private static DocumentBuilder xIncludeAwareBuilder() throws Exception {
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
AttackTestSupport.assumeDoesNotThrow(() ->
factory.setXIncludeAware(true));
final DocumentBuilder builder = factory.newDocumentBuilder();
@@ -177,7 +177,7 @@ private static DocumentBuilder xIncludeAwareBuilder()
throws Exception {
}
private static XMLReader xIncludeAwareReader() throws Exception {
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
AttackTestSupport.assumeDoesNotThrow(() ->
factory.setXIncludeAware(true));
final XMLReader reader = factory.newSAXParser().getXMLReader();
@@ -227,7 +227,7 @@ void domResolvesRelativeXIncludeSibling() throws Exception {
void saxParseWithHandlerDoesNotBypass() throws Exception {
// SAXParser.parse(source, handler) installs the handler as the
reader's entity resolver; the handler does not resolve it (returns null), so the
// ignore-all floor must still resolve the external entity to empty
rather than letting the parser fetch it.
- final SAXParser parser =
XmlFactories.newSAXParserFactory().newSAXParser();
+ final SAXParser parser =
HardeningSAXParserFactory.newInstance().newSAXParser();
final StringBuilder text = new StringBuilder();
try {
parser.parse(AttackTestSupport.inputSource(entityPayload(ALLOWED)),
AttackTestSupport.capturingHandler(text));
@@ -276,7 +276,7 @@ void saxResolvesRelativeXIncludeSibling() throws Exception {
@Tag("schema")
void schemaDeniesUnlisted() {
assertParseFails(() -> {
- final SchemaFactory factory =
XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ final SchemaFactory factory =
HardeningSchemaFactory.newInstance(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);
@@ -287,7 +287,7 @@ void schemaDeniesUnlisted() {
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);
+ final SchemaFactory factory =
HardeningSchemaFactory.newInstance(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"));
@@ -299,7 +299,7 @@ void schemaFetchesIdentifierOnlyOptIn() {
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);
+ final SchemaFactory factory =
HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setResourceResolver(SCHEMA_ALLOW_LIST);
factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
}, "Schema import via caller resolver");
@@ -336,7 +336,7 @@ void staxDoesNotLeakUnlisted() throws Exception {
@Test
@Tag("stax")
void staxGetXMLResolverReportsCallerUnwrapped() {
- final XMLInputFactory factory = XmlFactories.newXMLInputFactory();
+ final XMLInputFactory factory = HardeningXMLInputFactory.newInstance();
final XMLResolver caller = (publicID, systemID, baseURI, namespace) ->
null;
factory.setXMLResolver(caller);
assertSame(caller, factory.getXMLResolver(), "getXMLResolver should
report the caller's resolver, not the floor wrapper");
diff --git a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
b/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java
similarity index 63%
rename from src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
rename to src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java
index e61d64b..0ca7aa7 100644
--- a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
+++ b/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java
@@ -38,41 +38,40 @@
import org.xml.sax.InputSource;
/**
- * Public-API smoke tests for {@link XmlFactories}.
- *
- * <p>Attack tests live in the {@code attacks} sub-package; this file only
verifies that new factories are returned, that they report safe defaults, and
that
- * a benign document still parses successfully.</p>
+ * Public-API smoke tests for {@link org.apache.commons.xml}.
+ * <p>
+ * Attack tests live in the {@code attacks} sub-package; this file only
verifies that new factories are returned, that they report safe defaults, and
that a
+ * benign document still parses successfully.
+ * </p>
*/
-class XmlFactoriesTest {
+class HardeningFactoriesSmokeTest {
- private static final String BENIGN_XML =
- "<?xml version=\"1.0\"?>\n<root><child>hello</child></root>\n";
+ private static final String BENIGN_XML = "<?xml
version=\"1.0\"?>\n<root><child>hello</child></root>\n";
@Test
void benignDocumentParses() throws Exception {
- final Document doc =
XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(new
InputSource(new StringReader(BENIGN_XML)));
+ final Document doc =
HardeningDocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new
InputSource(new StringReader(BENIGN_XML)));
assertNotNull(doc);
assertNotNull(doc.getDocumentElement());
}
@Test
void newDocumentBuilderFactoryDisablesXIncludeAndValidation() {
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
assertFalse(factory.isXIncludeAware(), "XInclude must be off by
default");
assertFalse(factory.isValidating(), "Validation must be off by
default");
}
@Test
void newDocumentBuilderFactoryEnablesSecureProcessing() throws Exception {
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
- assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING),
- "FEATURE_SECURE_PROCESSING must be on");
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
+ assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING),
"FEATURE_SECURE_PROCESSING must be on");
}
@Test
void newDocumentBuilderFactoryReturnsFreshInstance() {
- final DocumentBuilderFactory a =
XmlFactories.newDocumentBuilderFactory();
- final DocumentBuilderFactory b =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory a =
HardeningDocumentBuilderFactory.newInstance();
+ final DocumentBuilderFactory b =
HardeningDocumentBuilderFactory.newInstance();
assertNotNull(a);
assertNotNull(b);
assertNotSame(a, b);
@@ -80,8 +79,8 @@ void newDocumentBuilderFactoryReturnsFreshInstance() {
@Test
void newSAXParserFactoryReturnsFreshInstance() {
- final SAXParserFactory a = XmlFactories.newSAXParserFactory();
- final SAXParserFactory b = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory a = HardeningSAXParserFactory.newInstance();
+ final SAXParserFactory b = HardeningSAXParserFactory.newInstance();
assertNotSame(a, b);
assertFalse(a.isValidating());
assertFalse(a.isXIncludeAware());
@@ -89,23 +88,23 @@ void newSAXParserFactoryReturnsFreshInstance() {
@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);
+ final SchemaFactory a =
HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ final SchemaFactory b =
HardeningSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
assertNotSame(a, b);
assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
}
@Test
void newTransformerFactoryReturnsFreshInstance() {
- final TransformerFactory a = XmlFactories.newTransformerFactory();
- final TransformerFactory b = XmlFactories.newTransformerFactory();
+ final TransformerFactory a = HardeningTransformerFactory.newInstance();
+ final TransformerFactory b = HardeningTransformerFactory.newInstance();
assertNotSame(a, b);
}
@Test
void newXMLInputFactoryReturnsFreshInstance() {
- final XMLInputFactory a = XmlFactories.newXMLInputFactory();
- final XMLInputFactory b = XmlFactories.newXMLInputFactory();
+ final XMLInputFactory a = HardeningXMLInputFactory.newInstance();
+ final XMLInputFactory b = HardeningXMLInputFactory.newInstance();
assertNotSame(a, b);
assertEquals(Boolean.TRUE, a.getProperty(XMLInputFactory.SUPPORT_DTD));
assertEquals(Boolean.FALSE,
a.getProperty(XMLInputFactory.IS_VALIDATING));
@@ -113,10 +112,9 @@ void newXMLInputFactoryReturnsFreshInstance() {
@Test
void newXPathFactoryReturnsFreshInstance() throws Exception {
- final XPathFactory a = XmlFactories.newXPathFactory();
- final XPathFactory b = XmlFactories.newXPathFactory();
+ final XPathFactory a = HardeningXPathFactory.newXPathFactory();
+ final XPathFactory b = HardeningXPathFactory.newXPathFactory();
assertNotSame(a, b);
assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
}
-
}
diff --git a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
index 687df35..6c8cd03 100644
--- a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
+++ b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
@@ -59,7 +59,7 @@ private static String entityPayload(final String
entitySystemId) {
@Tag("dom")
void documentBuilderResetKeepsEntityResolverFloor() throws Exception {
Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES,
"platform DOM does not resolve user-defined entities");
- final DocumentBuilder builder =
XmlFactories.newDocumentBuilderFactory().newDocumentBuilder();
+ final DocumentBuilder builder =
HardeningDocumentBuilderFactory.newInstance().newDocumentBuilder();
AttackTestSupport.assumeDoesNotThrow(builder::reset);
try {
final Document doc =
builder.parse(AttackTestSupport.inputSource(entityPayload(UNLISTED)));
@@ -72,7 +72,7 @@ void documentBuilderResetKeepsEntityResolverFloor() throws
Exception {
@Test
@Tag("sax")
void saxParserResetKeepsEntityResolverFloor() throws Exception {
- final SAXParser parser =
XmlFactories.newSAXParserFactory().newSAXParser();
+ final SAXParser parser =
HardeningSAXParserFactory.newInstance().newSAXParser();
// Materialize the hardened reader before the reset, so a stale cached
wrapper would be observable.
parser.getXMLReader();
AttackTestSupport.assumeDoesNotThrow(parser::reset);
@@ -90,7 +90,7 @@ void saxParserResetKeepsEntityResolverFloor() throws
Exception {
@Tag("trax")
void transformerResetKeepsUriResolverFloor() throws Exception {
// with-document.xsl copies document('referenced.xml') into the output
at transform time, so a transformer whose floor was stripped leaks the marker.
- final Transformer transformer = XmlFactories.newTransformerFactory()
+ final Transformer transformer =
HardeningTransformerFactory.newInstance()
.newTemplates(AttackTestSupport.resourceSource("with-document.xsl")).newTransformer();
AttackTestSupport.assumeDoesNotThrow(transformer::reset);
final StringWriter sink = new StringWriter();
@@ -107,7 +107,7 @@ void transformerResetKeepsUriResolverFloor() throws
Exception {
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();
+ final Validator validator =
HardeningSchemaFactory.newInstance(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
diff --git a/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
b/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
index 3a52ec9..2f16864 100644
--- a/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
@@ -39,7 +39,7 @@ 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();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
final XMLReader reader = factory.newSAXParser().getXMLReader();
reader.setContentHandler(handler);
@@ -49,7 +49,7 @@ static void feed(final ContentHandler handler, final
InputSource input) throws E
/** The hardened factory, as its runtime {@link SAXTransformerFactory}
type. */
static SAXTransformerFactory hardenedFactory() {
- return (SAXTransformerFactory) XmlFactories.newTransformerFactory();
+ return (SAXTransformerFactory)
HardeningTransformerFactory.newInstance();
}
/** Opens a fixture under {@code leaked/} as an {@link InputSource}
preserving its system id, so relative hrefs resolve normally. */
diff --git
a/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java
b/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java
index 80b50e2..584c703 100644
--- a/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java
@@ -23,7 +23,7 @@
import org.junit.jupiter.api.Test;
/**
- * Checks that {@link XmlFactories#newSchemaFactory(String)} passes the schema
language through to {@link javax.xml.validation.SchemaFactory#newInstance}.
+ * Checks that {@link HardeningSchemaFactory#newInstance(String)} passes the
schema language through to {@link
javax.xml.validation.SchemaFactory#newInstance}.
*
* <p>The working W3C XML Schema path is exercised by the whole schema suite;
this test covers only the language-selection contract.</p>
*/
@@ -32,7 +32,7 @@ class SchemaFactoryLanguageTest {
@Test
void unknownSchemaLanguageThrows() {
- assertThrows(IllegalArgumentException.class, () ->
XmlFactories.newSchemaFactory("urn:example:unknown-schema-language"),
+ assertThrows(IllegalArgumentException.class, () ->
HardeningSchemaFactory.newInstance("urn:example:unknown-schema-language"),
"an unsupported schema language should surface
SchemaFactory.newInstance's IllegalArgumentException");
}
}
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
b/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
index 7cd4c3b..d4700b6 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
@@ -87,7 +87,7 @@ private static boolean supportsSchemaLanguage() {
@Test
void hardenedDoesNotFetchExternalSchema() {
assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2
schema-language XSD validation");
- final DocumentBuilderFactory factory =
enableXsdValidation(XmlFactories.newDocumentBuilderFactory());
+ final DocumentBuilderFactory factory =
enableXsdValidation(HardeningDocumentBuilderFactory.newInstance());
// 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 {
diff --git
a/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
b/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
index 127c920..15c9640 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
@@ -82,7 +82,7 @@ private static <T> T configureOrSkip(final
ThrowingSupplier<T> setup) {
private static DocumentBuilder hardenedValidatingDom(final String
property, final String value) {
return configureOrSkip(() -> {
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
factory.setAttribute(SCHEMA_LANGUAGE,
XMLConstants.W3C_XML_SCHEMA_NS_URI);
@@ -93,7 +93,7 @@ private static DocumentBuilder hardenedValidatingDom(final
String property, fina
private static XMLReader hardenedValidatingSax(final String property,
final String value) {
return configureOrSkip(() -> {
- final SAXParserFactory factory =
XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
final SAXParser parser = factory.newSAXParser();
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
b/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
index 7d30e35..0e02342 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
@@ -100,7 +100,7 @@ private static boolean supportsSchemaLanguage() {
@Test
void hardenedDoesNotFetchExternalSchema() throws Exception {
assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2
schema-language XSD validation");
- final SAXParser parser =
newValidatingParser(XmlFactories.newSAXParserFactory());
+ final SAXParser parser =
newValidatingParser(HardeningSAXParserFactory.newInstance());
// 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();
diff --git a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
index 44af316..2a7342c 100644
--- a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
+++ b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
@@ -44,7 +44,7 @@
* <p>Using {@code jdependency}, the same library {@code maven-shade-plugin}'s
{@code minimizeJar} uses, this test computes each entry point's transitive class
* closure over the compiled {@code target/classes} and pins it to an expected
set. It keeps each hardener from silently regaining a dependency on classes it
* should not need (for example a sibling resolver floor or another hardener),
so schema builds only on the shared SAX path, TrAX and XPath additionally on the
- * DOM path their Xalan getAssociatedStylesheet and InputSource rewrites parse
through, while only the public {@link XmlFactories} entry pulls the whole
+ * DOM path their Xalan getAssociatedStylesheet and InputSource rewrites parse
through, while only the public {@link org.apache.commons.xml} entry pulls the
whole
* library. Update the expected sets deliberately: a change here is a change
to what a downstream shade includes.</p>
*
* <p>The test reads the compiled {@code .class} files from the code-source
location, which only exists on a regular JVM: a native image carries no
bytecode (and
@@ -87,15 +87,15 @@ class ShadingFootprintTest {
"HardeningValidatorHandler", "HardeningSchema",
"FallbackIgnoreLSResourceResolver");
/**
- * Only the public {@link XmlFactories} entry, which references every
hardener, still pulls the whole library; this is its class count.
+ * Only the public {@link org.apache.commons.xml} entry, which references
every hardener, still pulls the whole library; this is its class count.
*/
- private static final int LIBRARY_CLASS_COUNT = 35;
+ private static final int LIBRARY_CLASS_COUNT = 5;
/**
* Entry points reported by the {@link #reportFootprint()} diagnostic,
most-focused first, ending with the whole library.
*/
private static final String[] REPORTED = {"DocumentBuilderHardener",
"SAXParserHardener", "StaxHardener", "TransformerHardener", "XPathHardener",
- "SchemaHardener", "XmlFactories"};
+ "SchemaHardener"};
private static Clazzpath clazzpath;
private static Path classesDir;
@@ -146,7 +146,7 @@ static void indexCompiledClasses() throws Exception {
*/
@AfterAll
static void reportFootprint() {
- final long library = bytesOf(closureOf("XmlFactories"));
+ final long library = bytesOf(rootClosure());
final StringBuilder report = new StringBuilder("\nShade footprint
(uncompressed .class bytes, % of full library):\n");
for (final String entry : REPORTED) {
final Set<String> closure = closureOf(entry);
@@ -158,6 +158,10 @@ static void reportFootprint() {
}
}
+ private static Set<String> rootClosure() {
+ return closureOf("HardeningDocumentBuilderFactory");
+ }
+
/**
* {@link #SAX_PARSER_HARDENER} plus the extra names; used where an entry
point's closure is the SAX path plus its own classes.
*/
@@ -182,7 +186,7 @@ void documentBuilderHardenerFootprint() {
@Test
void onlyXmlFactoriesPullsTheWholeLibrary() {
- assertEquals(LIBRARY_CLASS_COUNT, closureOf("XmlFactories").size(),
"XmlFactories closure size drifted");
+ assertEquals(LIBRARY_CLASS_COUNT, rootClosure().size(), "XML factories
closure size drifted");
}
@Test
diff --git a/src/test/java/org/apache/commons/xml/XIncludeTest.java
b/src/test/java/org/apache/commons/xml/XIncludeTest.java
index 06ea446..3dbc4ce 100644
--- a/src/test/java/org/apache/commons/xml/XIncludeTest.java
+++ b/src/test/java/org/apache/commons/xml/XIncludeTest.java
@@ -43,7 +43,7 @@
import org.xml.sax.XMLReader;
/**
- * Tests that XInclude resolution is blocked by default on factories from
{@link XmlFactories}, and that callers can
+ * Tests that XInclude resolution is blocked by default on factories from
{@link org.apache.commons.xml}, and that callers can
* allow-list specific resources via an {@link EntityResolver}.
*
* <p>Each case is exercised in both {@code parse="xml"} and {@code
parse="text"} modes, and for both DOM and SAX
@@ -187,7 +187,7 @@ void baselineSaxLeaksParseXml() throws Exception {
void hardenedDomBlocksParseText() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT,
"text"));
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final Document doc = factory.newDocumentBuilder().parse(input);
@@ -201,7 +201,7 @@ void hardenedDomBlocksParseText() throws Exception {
void hardenedDomBlocksParseXml() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML,
"xml"));
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
assertThrows(SAXException.class, () -> {
@@ -215,7 +215,7 @@ void hardenedDomBlocksParseXml() throws Exception {
void hardenedDomNullResolverDoesNotLeak() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML,
"xml"));
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final DocumentBuilder builder = factory.newDocumentBuilder();
@@ -229,7 +229,7 @@ void hardenedDomNullResolverDoesNotLeak() throws Exception {
void hardenedDomWithAllowListResolvesParseText() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT,
"text"));
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final DocumentBuilder builder = factory.newDocumentBuilder();
@@ -248,7 +248,7 @@ void hardenedDomWithAllowListResolvesParseText() throws
Exception {
void hardenedDomWithAllowListResolvesParseXml() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML,
"xml"));
- final DocumentBuilderFactory factory =
XmlFactories.newDocumentBuilderFactory();
+ final DocumentBuilderFactory factory =
HardeningDocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final DocumentBuilder builder = factory.newDocumentBuilder();
@@ -263,7 +263,7 @@ void hardenedDomWithAllowListResolvesParseXml() throws
Exception {
void hardenedSaxBlocksParseText() throws Exception {
final String input = xiIncludeXml(REFERENCED_TEXT, "text");
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final String captured =
captureCharacters(factory.newSAXParser().getXMLReader(), input);
@@ -276,7 +276,7 @@ void hardenedSaxBlocksParseText() throws Exception {
void hardenedSaxBlocksParseXml() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML,
"xml"));
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
assertThrows(SAXException.class, () -> {
@@ -290,7 +290,7 @@ void hardenedSaxBlocksParseXml() throws Exception {
void hardenedSaxNullResolverDoesNotLeak() throws Exception {
final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML,
"xml"));
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final XMLReader reader = factory.newSAXParser().getXMLReader();
@@ -304,7 +304,7 @@ void hardenedSaxNullResolverDoesNotLeak() throws Exception {
void hardenedSaxWithAllowListResolvesParseText() throws Exception {
final String input = xiIncludeXml(REFERENCED_TEXT, "text");
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final XMLReader reader = factory.newSAXParser().getXMLReader();
@@ -319,7 +319,7 @@ void hardenedSaxWithAllowListResolvesParseText() throws
Exception {
void hardenedSaxWithAllowListResolvesParseXml() throws Exception {
final String input = xiIncludeXml(REFERENCED_XML, "xml");
- final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+ final SAXParserFactory factory =
HardeningSAXParserFactory.newInstance();
factory.setNamespaceAware(true);
assumeXIncludeAware(factory);
final XMLReader reader = factory.newSAXParser().getXMLReader();
diff --git a/src/test/java/org/apache/commons/xml/XMLFilterParseStringTest.java
b/src/test/java/org/apache/commons/xml/XMLFilterParseStringTest.java
index e7a2a98..8b0b98b 100644
--- a/src/test/java/org/apache/commons/xml/XMLFilterParseStringTest.java
+++ b/src/test/java/org/apache/commons/xml/XMLFilterParseStringTest.java
@@ -53,7 +53,7 @@ private static String entityPayload() {
@Test
void hardenedFilterParseStringDoesNotLeakExternalEntity(@TempDir final
Path tmpDir) throws Exception {
- final SAXTransformerFactory factory = (SAXTransformerFactory)
XmlFactories.newTransformerFactory();
+ final SAXTransformerFactory factory = (SAXTransformerFactory)
HardeningTransformerFactory.newInstance();
final Templates templates = factory.newTemplates(new StreamSource(new
StringReader(IDENTITY_XSLT)));
final XMLFilter filter = factory.newXMLFilter(templates);
final Path tmp = Files.createTempFile(tmpDir, "xmlfilter", ".xml");
diff --git a/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
b/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
index e28acd3..0b7a809 100644
--- a/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
+++ b/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
@@ -52,14 +52,14 @@ private static String entityPayload() {
void hardenedXPathEvaluateDoesNotLeak() throws Exception {
// Deterministic on every engine: the entity is declared in the
internal subset and the floor resolves only its
// external content — to empty replacement text — so the pre-parse
completes and the reference expands to nothing.
- final String result =
XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION,
AttackTestSupport.inputSource(entityPayload()));
+ final String result =
HardeningXPathFactory.newXPathFactory().newXPath().evaluate(EXPRESSION,
AttackTestSupport.inputSource(entityPayload()));
assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER),
"external entity leaked into the XPath result: " + result);
}
@Test
void hardenedXPathEvaluatesPlainDocument() throws Exception {
// Positive control: the hardened pre-parse still evaluates an
entity-free document end to end.
- final String result =
XmlFactories.newXPathFactory().newXPath().evaluate(EXPRESSION,
+ final String result =
HardeningXPathFactory.newXPathFactory().newXPath().evaluate(EXPRESSION,
AttackTestSupport.inputSource(AttackTestSupport.xmlBody("plain
text")));
assertEquals("plain text", result, "hardened XPath should evaluate a
plain document");
}
@@ -67,7 +67,7 @@ void hardenedXPathEvaluatesPlainDocument() throws Exception {
@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()));
+ final String result =
HardeningXPathFactory.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER),
"external entity leaked into the compiled XPath result: " + result);
}