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 2a67b2f  Remove the harden(XMLReader) and harden(Source) methods from 
the public API (#37)
2a67b2f is described below

commit 2a67b2f1fdb66f4980273f3197e5cfa62e76e407
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Tue Aug 18 17:56:25 2026 +0200

    Remove the harden(XMLReader) and harden(Source) methods from the public API 
(#37)
    
    Keep the public surface of XmlFactories minimal for the first release:
    callers who need a hardened XMLReader can obtain one from a hardened SAX
    factory, and rewriting a Source is an edge case that can live in another
    library. Both methods were one-line delegations to the package-private
    SAXParserHardener workhorses, which every internal caller already uses
    directly.
    
    Javadoc references now point at SAXParserHardener#hardenSource and
    SAXParserHardener#hardenReader, and the tests that exercised the
    hardened-in-place reader scenario (still relied on by SaxonProvider)
    call the package-private hardenReader directly.
    
    Assisted-By: Claude Fable 5 <[email protected]>
---
 .../org/apache/commons/xml/HardeningSchema.java    |  2 +-
 .../apache/commons/xml/HardeningSchemaFactory.java |  4 +--
 .../apache/commons/xml/HardeningTransformer.java   |  2 +-
 .../commons/xml/HardeningTransformerFactory.java   |  4 +--
 .../org/apache/commons/xml/HardeningValidator.java |  2 +-
 .../org/apache/commons/xml/SAXParserHardener.java  |  2 +-
 .../java/org/apache/commons/xml/SaxonProvider.java |  2 +-
 .../java/org/apache/commons/xml/XmlFactories.java  | 29 ----------------------
 .../org/apache/commons/xml/AttackTestSupport.java  | 10 ++++----
 .../java/org/apache/commons/xml/XIncludeTest.java  | 14 +++++------
 10 files changed, 21 insertions(+), 50 deletions(-)

diff --git a/src/main/java/org/apache/commons/xml/HardeningSchema.java 
b/src/main/java/org/apache/commons/xml/HardeningSchema.java
index 87244f9..f3294c1 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSchema.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSchema.java
@@ -23,7 +23,7 @@
 
 /**
  * {@link Schema} wrapper that hardens every {@link Validator} and {@link 
ValidatorHandler} the inner Schema produces: each {@link Validator} is wrapped 
in
- * {@link HardeningValidator} (which rewrites the Source through {@link 
XmlFactories#harden(javax.xml.transform.Source)} and installs the resolver 
floor), and
+ * {@link HardeningValidator} (which rewrites the Source through {@link 
SAXParserHardener#hardenSource(javax.xml.transform.Source)} and installs the 
resolver floor), and
  * each {@link ValidatorHandler} is wrapped in a {@link 
HardeningValidatorHandler} that keeps the same ignore-all resolver floor so 
{@code xsi:schemaLocation} is
  * not resolved during SAX-driven validation.
  */
diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java 
b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
index ac000c2..a21ee58 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
@@ -38,13 +38,13 @@
  * <ol>
  *   <li>{@link HardeningSchemaFactory} installs an ignore-all {@link 
FallbackIgnoreLSResourceResolver} floor on the factory (blocking
  *       {@code xs:import}/{@code xs:include}/{@code xs:redefine} at compile 
time) and rewrites the Source on every {@code newSchema(Source[])} entry point
- *       through {@link XmlFactories#harden(Source)}.</li>
+ *       through {@link SAXParserHardener#hardenSource(Source)}.</li>
  *   <li>{@link HardeningSchema} wraps every Validator/ValidatorHandler the 
inner Schema produces and re-installs the floor on each (blocking
  *       {@code xsi:schemaLocation} at validation time), since neither the JDK 
nor Xerces reliably propagates it through {@code Schema}.</li>
  *   <li>{@link HardeningValidator} rewrites the Source on every {@link 
Validator#validate(Source)} call.</li>
  * </ol>
  *
- * <p>The hardened reader supplied by {@link XmlFactories#harden(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
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformer.java 
b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
index 6ab4f97..48587b5 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformer.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
@@ -29,7 +29,7 @@
 
 /**
  * {@link Transformer} wrapper that rewrites the Source on every {@link 
Transformer#transform(Source, Result)} call through
- * {@link XmlFactories#harden(Source)} before delegating, and keeps an 
ignore-all {@link URIResolver} floor so runtime {@code document()} calls a 
caller does not
+ * {@link SAXParserHardener#hardenSource(Source)} before delegating, and keeps 
an ignore-all {@link URIResolver} floor so runtime {@code document()} calls a 
caller does not
  * resolve return empty rather than being fetched.
  *
  * <p>The floor is installed on the delegate transformer at construction, 
seeded with the factory's compile-time resolver; {@link 
#setURIResolver(URIResolver)}
diff --git 
a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java 
b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
index ed8ac0f..8c7ebfa 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
@@ -32,7 +32,7 @@
 import org.xml.sax.XMLReader;
 
 /**
- * {@link javax.xml.transform.TransformerFactory} wrapper that rewrites every 
Source-taking entry point through {@link XmlFactories#harden(Source)} before
+ * {@link javax.xml.transform.TransformerFactory} wrapper that rewrites every 
Source-taking entry point through {@link 
SAXParserHardener#hardenSource(Source)} before
  * delegating.
  *
  * <p>Used by providers whose underlying TrAX implementation pulls a fresh 
{@code SAXParserFactory.newInstance()} for any Source that is not already a
@@ -50,7 +50,7 @@
  * <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()} or {@link 
XmlFactories#harden(XMLReader)}) in that case.</li>
+ *       {@link XmlFactories#newSAXParserFactory()}) in that case.</li>
  *   <li>{@link TransformerHandler} returned from {@code 
newTransformerHandler} is not wrapped: it processes incoming SAX events instead 
of reading a Source, so
  *       it has no inner Source-parsing path. A caller who pulls the inner 
{@link Transformer} via {@link TransformerHandler#getTransformer()} bypasses the
  *       runtime source rewrite.</li>
diff --git a/src/main/java/org/apache/commons/xml/HardeningValidator.java 
b/src/main/java/org/apache/commons/xml/HardeningValidator.java
index e67f1e0..730230f 100644
--- a/src/main/java/org/apache/commons/xml/HardeningValidator.java
+++ b/src/main/java/org/apache/commons/xml/HardeningValidator.java
@@ -32,7 +32,7 @@
 
 /**
  * {@link Validator} wrapper that rewrites the Source on every {@link 
Validator#validate(Source)} and {@link Validator#validate(Source, Result)} call 
through
- * {@link XmlFactories#harden(Source)} before delegating, and keeps an 
ignore-all {@link LSResourceResolver} floor so {@code xsi:schemaLocation} is 
not resolved at
+ * {@link SAXParserHardener#hardenSource(Source)} before delegating, and keeps 
an ignore-all {@link LSResourceResolver} floor so {@code xsi:schemaLocation} is 
not resolved at
  * validation time. {@link #reset()} re-establishes the bare ignore-all floor, 
matching the just-constructed state.
  */
 final class HardeningValidator extends Validator {
diff --git a/src/main/java/org/apache/commons/xml/SAXParserHardener.java 
b/src/main/java/org/apache/commons/xml/SAXParserHardener.java
index 4cd784f..5a309c2 100644
--- a/src/main/java/org/apache/commons/xml/SAXParserHardener.java
+++ b/src/main/java/org/apache/commons/xml/SAXParserHardener.java
@@ -103,7 +103,7 @@ static SAXParserFactory harden(final SAXParserFactory 
factory) {
      */
     static XMLReader hardenReader(final XMLReader reader) {
         if (reader instanceof HardeningXMLReader) {
-            // Already hardened (for example, handed back through 
XmlFactories.harden(XMLReader)); the floor is already in place.
+            // Already hardened (for example, a reader from a hardened factory 
passed back through hardenReader); the floor is already in place.
             return reader;
         }
         if (ANDROID_EXPAT_READER.equals(reader.getClass().getName())) {
diff --git a/src/main/java/org/apache/commons/xml/SaxonProvider.java 
b/src/main/java/org/apache/commons/xml/SaxonProvider.java
index eaec0ca..9abc224 100644
--- a/src/main/java/org/apache/commons/xml/SaxonProvider.java
+++ b/src/main/java/org/apache/commons/xml/SaxonProvider.java
@@ -52,7 +52,7 @@ final class SaxonProvider {
      *
      * <ol>
      *   <li><b>SAX layer.</b> {@link #makeParser} hands every {@link 
XMLReader} Saxon would otherwise use through
-     *   {@link XmlFactories#harden(XMLReader)}, which routes it to the 
matching bundled hardening recipe. External DTDs, entities and XInclude
+     *   {@link SAXParserHardener#hardenReader(XMLReader)}, which routes it to 
the matching bundled hardening recipe. External DTDs, entities and XInclude
      *   resolve to empty content at parse time.</li>
      *   <li><b>Resource-resolution layer.</b> A non-removable ignore-all 
{@link ResourceResolver} floor backs every resolution chain ({@code 
xsl:include},
      *   {@code xsl:import}, {@code xsl:source-document}, and the XPath/XSLT 
functions {@code fn:doc}, {@code fn:document}, {@code fn:unparsed-text},
diff --git a/src/main/java/org/apache/commons/xml/XmlFactories.java 
b/src/main/java/org/apache/commons/xml/XmlFactories.java
index 270c830..a01b914 100644
--- a/src/main/java/org/apache/commons/xml/XmlFactories.java
+++ b/src/main/java/org/apache/commons/xml/XmlFactories.java
@@ -21,14 +21,10 @@
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.SAXParserFactory;
 import javax.xml.stream.XMLInputFactory;
-import javax.xml.transform.Source;
-import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.validation.SchemaFactory;
 import javax.xml.xpath.XPathFactory;
 
-import org.xml.sax.XMLReader;
-
 /**
  * Entry point for obtaining hardened JAXP factories.
  *
@@ -80,31 +76,6 @@ public final class XmlFactories {
      */
     static final String THROW_ON_UNRESOLVED = 
"org.apache.commons.xml.throwOnUnresolved";
 
-    /**
-     * Rewrites a {@link Source} so that any SAX parsing it triggers runs 
through an {@link XmlFactories}-hardened {@link XMLReader}.
-     *
-     * <p>Only a {@code StreamSource} or a {@code SAXSource} without a reader 
is enriched with a hardened, namespace-aware reader; other kinds of sources are
-     * returned as-is.</p>
-     *
-     * @param source The source to harden; never {@code null}.
-     * @return A hardened source.
-     * @throws TransformerConfigurationException if a hardened reader cannot 
be obtained.
-     */
-    public static Source harden(final Source source) throws 
TransformerConfigurationException {
-        return SAXParserHardener.hardenSource(source);
-    }
-
-    /**
-     * Hardens an existing {@link XMLReader}.
-     *
-     * @param reader The reader to harden; never {@code null}.
-     * @return A hardened reader.
-     * @throws IllegalStateException if a required hardening setting cannot be 
applied to the underlying implementation.
-     */
-    public static XMLReader harden(final XMLReader reader) {
-        return SAXParserHardener.hardenReader(reader);
-    }
-
     /**
      * Returns a fresh, hardened {@link DocumentBuilderFactory}.
      *
diff --git a/src/test/java/org/apache/commons/xml/AttackTestSupport.java 
b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
index 8ca31ee..df2c334 100644
--- a/src/test/java/org/apache/commons/xml/AttackTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
@@ -691,7 +691,7 @@ static void assertValidatorValidates(final String xml) {
     /**
      * Asserts a hardened-in-place XMLReader parse of the payload throws.
      *
-     * <p>{@link XMLReader#parse(InputSource)} on a raw reader hardened via 
{@link XmlFactories#harden(XMLReader)}; only a thrown exception passes.</p>
+     * <p>{@link XMLReader#parse(InputSource)} on a raw reader hardened via 
{@link SAXParserHardener#hardenReader(XMLReader)}; only a thrown exception 
passes.</p>
      */
     static void assertXmlReaderBlocks(final String payload) {
         assertParseFails(() -> consumeXmlReader(rawHardenedReader(), payload), 
"XMLReader", SAXException.class);
@@ -707,7 +707,7 @@ static void assertXmlReaderBlocksOrDoesNotLeak(final String 
payload) {
     /**
      * Asserts a hardened-in-place XMLReader parse completes without throwing 
and without leaked content.
      *
-     * <p>{@link XMLReader#parse(InputSource)} on a raw reader hardened via 
{@link XmlFactories#harden(XMLReader)}; use this when the hardening contract
+     * <p>{@link XMLReader#parse(InputSource)} on a raw reader hardened via 
{@link SAXParserHardener#hardenReader(XMLReader)}; use this when the hardening 
contract
      * guarantees the parse succeeds but never resolves the external 
resource.</p>
      */
     static void assertXmlReaderDoesNotLeak(final String payload) {
@@ -717,7 +717,7 @@ static void assertXmlReaderDoesNotLeak(final String 
payload) {
     /**
      * Asserts a hardened-in-place XMLReader parse succeeds.
      *
-     * <p>{@link XMLReader#parse(InputSource)} on a raw reader hardened via 
{@link XmlFactories#harden(XMLReader)}; positive control for DOCTYPE-only
+     * <p>{@link XMLReader#parse(InputSource)} on a raw reader hardened via 
{@link SAXParserHardener#hardenReader(XMLReader)}; positive control for 
DOCTYPE-only
      * payloads.</p>
      */
     static void assertXmlReaderParses(final String payload) {
@@ -915,13 +915,13 @@ private static boolean probeDomResolvesInternalEntities() 
{
         }
     }
 
-    /** Builds a raw {@link XMLReader} from a deliberately permissive {@link 
SAXParserFactory} and hardens it via {@link XmlFactories#harden(XMLReader)}. */
+    /** Builds a raw {@link XMLReader} from a deliberately permissive {@link 
SAXParserFactory} and hardens it via {@link 
SAXParserHardener#hardenReader(XMLReader)}. */
     private static XMLReader rawHardenedReader() throws Exception {
         final SAXParserFactory factory = SAXParserFactory.newInstance();
         if (!IS_ANDROID) {
             factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
         }
-        return XmlFactories.harden(factory.newSAXParser().getXMLReader());
+        return 
SAXParserHardener.hardenReader(factory.newSAXParser().getXMLReader());
     }
 
     /** Opens the named test resource as a {@link StreamSource} preserving its 
system id, so relative includes/imports/redefines resolve normally. */
diff --git a/src/test/java/org/apache/commons/xml/XIncludeTest.java 
b/src/test/java/org/apache/commons/xml/XIncludeTest.java
index a75a715..84a3933 100644
--- a/src/test/java/org/apache/commons/xml/XIncludeTest.java
+++ b/src/test/java/org/apache/commons/xml/XIncludeTest.java
@@ -331,7 +331,7 @@ void hardenedSaxNullResolverDoesNotLeak() throws Exception {
 
     //endregion
 
-    //region harden(XMLReader): reader with XInclude enabled before hardening 
is blocked
+    //region hardenReader: reader with XInclude enabled before hardening is 
blocked (the internal recipe behind the hardened factory and SaxonProvider)
 
     @Test
     @Tag("sax")
@@ -342,9 +342,9 @@ void hardenReaderBlocksParseXml() throws Exception {
         final SAXParserFactory unhardenedFactory = 
SAXParserFactory.newInstance();
         unhardenedFactory.setNamespaceAware(true);
         assumeXIncludeAware(unhardenedFactory);
-        final XMLReader reader = 
XmlFactories.harden(unhardenedFactory.newSAXParser().getXMLReader());
+        final XMLReader reader = 
SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
         assertThrows(SAXException.class, () -> reader.parse(input),
-                "harden(reader) should block XInclude parse=xml on reader with 
XInclude already enabled");
+                "hardenReader should block XInclude parse=xml on reader with 
XInclude already enabled");
     }
 
     @Test
@@ -355,10 +355,10 @@ void hardenReaderBlocksParseText() throws Exception {
         final SAXParserFactory unhardenedFactory = 
SAXParserFactory.newInstance();
         unhardenedFactory.setNamespaceAware(true);
         assumeXIncludeAware(unhardenedFactory);
-        final XMLReader reader = 
XmlFactories.harden(unhardenedFactory.newSAXParser().getXMLReader());
+        final XMLReader reader = 
SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
         final String captured = captureCharacters(reader, input);
         assertFalse(captured.contains(LEAKED_MARKER),
-                "harden(reader) parse=text must resolve the include to empty, 
not leak; got: " + captured);
+                "hardenReader parse=text must resolve the include to empty, 
not leak; got: " + captured);
     }
 
     @Test
@@ -369,11 +369,11 @@ void hardenReaderAllowListResolvesParseXml() throws 
Exception {
         final SAXParserFactory unhardenedFactory = 
SAXParserFactory.newInstance();
         unhardenedFactory.setNamespaceAware(true);
         assumeXIncludeAware(unhardenedFactory);
-        final XMLReader reader = 
XmlFactories.harden(unhardenedFactory.newSAXParser().getXMLReader());
+        final XMLReader reader = 
SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
         reader.setEntityResolver(new AllowListResolver());
         final String captured = captureCharacters(reader, input);
         assertEquals(RESOLVED_MARKER, captured.trim(),
-                "harden(reader) + allow-list should resolve to the resolver's 
content on a reader with XInclude already enabled");
+                "hardenReader + allow-list should resolve to the resolver's 
content on a reader with XInclude already enabled");
     }
 
     //endregion

Reply via email to