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

garydgregory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-secure-xml.git

commit 18dcc5f74a1227bfe5ac96ef54fbe804ef3c6be8
Author: Gary Gregory <[email protected]>
AuthorDate: Tue Sep 1 08:03:20 2026 -0400

    Sort members.
---
 .../xml/secure/SecureTransformerFactory.java       | 30 ++++----
 .../commons/xml/secure/AttackTestSupport.java      | 42 +++++------
 .../xml/secure/OverrideDefaultParserTest.java      | 10 +--
 .../secure/SaxonTransformerExternalCallsTest.java  | 88 +++++++++++-----------
 .../xml/secure/SchemaContentModelLimitTest.java    | 34 ++++-----
 .../commons/xml/secure/SecureXMLFilterTest.java    | 36 ++++-----
 .../xml/secure/SecureXMLInputFactoryTest.java      | 56 +++++++-------
 .../commons/xml/secure/SecureXPathFactoryTest.java | 76 +++++++++----------
 8 files changed, 186 insertions(+), 186 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/xml/secure/SecureTransformerFactory.java 
b/src/main/java/org/apache/commons/xml/secure/SecureTransformerFactory.java
index f7dfa13..e31f7ab 100644
--- a/src/main/java/org/apache/commons/xml/secure/SecureTransformerFactory.java
+++ b/src/main/java/org/apache/commons/xml/secure/SecureTransformerFactory.java
@@ -187,21 +187,6 @@ private Wrapper(final SAXTransformerFactory delegate, 
final Supplier<Source> emp
             delegate.setURIResolver(floor);
         }
 
