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); + } }
