This is an automated email from the ASF dual-hosted git repository.

ppkarwasz pushed a commit to branch fix/saxparser-reset
in repository https://gitbox.apache.org/repos/asf/commons-xml.git

commit bcba6adba29a7a95ac0ee0cf01e1ea77e5ba61e3
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Tue Aug 18 09:27:37 2026 +0200

    fix: Restore hardened state after Transformer.reset()
    
    The JAXP reset contract reverts a transformer to its just-created state,
    and several implementations clear the URI resolver installed by the
    constructor. HardeningTransformer.reset() now re-establishes the floor,
    seeded again with the factory's compile-time resolver, matching the other
    hardened wrappers.
    
    Assisted-By: Claude Fable 5 <[email protected]>
---
 .../apache/commons/xml/HardeningTransformer.java    | 19 +++++++++++++------
 .../org/apache/commons/xml/ResetHardeningTest.java  | 21 +++++++++++++++++++++
 2 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformer.java 
b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
index a1b9322..6ab4f97 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformer.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
@@ -33,20 +33,32 @@
  * 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)}
- * routes a caller's resolver through it rather than replacing it, so the 
block cannot be dropped.</p>
+ * routes a caller's resolver through it rather than replacing it, so the 
block cannot be dropped. {@link #reset()} re-establishes the floor, seeded 
again with
+ * the factory's compile-time resolver, matching the just-constructed 
state.</p>
  */
 final class HardeningTransformer extends Transformer {
 
     private final Transformer delegate;
 
+    /** Compile-time URIResolver snapshot the floor is seeded with, both at 
construction and again on {@link #reset()}. */
+    private final URIResolver uriResolver;
+
     private final FallbackIgnoreURIResolver floor;
 
     HardeningTransformer(final Transformer delegate, final URIResolver 
uriResolver) {
         this.delegate = delegate;
+        this.uriResolver = uriResolver;
         this.floor = new FallbackIgnoreURIResolver(uriResolver);
         delegate.setURIResolver(floor);
     }
 
+    @Override
+    public void reset() {
+        delegate.reset();
+        floor.setDelegate(uriResolver);
+        delegate.setURIResolver(floor);
+    }
+
     @Override
     public void setURIResolver(final URIResolver resolver) {
         floor.setDelegate(resolver);
@@ -92,11 +104,6 @@ public Object getParameter(final String name) {
         return delegate.getParameter(name);
     }
 
-    @Override
-    public void reset() {
-        delegate.reset();
-    }
-
     @Override
     public void setErrorListener(final ErrorListener listener) {
         delegate.setErrorListener(listener);
diff --git a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java 
b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
index a7221bd..1c72eeb 100644
--- a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
+++ b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
@@ -19,8 +19,13 @@
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
+import java.io.StringWriter;
+
 import javax.xml.XMLConstants;
 import javax.xml.parsers.SAXParser;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
 import javax.xml.validation.Validator;
 
 import org.junit.jupiter.api.Tag;
@@ -77,4 +82,20 @@ void validatorResetKeepsResourceResolverFloor() throws 
Exception {
         AttackTestSupport.assertParseFails(() -> 
validator.validate(AttackTestSupport.resourceSource("schema-location-instance.xml")),
                 "Validator after reset", SAXException.class, 
SecurityException.class);
     }
+
+    @Test
+    @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()
+                
.newTemplates(AttackTestSupport.resourceSource("with-document.xsl")).newTransformer();
+        AttackTestSupport.assumeDoesNotThrow(transformer::reset);
+        final StringWriter sink = new StringWriter();
+        try {
+            transformer.transform(AttackTestSupport.streamSource("<root/>"), 
new StreamResult(sink));
+        } catch (final TransformerException blocked) {
+            return; // Acceptable: rejected at transform rather than resolved 
to empty.
+        }
+        assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), 
"document() leaked after reset:\n" + sink);
+    }
 }

Reply via email to