-        /**
-         * {@inheritDoc}
-         *
-         * @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.
-         */
-        @Override
-        public Source getAssociatedStylesheet(final Source source, final 
String media, final String title, final String charset)
-                throws TransformerConfigurationException {
-            // Xalan's getAssociatedStylesheet drops a SAXSource's reader and 
self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849), and the
-            // JDK's XSLTC did the same before 8u162; hand those a DOM so no 
parser but ours ever sees the document.
-            final Source secure = isXalan(delegate) || JAVA_8 ? 
secureSourceToDom(source) : SecureSAXParserFactory.secure(source, 
overrideDefaultParser());
-            return floorAssociated(delegate.getAssociatedStylesheet(secure, 
media, title, charset), secure.getSystemId());
-        }
-
         /**
          * Routes the href an {@code xml-stylesheet} PI yielded through the 
floor, so a URI distilled from untrusted content is opted in by the caller's
          * resolver or resolved to empty like any other content-named 
reference.
@@ -227,6 +212,21 @@ private Source floorAssociated(final Source associated, 
final String base) throw
             }
         }
 
+        /**
+         * {@inheritDoc}
+         *
+         * @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.
+         */
+        @Override
+        public Source getAssociatedStylesheet(final Source source, final 
String media, final String title, final String charset)
+                throws TransformerConfigurationException {
+            // Xalan's getAssociatedStylesheet drops a SAXSource's reader and 
self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849), and the
+            // JDK's XSLTC did the same before 8u162; hand those a DOM so no 
parser but ours ever sees the document.
+            final Source secure = isXalan(delegate) || JAVA_8 ? 
secureSourceToDom(source) : SecureSAXParserFactory.secure(source, 
overrideDefaultParser());
+            return floorAssociated(delegate.getAssociatedStylesheet(secure, 
media, title, charset), secure.getSystemId());
+        }
+
         @Override
         public Object getAttribute(final String name) {
             return delegate.getAttribute(name);
diff --git a/src/test/java/org/apache/commons/xml/secure/AttackTestSupport.java 
b/src/test/java/org/apache/commons/xml/secure/AttackTestSupport.java
index 9277628..819f7ec 100644
--- a/src/test/java/org/apache/commons/xml/secure/AttackTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/secure/AttackTestSupport.java
@@ -240,6 +240,13 @@ static void assertDomBlocksOrDoesNotLeak(final String 
payload) {
         assertNoLeakOrThrows(() -> 
domParseAndCaptureText(SecureDocumentBuilderFactory.newInstance(), payload), 
"DOM", SAXException.class);
     }
 
+    /**
+     * Same contract as {@link #assertDomDoesNotLeak(String)}, on a 
caller-configured secure factory.
+     */
+    static void assertDomDoesNotLeak(final DocumentBuilderFactory factory, 
final String payload) {
+        assertNoLeakStrict(() -> domParseAndCaptureText(factory, payload), 
"DOM");
+    }
+
     /**
      * Asserts a secure DOM parse completes without throwing and without 
leaked content.
      *
@@ -250,13 +257,6 @@ static void assertDomDoesNotLeak(final String payload) {
         assertDomDoesNotLeak(SecureDocumentBuilderFactory.newInstance(), 
payload);
     }
 
-    /**
-     * Same contract as {@link #assertDomDoesNotLeak(String)}, on a 
caller-configured secure factory.
-     */
-    static void assertDomDoesNotLeak(final DocumentBuilderFactory factory, 
final String payload) {
-        assertNoLeakStrict(() -> domParseAndCaptureText(factory, payload), 
"DOM");
-    }
-
     /**
      * Asserts a secure DOM parse succeeds.
      *
@@ -507,6 +507,13 @@ static void assertSaxParses(final String payload) {
         assertParseSucceeds(() -> 
consumeXmlReader(strictXMLReader(SecureSAXParserFactory.newInstance()), 
payload), "SAX");
     }
 
+    /**
+     * Same contract as {@link #assertSchemaBlocks(Source)}, on a 
caller-configured secure factory.
+     */
+    static void assertSchemaBlocks(final SchemaFactory factory, final Source 
xsd) {
+        assertParseFails(() -> strictSchema(factory, xsd), "Schema compile", 
SAXException.class, SecurityException.class);
+    }
+
     /**
      * Asserts a secure Schema compilation throws.
      *
@@ -516,13 +523,6 @@ static void assertSchemaBlocks(final Source xsd) {
         
assertSchemaBlocks(SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
 xsd);
     }
 
-    /**
-     * Same contract as {@link #assertSchemaBlocks(Source)}, on a 
caller-configured secure factory.
-     */
-    static void assertSchemaBlocks(final SchemaFactory factory, final Source 
xsd) {
-        assertParseFails(() -> strictSchema(factory, xsd), "Schema compile", 
SAXException.class, SecurityException.class);
-    }
-
     /**
      * Asserts a secure Schema compile either blocks or completes: an 
unresolved import resolves to an empty schema (which may itself fail to 
compile) or is
      * rejected outright. See {@link #assertDomBlocksOrDoesNotLeak(String)}.
@@ -543,6 +543,13 @@ static void assertSchemaCompiles(final Source xsd) {
         assertParseSucceeds(() -> 
strictSchema(SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
 xsd), "Schema compile");
     }
 
+    /**
+     * Same contract as {@link #assertSchemaDoesNotLeak(Source)}, on a 
caller-configured secure factory.
+     */
+    static void assertSchemaDoesNotLeak(final SchemaFactory factory, final 
Source xsd) {
+        assertParseSucceeds(() -> strictSchema(factory, xsd), "Schema 
compile");
+    }
+
     /**
      * Asserts a secure Schema compilation completes without throwing.
      *
@@ -554,13 +561,6 @@ static void assertSchemaDoesNotLeak(final Source xsd) {
         
assertSchemaDoesNotLeak(SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI),
 xsd);
     }
 
-    /**
-     * Same contract as {@link #assertSchemaDoesNotLeak(Source)}, on a 
caller-configured secure factory.
-     */
-    static void assertSchemaDoesNotLeak(final SchemaFactory factory, final 
Source xsd) {
-        assertParseSucceeds(() -> strictSchema(factory, xsd), "Schema 
compile");
-    }
-
     /**
      * Asserts a secure StAX parse of the payload throws.
      *
diff --git 
a/src/test/java/org/apache/commons/xml/secure/OverrideDefaultParserTest.java 
b/src/test/java/org/apache/commons/xml/secure/OverrideDefaultParserTest.java
index d5b640e..eec1217 100644
--- a/src/test/java/org/apache/commons/xml/secure/OverrideDefaultParserTest.java
+++ b/src/test/java/org/apache/commons/xml/secure/OverrideDefaultParserTest.java
@@ -58,6 +58,11 @@ class OverrideDefaultParserTest {
     /** {@code true} where the runtime's factories know {@value 
SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER}; JDK 8 gained it in 8u162. */
     private static final boolean SUPPORTS_FEATURE = probeFeature();
 
+    /** Skips a test on a runtime whose factories do not recognize the 
feature, where there is no selection to observe. */
+    private static void assumeFeatureSupported() {
+        assumeTrue(SUPPORTS_FEATURE, "runtime does not recognize " + FEATURE);
+    }
+
     private static boolean probeFeature() {
         try {
             TransformerFactory.newInstance().setFeature(FEATURE, true);
@@ -67,11 +72,6 @@ private static boolean probeFeature() {
         }
     }
 
-    /** Skips a test on a runtime whose factories do not recognize the 
feature, where there is no selection to observe. */
-    private static void assumeFeatureSupported() {
-        assumeTrue(SUPPORTS_FEATURE, "runtime does not recognize " + FEATURE);
-    }
-
     private static String transform(final TransformerFactory factory, final 
String text) throws Exception {
         final Transformer transformer = 
factory.newTransformer(AttackTestSupport.streamSource(AttackTestSupport.xsltBody(text)));
         final StringWriter out = new StringWriter();
diff --git 
a/src/test/java/org/apache/commons/xml/secure/SaxonTransformerExternalCallsTest.java
 
b/src/test/java/org/apache/commons/xml/secure/SaxonTransformerExternalCallsTest.java
index d75f430..eadd3c0 100644
--- 
a/src/test/java/org/apache/commons/xml/secure/SaxonTransformerExternalCallsTest.java
+++ 
b/src/test/java/org/apache/commons/xml/secure/SaxonTransformerExternalCallsTest.java
@@ -52,6 +52,22 @@ class SaxonTransformerExternalCallsTest {
 
     private static final String SAXON_TRANSFORMER_FACTORY_CLASS = 
"net.sf.saxon.TransformerFactoryImpl";
 
+    /** Runs the expression through the secure Saxon factory; a throw is an 
acceptable block, otherwise the marker must be absent. */
+    private static void assertSecureDoesNotLeak(final String expression) {
+        try {
+            final String result = 
transform(SecureTransformerFactory.secure(saxonFactory()), expression);
+            assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), 
"secure Saxon transform leaked through " + expression + ":\n" + result);
+        } catch (final TransformerException blocked) {
+            // Throwing also prevents the leak.
+        }
+    }
+
+    /** Runs the expression through the unconfigured Saxon factory and asserts 
the marker is resolved into the output (leak control). */
+    private static void assertUnconfiguredLeaks(final String expression) 
throws TransformerException {
+        final String result = transform(saxonFactory(), expression);
+        assertTrue(result.contains(AttackTestSupport.LEAKED_MARKER), 
"unconfigured Saxon was expected to resolve " + expression + ", got: " + 
result);
+    }
+
     private static void assumeSaxonPresent() {
         boolean present;
         try {
@@ -63,19 +79,13 @@ private static void assumeSaxonPresent() {
         Assumptions.assumeTrue(present, "Saxon is not on the classpath");
     }
 
-    /** Wraps a single XPath 3.1 expression in an XSLT 3.0 stylesheet that 
copies its string value into the output. */
-    private static String stylesheet(final String expression) {
-        return "<?xml version=\"1.0\"?>\n"
-                + "<xsl:stylesheet version=\"3.0\" 
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>\n"
-                + "  <xsl:template match=\"/\">\n"
-                + "    <leaked><xsl:value-of select=\"" + expression + 
"\"/></leaked>\n"
-                + "  </xsl:template>\n"
-                + "</xsl:stylesheet>\n";
-    }
-
-    /** URL of a fixture that carries {@link AttackTestSupport#LEAKED_MARKER}; 
{@code name} is a file under {@code src/test/resources/leaked/}. */
-    private static String url(final String name) {
-        return AttackTestSupport.resourceUrl(name).toString();
+    /** The {@code unparsed-text-available} answer under the secure factory, 
or {@code "blocked"} when the transform throws. */
+    private static String availabilityUnderSecure(final String uri) {
+        try {
+            return transform(SecureTransformerFactory.secure(saxonFactory()), 
"unparsed-text-available('" + uri + "')").contains("true") ? "true" : "false";
+        } catch (final TransformerException blocked) {
+            return "blocked";
+        }
     }
 
     /** URL of a sibling resource that does not exist, so a real fetch fails; 
used as the negative side of the existence-oracle check. */
@@ -91,6 +101,16 @@ private static TransformerFactory saxonFactory() {
         }
     }
 
+    /** Wraps a single XPath 3.1 expression in an XSLT 3.0 stylesheet that 
copies its string value into the output. */
+    private static String stylesheet(final String expression) {
+        return "<?xml version=\"1.0\"?>\n"
+                + "<xsl:stylesheet version=\"3.0\" 
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>\n"
+                + "  <xsl:template match=\"/\">\n"
+                + "    <leaked><xsl:value-of select=\"" + expression + 
"\"/></leaked>\n"
+                + "  </xsl:template>\n"
+                + "</xsl:stylesheet>\n";
+    }
+
     private static String transform(final TransformerFactory factory, final 
String expression) throws TransformerException {
         final StringWriter sink = new StringWriter();
         
factory.newTemplates(AttackTestSupport.streamSource(stylesheet(expression))).newTransformer()
@@ -98,20 +118,15 @@ private static String transform(final TransformerFactory 
factory, final String e
         return sink.toString();
     }
 
-    /** Runs the expression through the secure Saxon factory; a throw is an 
acceptable block, otherwise the marker must be absent. */
-    private static void assertSecureDoesNotLeak(final String expression) {
-        try {
-            final String result = 
transform(SecureTransformerFactory.secure(saxonFactory()), expression);
-            assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), 
"secure Saxon transform leaked through " + expression + ":\n" + result);
-        } catch (final TransformerException blocked) {
-            // Throwing also prevents the leak.
-        }
+    /** URL of a fixture that carries {@link AttackTestSupport#LEAKED_MARKER}; 
{@code name} is a file under {@code src/test/resources/leaked/}. */
+    private static String url(final String name) {
+        return AttackTestSupport.resourceUrl(name).toString();
     }
 
-    /** Runs the expression through the unconfigured Saxon factory and asserts 
the marker is resolved into the output (leak control). */
-    private static void assertUnconfiguredLeaks(final String expression) 
throws TransformerException {
-        final String result = transform(saxonFactory(), expression);
-        assertTrue(result.contains(AttackTestSupport.LEAKED_MARKER), 
"unconfigured Saxon was expected to resolve " + expression + ", got: " + 
result);
+    @Test
+    void secureTransformerBlocksJsonDoc() {
+        assumeSaxonPresent();
+        assertSecureDoesNotLeak("json-doc('" + url("referenced.json") + 
"')?leaked");
     }
 
     @Test
@@ -126,12 +141,6 @@ void secureTransformerBlocksUnparsedTextLines() {
         assertSecureDoesNotLeak("string-join(unparsed-text-lines('" + 
url("referenced.txt") + "'), ' ')");
     }
 
-    @Test
-    void secureTransformerBlocksJsonDoc() {
-        assumeSaxonPresent();
-        assertSecureDoesNotLeak("json-doc('" + url("referenced.json") + 
"')?leaked");
-    }
-
     @Test
     void secureTransformerHidesUnparsedTextAvailability() {
         assumeSaxonPresent();
@@ -152,13 +161,10 @@ void secureTransformerHidesUnparsedTextAvailability() {
                 "secure Saxon unparsed-text-available still distinguishes an 
existing file from a missing one");
     }
 
-    /** The {@code unparsed-text-available} answer under the secure factory, 
or {@code "blocked"} when the transform throws. */
-    private static String availabilityUnderSecure(final String uri) {
-        try {
-            return transform(SecureTransformerFactory.secure(saxonFactory()), 
"unparsed-text-available('" + uri + "')").contains("true") ? "true" : "false";
-        } catch (final TransformerException blocked) {
-            return "blocked";
-        }
+    @Test
+    void unconfiguredTransformerLeaksJsonDoc() throws TransformerException {
+        assumeSaxonPresent();
+        assertUnconfiguredLeaks("json-doc('" + url("referenced.json") + 
"')?leaked");
     }
 
     @Test
@@ -172,10 +178,4 @@ void unconfiguredTransformerLeaksUnparsedTextLines() 
throws TransformerException
         assumeSaxonPresent();
         assertUnconfiguredLeaks("string-join(unparsed-text-lines('" + 
url("referenced.txt") + "'), ' ')");
     }
-
-    @Test
-    void unconfiguredTransformerLeaksJsonDoc() throws TransformerException {
-        assumeSaxonPresent();
-        assertUnconfiguredLeaks("json-doc('" + url("referenced.json") + 
"')?leaked");
-    }
 }
diff --git 
a/src/test/java/org/apache/commons/xml/secure/SchemaContentModelLimitTest.java 
b/src/test/java/org/apache/commons/xml/secure/SchemaContentModelLimitTest.java
index 370c6d0..0b4946f 100644
--- 
a/src/test/java/org/apache/commons/xml/secure/SchemaContentModelLimitTest.java
+++ 
b/src/test/java/org/apache/commons/xml/secure/SchemaContentModelLimitTest.java
@@ -44,6 +44,15 @@ class SchemaContentModelLimitTest {
     /** Above both recognized implementations' limits (3,000 nodes on Xerces, 
5,000 on the stock JDK); an unbounded run still finishes in seconds. */
     private static final int MAX_OCCURS = 10_000;
 
+    /** Compiles the payload through {@code factory} and validates a matching 
instance, the step that forces the expansion. */
+    private static void compileAndValidate(final SchemaFactory factory) throws 
Exception {
+        factory.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+        final Schema schema = 
factory.newSchema(AttackTestSupport.streamSource(maxOccursPayload()));
+        final Validator validator = schema.newValidator();
+        validator.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+        
validator.validate(AttackTestSupport.streamSource("<root><a>x</a><b>y</b></root>"));
+    }
+
     private static String maxOccursPayload() {
         return "<?xml version=\"1.0\"?>\n"
                 + "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\";>\n"
@@ -59,21 +68,20 @@ private static String maxOccursPayload() {
                 + "</xs:schema>\n";
     }
 
-    /** Compiles the payload through {@code factory} and validates a matching 
instance, the step that forces the expansion. */
-    private static void compileAndValidate(final SchemaFactory factory) throws 
Exception {
-        factory.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
-        final Schema schema = 
factory.newSchema(AttackTestSupport.streamSource(maxOccursPayload()));
-        final Validator validator = schema.newValidator();
-        validator.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
-        
validator.validate(AttackTestSupport.streamSource("<root><a>x</a><b>y</b></root>"));
-    }
-
     @Test
     void secureSchemaBoundsContentModelExpansion() {
         AttackTestSupport.assertParseFails(() -> 
compileAndValidate(SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)),
                 "Schema content-model expansion", 
org.xml.sax.SAXException.class);
     }
 
+    @Test
+    void unconfiguredSchemaValidatesWhereTheLimitIsOptional() {
+        // Control: the payload is a valid schema and instance, so a rejection 
above is the limit firing and not a malformed fixture. It is skipped on an
+        // implementation that bounds the expansion unconditionally (the stock 
JDK), where there is no unbounded run to compare against.
+        final SchemaFactory factory = 
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        AttackTestSupport.assumeDoesNotThrow(() -> 
compileAndValidate(factory));
+    }
+
     @Test
     void unconfiguredSchemaWithSecureProcessingBoundsContentModelExpansion() {
         // Control: the payload does trip the limit once secure processing is 
on, so a pass above is the limit firing rather than the payload being harmless.
@@ -83,12 +91,4 @@ void 
unconfiguredSchemaWithSecureProcessingBoundsContentModelExpansion() {
             compileAndValidate(factory);
         }, "Schema content-model expansion", org.xml.sax.SAXException.class);
     }
-
-    @Test
-    void unconfiguredSchemaValidatesWhereTheLimitIsOptional() {
-        // Control: the payload is a valid schema and instance, so a rejection 
above is the limit firing and not a malformed fixture. It is skipped on an
-        // implementation that bounds the expansion unconditionally (the stock 
JDK), where there is no unbounded run to compare against.
-        final SchemaFactory factory = 
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-        AttackTestSupport.assumeDoesNotThrow(() -> 
compileAndValidate(factory));
-    }
 }
diff --git 
a/src/test/java/org/apache/commons/xml/secure/SecureXMLFilterTest.java 
b/src/test/java/org/apache/commons/xml/secure/SecureXMLFilterTest.java
index 8414a80..5f68d0b 100644
--- a/src/test/java/org/apache/commons/xml/secure/SecureXMLFilterTest.java
+++ b/src/test/java/org/apache/commons/xml/secure/SecureXMLFilterTest.java
@@ -201,24 +201,6 @@ void sendsLexicalEventsToALexicalContentHandler() throws 
Exception {
         filter.parse(new InputSource(new 
StringReader("<root><!--comment--><![CDATA[text]]></root>")));
     }
 
-    @Test
-    void wrapsErrorHandlerFailuresAndUsesTheConfiguredParent() throws 
Exception {
-        final SecureXMLFilter filter = filter();
-        final SAXException handlerFailure = new SAXException("error handler");
-        filter.setErrorHandler(new DefaultHandler() {
-
-            @Override
-            public void warning(final SAXParseException e) throws SAXException 
{
-                throw handlerFailure;
-            }
-        });
-        final TransformerException warning = 
assertThrows(TransformerException.class, () -> filter.warning(new 
TransformerException("warning")));
-        assertSame(handlerFailure, warning.getCause());
-        filter.setContentHandler(new DefaultHandler());
-        filter.setParent(SecureSAXParserFactory.newXMLReader(false));
-        filter.parse(new InputSource(new StringReader("<root/>")));
-    }
-
     @Test
     void surfacesIoFailuresFromTheParentReader() throws Exception {
         final SecureXMLFilter filter = filter();
@@ -236,4 +218,22 @@ public void parse(final InputSource input) throws 
IOException {
         assertTrue(exception instanceof SAXException || exception instanceof 
IOException,
                 "parse must fail with a declared exception type: " + 
exception);
     }
+
+    @Test
+    void wrapsErrorHandlerFailuresAndUsesTheConfiguredParent() throws 
Exception {
+        final SecureXMLFilter filter = filter();
+        final SAXException handlerFailure = new SAXException("error handler");
+        filter.setErrorHandler(new DefaultHandler() {
+
+            @Override
+            public void warning(final SAXParseException e) throws SAXException 
{
+                throw handlerFailure;
+            }
+        });
+        final TransformerException warning = 
assertThrows(TransformerException.class, () -> filter.warning(new 
TransformerException("warning")));
+        assertSame(handlerFailure, warning.getCause());
+        filter.setContentHandler(new DefaultHandler());
+        filter.setParent(SecureSAXParserFactory.newXMLReader(false));
+        filter.parse(new InputSource(new StringReader("<root/>")));
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/xml/secure/SecureXMLInputFactoryTest.java 
b/src/test/java/org/apache/commons/xml/secure/SecureXMLInputFactoryTest.java
index 05b92e7..ea6703b 100644
--- a/src/test/java/org/apache/commons/xml/secure/SecureXMLInputFactoryTest.java
+++ b/src/test/java/org/apache/commons/xml/secure/SecureXMLInputFactoryTest.java
@@ -477,6 +477,18 @@ void 
setPropertyWrongTypeForResolverHookSurfacesDelegateException() {
                 "the delegate must surface its own rejection of a wrong-typed 
resolver");
     }
 
+    @Test
+    void settingAResolverInstallsAFreshFloorInsteadOfMutatingTheInstalledOne() 
{
+        // The implementations copy the floor reference into every reader they 
create, so mutating the installed floor would change the resolution policy of
+        // readers created before the call, including ones already parsing. 
Replacing it leaves what those readers captured alone.
+        final RecordingXMLInputFactory fake = new RecordingXMLInputFactory();
+        final XMLInputFactory secure = SecureXMLInputFactory.secure(fake);
+        final Object captured = fake.resolverHook;
+        secure.setXMLResolver((publicID, systemID, baseURI, namespace) -> 
null);
+        assertNotSame(captured, fake.resolverHook, "setting a resolver must 
install a fresh floor, not re-delegate the one already on the hook");
+        assertNull(((FallbackIgnoreXMLResolver) captured).getDelegate(), "the 
floor an existing reader captured must keep resolving to empty");
+    }
+
     @Test
     void setXMLResolverNullClearsCallerDelegate() {
         final RecordingXMLInputFactory fake = new RecordingXMLInputFactory();
@@ -526,6 +538,22 @@ void woodstoxDtdHookRoutesBehindInstalledFloor() {
         assertSame(second, 
factory.getProperty(SecureXMLInputFactory.WSTX_DTD_RESOLVER), "a second caller 
resolver must replace the first behind the floor");
     }
 
+    @Test
+    void woodstoxResolverHooksStayIndependent() {
+        // Woodstox routes setXMLResolver to both its DTD-subset and entity 
hooks, so one floor object sits on several of them. Setting one hook must not
+        // answer the others, which it would if the shared floor were mutated 
in place.
+        final XMLInputFactory secure = SecureXMLInputFactory.newInstance();
+        final XMLResolver dtd = (publicID, systemID, baseURI, namespace) -> 
null;
+        try {
+            secure.setProperty(SecureXMLInputFactory.WSTX_DTD_RESOLVER, dtd);
+        } catch (final IllegalArgumentException notWoodstox) {
+            Assumptions.abort("the implementation does not support " + 
SecureXMLInputFactory.WSTX_DTD_RESOLVER);
+            return;
+        }
+        assertSame(dtd, 
secure.getProperty(SecureXMLInputFactory.WSTX_DTD_RESOLVER), "the hook the 
caller named must report their resolver");
+        
assertNull(secure.getProperty(SecureXMLInputFactory.WSTX_ENTITY_RESOLVER), "a 
resolver set on the DTD hook must not answer the entity hook");
+    }
+
     @Test
     void woodstoxUndeclaredEntityHookWrapsCallerResolver() {
         final XMLInputFactory factory = SecureXMLInputFactory.newInstance();
@@ -610,34 +638,6 @@ void wrapperDelegatesReaderCreationToDelegate() throws 
Exception {
                 "the exact stream filter must be forwarded");
     }
 
-    @Test
-    void settingAResolverInstallsAFreshFloorInsteadOfMutatingTheInstalledOne() 
{
-        // The implementations copy the floor reference into every reader they 
create, so mutating the installed floor would change the resolution policy of
-        // readers created before the call, including ones already parsing. 
Replacing it leaves what those readers captured alone.
-        final RecordingXMLInputFactory fake = new RecordingXMLInputFactory();
-        final XMLInputFactory secure = SecureXMLInputFactory.secure(fake);
-        final Object captured = fake.resolverHook;
-        secure.setXMLResolver((publicID, systemID, baseURI, namespace) -> 
null);
-        assertNotSame(captured, fake.resolverHook, "setting a resolver must 
install a fresh floor, not re-delegate the one already on the hook");
-        assertNull(((FallbackIgnoreXMLResolver) captured).getDelegate(), "the 
floor an existing reader captured must keep resolving to empty");
-    }
-
-    @Test
-    void woodstoxResolverHooksStayIndependent() {
-        // Woodstox routes setXMLResolver to both its DTD-subset and entity 
hooks, so one floor object sits on several of them. Setting one hook must not
-        // answer the others, which it would if the shared floor were mutated 
in place.
-        final XMLInputFactory secure = SecureXMLInputFactory.newInstance();
-        final XMLResolver dtd = (publicID, systemID, baseURI, namespace) -> 
null;
-        try {
-            secure.setProperty(SecureXMLInputFactory.WSTX_DTD_RESOLVER, dtd);
-        } catch (final IllegalArgumentException notWoodstox) {
-            Assumptions.abort("the implementation does not support " + 
SecureXMLInputFactory.WSTX_DTD_RESOLVER);
-            return;
-        }
-        assertSame(dtd, 
secure.getProperty(SecureXMLInputFactory.WSTX_DTD_RESOLVER), "the hook the 
caller named must report their resolver");
-        
assertNull(secure.getProperty(SecureXMLInputFactory.WSTX_ENTITY_RESOLVER), "a 
resolver set on the DTD hook must not answer the entity hook");
-    }
-
     @Test
     void wrapperInstallsFloorOnDelegateHook() {
         final RecordingXMLInputFactory fake = new RecordingXMLInputFactory();
diff --git 
a/src/test/java/org/apache/commons/xml/secure/SecureXPathFactoryTest.java 
b/src/test/java/org/apache/commons/xml/secure/SecureXPathFactoryTest.java
index caaa5bd..bc270c4 100644
--- a/src/test/java/org/apache/commons/xml/secure/SecureXPathFactoryTest.java
+++ b/src/test/java/org/apache/commons/xml/secure/SecureXPathFactoryTest.java
@@ -40,6 +40,24 @@
 @Tag("xpath")
 class SecureXPathFactoryTest {
 
+    /** A processing limit the JDK's XPath implementation recognizes through 
the Java 18 property API. */
+    private static final String XPATH_GROUP_LIMIT = 
"jdk.xml.xpathExprGrpLimit";
+
+    /**
+     * The Java 18 {@code XPathFactory} property method of the given name, or 
an aborted test where the platform predates it.
+     *
+     * <p>Reached reflectively because this suite compiles against the Java 8 
API, the same reason the wrapper delegates the pair through method handles: the
+     * call has to resolve at run time, which is also exactly how a Java 18 
caller reaches it.</p>
+     */
+    private static Method propertyMethod(final String name, final Class<?>... 
parameterTypes) {
+        try {
+            return XPathFactory.class.getMethod(name, parameterTypes);
+        } catch (final NoSuchMethodException e) {
+            Assumptions.abort("XPathFactory." + name + " requires Java 18 or 
later");
+            throw new AssertionError("unreachable");
+        }
+    }
+
     @Test
     void createsAndConfiguresAFactoryForTheDefaultObjectModel() throws 
Exception {
         final XPathFactory factory = 
SecureXPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
@@ -59,6 +77,17 @@ void createsSecureXPathFromStaticEntryPoints() {
         assertInstanceOf(SecureXPath.class, 
SecureXPathFactory.newDefaultInstance().newXPath());
     }
 
+    @Test
+    void delegatesTheJava18PropertyApi() throws Exception {
+        // The wrapper is compiled against the Java 8 API, so without an 
explicit delegation the inherited default answers for it and every property the
+        // implementation supports, including its own limits, becomes 
unreachable through a secured factory.
+        final Method setProperty = propertyMethod("setProperty", String.class, 
String.class);
+        final Method getProperty = propertyMethod("getProperty", String.class);
+        final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
+        setProperty.invoke(factory, XPATH_GROUP_LIMIT, "5");
+        assertEquals("5", getProperty.invoke(factory, XPATH_GROUP_LIMIT), "a 
property set on the secured factory must be read back from the delegate");
+    }
+
     @Test
     void preservesANullXPathFromTheDelegate() {
         final XPathFactory delegate = new XPathFactory() {
@@ -93,6 +122,15 @@ public void setXPathVariableResolver(final 
XPathVariableResolver resolver) {
         assertNull(SecureXPathFactory.secure(delegate).newXPath());
     }
 
+    @Test
+    void reportsAnUnknownPropertyLikeTheDelegate() {
+        final Method getProperty = propertyMethod("getProperty", String.class);
+        final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
+        final InvocationTargetException thrown = 
assertThrows(InvocationTargetException.class,
+                () -> getProperty.invoke(factory, "jdk.xml.noSuchProperty"));
+        assertInstanceOf(IllegalArgumentException.class, thrown.getCause(), 
"an unrecognized property must surface the delegate's own rejection");
+    }
+
     @Test
     void wrapsARejectedRequiredFeatureInSecureException() {
         final XPathFactory rejectingFactory = new XPathFactory() {
@@ -127,42 +165,4 @@ public void setXPathVariableResolver(final 
XPathVariableResolver resolver) {
         };
         assertThrows(SecureException.class, () -> 
SecureXPathFactory.secure(rejectingFactory));
     }
-
-    /** A processing limit the JDK's XPath implementation recognizes through 
the Java 18 property API. */
-    private static final String XPATH_GROUP_LIMIT = 
"jdk.xml.xpathExprGrpLimit";
-
-    /**
-     * The Java 18 {@code XPathFactory} property method of the given name, or 
an aborted test where the platform predates it.
-     *
-     * <p>Reached reflectively because this suite compiles against the Java 8 
API, the same reason the wrapper delegates the pair through method handles: the
-     * call has to resolve at run time, which is also exactly how a Java 18 
caller reaches it.</p>
-     */
-    private static Method propertyMethod(final String name, final Class<?>... 
parameterTypes) {
-        try {
-            return XPathFactory.class.getMethod(name, parameterTypes);
-        } catch (final NoSuchMethodException e) {
-            Assumptions.abort("XPathFactory." + name + " requires Java 18 or 
later");
-            throw new AssertionError("unreachable");
-        }
-    }
-
-    @Test
-    void delegatesTheJava18PropertyApi() throws Exception {
-        // The wrapper is compiled against the Java 8 API, so without an 
explicit delegation the inherited default answers for it and every property the
-        // implementation supports, including its own limits, becomes 
unreachable through a secured factory.
-        final Method setProperty = propertyMethod("setProperty", String.class, 
String.class);
-        final Method getProperty = propertyMethod("getProperty", String.class);
-        final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
-        setProperty.invoke(factory, XPATH_GROUP_LIMIT, "5");
-        assertEquals("5", getProperty.invoke(factory, XPATH_GROUP_LIMIT), "a 
property set on the secured factory must be read back from the delegate");
-    }
-
-    @Test
-    void reportsAnUnknownPropertyLikeTheDelegate() {
-        final Method getProperty = propertyMethod("getProperty", String.class);
-        final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
-        final InvocationTargetException thrown = 
assertThrows(InvocationTargetException.class,
-                () -> getProperty.invoke(factory, "jdk.xml.noSuchProperty"));
-        assertInstanceOf(IllegalArgumentException.class, thrown.getCause(), 
"an unrecognized property must surface the delegate's own rejection");
-    }
 }

Reply via email to