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


The following commit(s) were added to refs/heads/main by this push:
     new 9192933  Secure getAssociatedStylesheet, and pin the CI Zulu job to 
JDK 8u152 (#74)
9192933 is described below

commit 91929333d96bf213f97040de63b7c34690fd98f6
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Tue Sep 1 13:38:43 2026 +0200

    Secure getAssociatedStylesheet, and pin the CI Zulu job to JDK 8u152 (#74)
    
    * Pin the CI Zulu job to JDK 8u152
    
    The Zulu job exists to cover the oldest JDK 8 behavior the securing has
    to work against, but 8u201 already carries the 8u162 backport wave. Pin
    it to 8u152, the last release before XSLTC's getAssociatedStylesheet
    began honoring the XMLReader carried by a SAXSource: through 8u152 it
    self-provisions a parser instead, which the newer job cannot exercise.
    
    jdk.xml.overrideDefaultParser arrived in the same wave, so
    OverrideDefaultParserTest now skips where the runtime does not
    recognize the feature rather than failing on it.
    
    Assisted-By: Claude Fable 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
    
    * Secure both untrusted inputs of getAssociatedStylesheet
    
    The PI scan and the href it yields are each derived from the scanned
    document, and each reached an implementation unfiltered.
    
    The scan: Apache Xalan provisions its own parser instead of using the
    one a SAXSource carries (XALANJ-2849), and the JDK's XSLTC did the same
    before 8u162, so on Java 8 the document was scanned by a parser this
    library never configured. Pre-parse to a DOM for those engines, keying
    on Java 8 as a whole since the patch levels are indistinguishable
    through any API. Include a SAXSource that carries the caller's own
    reader: an engine that reaches this path drops that reader anyway, so
    the choice is only between this parse and the engine's own.
    
    The href: XSLTC-lineage engines resolve it during the scan, before the
    factory's URIResolver is installed, and hand back a live Source naming
    the absolutized URI, which compiling would then fetch. Route it through
    the floor like any other content-named reference, so a caller resolver
    opts it in and everything else resolves to empty. Saxon already behaved
    this way, so the engines now agree.
    
    Assisted-By: Claude Fable 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
    
    * Add comment.
    
    ---------
    
    Co-authored-by: Gary Gregory <[email protected]>
---
 .github/workflows/maven.yml                        |  7 +--
 .../xml/secure/SecureTransformerFactory.java       | 60 ++++++++++++++++++---
 .../xml/secure/AssociatedStylesheetTest.java       | 63 +++++++++++++++-------
 .../xml/secure/OverrideDefaultParserTest.java      | 22 ++++++++
 4 files changed, 122 insertions(+), 30 deletions(-)

diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index f231076..15115d9 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -42,8 +42,9 @@ jobs:
     # - Distribution variance, one job each:
     #     * Semeru 8 and 21     OpenJ9's class library is the most divergent 
runtime setup-java offers,
     #                           and the most plausible source of surprises in 
factory lookup and class loading.
-    #     * Zulu                Pinned to an old 8 patch level.
-    #                           Verifies the securing degrades gracefully on a 
runtime predating the later jdk.xml.* backports.
+    #     * Zulu                Pinned to 8u152, the last release before 
XSLTC's getAssociatedStylesheet started honoring the XMLReader
+    #                           carried by a SAXSource (it self-provisioned 
one through 8u152, and honors it from 8u162 on).
+    #                           Also predates the later jdk.xml.* backports, 
so it covers the oldest behavior the securing has to degrade against.
     #     * GraalVM             Runs the suite as a native image via the 
`native-xalan` profile:
     #                           the JAXP providers resolve at build time under 
the closed-world assumption, a different code path from
     #                           the JVM's run-time ServiceLoader lookup. 25 is 
the latest GraalVM release.
@@ -75,7 +76,7 @@ jobs:
             java-version: 21
             distribution: semeru
           - os: ubuntu-latest
-            java-version: 8.0.201
+            java-version: 8.0.152 # Not the latest, see comment above.
             distribution: zulu
           # native-image resolves the JAXP providers at build time under the 
closed-world assumption, a different
           # code path from the JVM's run-time ServiceLoader lookup. 25 is the 
latest GraalVM release.
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 744ba85..f7dfa13 100644
--- a/src/main/java/org/apache/commons/xml/secure/SecureTransformerFactory.java
+++ b/src/main/java/org/apache/commons/xml/secure/SecureTransformerFactory.java
@@ -31,6 +31,7 @@
 import javax.xml.transform.Templates;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.TransformerFactoryConfigurationError;
 import javax.xml.transform.URIResolver;
@@ -63,6 +64,12 @@
  * {@code Transformer.transform(Source, Result)} time.
  * </p>
  * <p>
+ * The {@code href} an {@code xml-stylesheet} processing instruction names is 
content of the document being scanned, so
+ * {@link TransformerFactory#getAssociatedStylesheet(Source, String, String, 
String) getAssociatedStylesheet} treats it as any other content-named reference:
+ * install a {@link URIResolver} resolving that href to compile the stylesheet 
it points at. Without one the returned {@link Source} carries empty content
+ * rather than naming the URI, so compiling it cannot fetch a stylesheet the 
parsed document chose.
+ * </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>
@@ -101,7 +108,8 @@ public final class SecureTransformerFactory {
      * <ul>
      *   <li>A {@link SAXSource} that carries its own {@link XMLReader} is 
trusted as-is: the caller is expected to supply a secure reader (via
      *       {@link SecureSAXParserFactory#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>
+     *       caller sets on a returned {@link XMLFilter}. The exception is 
{@code getAssociatedStylesheet} on an engine that drops the reader (Apache 
Xalan, and
+     *       the JDK's XSLTC on Java 8): there the document is pre-parsed into 
a DOM instead, since the reader would otherwise be replaced by the engine's 
own.</li>
      * </ul>
      *
      * @see org.apache.commons.xml.secure
@@ -188,9 +196,35 @@ private Wrapper(final SAXTransformerFactory delegate, 
final Supplier<Source> emp
         @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).
-            final Source secure = isXalan(delegate) ? 
secureSourceToDom(source) : SecureSAXParserFactory.secure(source, 
overrideDefaultParser());
-            return delegate.getAssociatedStylesheet(secure, media, title, 
charset);
+            // 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.
+         *
+         * <p>XSLTC-lineage engines resolve the href during the scan, before 
they install the factory's {@link URIResolver}, and hand back a live
+         * {@link SAXSource} naming the absolutized URI; compiling it, the one 
documented use of this method, would then fetch it. Saxon already floors the 
href
+         * itself and returns an empty source, so flooring here is also what 
makes the engines agree.</p>
+         *
+         * @param associated The delegate's result; {@code null} when no PI 
matched.
+         * @param base       The system id of the scanned document, the base 
the href was resolved against.
+         * @return The caller resolver's source for an opted-in href, an empty 
source otherwise, or {@code null} when no PI matched.
+         * @throws TransformerConfigurationException if the floor rejects the 
href, which it does when {@value SecureException#THROW_ON_UNRESOLVED} is set.
+         */
+        private Source floorAssociated(final Source associated, final String 
base) throws TransformerConfigurationException {
+            if (associated == null || associated.getSystemId() == null) {
+                // No PI matched, or the engine already floored the href to a 
source that names no URI.
+                return associated;
+            }
+            try {
+                return floor.resolve(associated.getSystemId(), base);
+            } catch (final TransformerException e) {
+                throw new TransformerConfigurationException("Failed to resolve 
the associated stylesheet " + associated.getSystemId(), e);
+            }
         }
 
         @Override
@@ -311,19 +345,22 @@ private TransformerHandler secure(final 
TransformerHandler handler) {
         }
 
         /**
-         * Parses a reader-less source into a DOM through a secure, 
namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link 
DOMSource}
+         * Parses a stream or SAX source into a DOM through a secure, 
namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link 
DOMSource}
          * carrying its system id, so the consumer walks the tree instead of 
provisioning its own reader. Any other source is left to
          * {@link SecureSAXParserFactory#secure(Source, boolean)}.
          *
+         * <p>A {@link SAXSource} carrying the caller's own reader is 
pre-parsed here too, unlike everywhere else in this class: an engine that 
reaches this
+         * method drops that reader anyway, so honoring it is not among the 
options — the choice is only between this parse and the engine's unsecured 
one.</p>
+         *
          * @param source The source to scan for an associated stylesheet.
-         * @return A {@link DOMSource} for a reader-less source, otherwise the 
result of {@link SecureSAXParserFactory#secure(Source, boolean)}.
+         * @return A {@link DOMSource} for a stream or SAX source, otherwise 
the result of {@link SecureSAXParserFactory#secure(Source, boolean)}.
          * @throws TransformerConfigurationException if the source cannot be 
parsed.
          * @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.
          * @throws SecureException Thrown if a (non-Android) factory cannot 
support the secure processing feature {@link 
XMLConstants#FEATURE_SECURE_PROCESSING}.
          */
         private Source secureSourceToDom(final Source source) throws 
TransformerConfigurationException {
-            if (source instanceof StreamSource || source instanceof SAXSource 
&& ((SAXSource) source).getXMLReader() == null) {
+            if (source instanceof StreamSource || source instanceof SAXSource) 
{
                 final InputSource inputSource = 
SAXSource.sourceToInputSource(source);
                 if (inputSource != null) {
                     try {
@@ -365,6 +402,15 @@ public void setURIResolver(final URIResolver resolver) {
 
     private static final MethodHandle MH_newDefaultInstance = 
MethodHandleFactory.findStatic(TransformerFactory.class, "newDefaultInstance");
 
+    /**
+     * {@code true} on Java 8, detected by the absence of {@code 
TransformerFactory.newDefaultInstance()}, which arrived in Java 9.
+     *
+     * <p>The JDK's XSLTC only began honoring the {@link XMLReader} carried by 
a {@link SAXSource} in {@code getAssociatedStylesheet} in 8u162; through 8u152 
it
+     * provisions its own parser, exactly as Apache Xalan does. Java 8 as a 
whole is used as the boundary rather than the patch level: the two are
+     * indistinguishable through any API, and a runtime that old has already 
chosen correctness of configuration over the cost of a DOM pre-parse.</p>
+     */
+    private static final boolean JAVA_8 = MH_newDefaultInstance == null;
+
     /**
      * Returns a new, secure {@link TransformerFactory} of the system-default 
implementation.
      * <p>
diff --git 
a/src/test/java/org/apache/commons/xml/secure/AssociatedStylesheetTest.java 
b/src/test/java/org/apache/commons/xml/secure/AssociatedStylesheetTest.java
index 25b98f3..7f3f070 100644
--- a/src/test/java/org/apache/commons/xml/secure/AssociatedStylesheetTest.java
+++ b/src/test/java/org/apache/commons/xml/secure/AssociatedStylesheetTest.java
@@ -18,6 +18,7 @@
 package org.apache.commons.xml.secure;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -25,31 +26,27 @@
 import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stream.StreamSource;
 
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
+import org.xml.sax.InputSource;
 
 /**
- * Checks that {@code getAssociatedStylesheet} scans for {@code 
xml-stylesheet} PIs without fetching an external DTD declared in the document 
prolog.
+ * Checks the two untrusted inputs {@code getAssociatedStylesheet} handles: 
the document it scans, and the href the {@code xml-stylesheet} PI names.
  *
- * <p>The PI scan parses the prolog, where a {@code DOCTYPE} with an external 
subset is processed before the root element. On Apache Xalan the scan runs on a
- * reader the engine provisions itself, ignoring a secure reader passed in a 
{@link SAXSource} (XALANJ-2849); the wrapper works around
- * that by handing Xalan a {@code DOMSource} it pre-parsed through a secure 
{@code DocumentBuilder}. The JDK's XSLTC honors the secure reader directly. 
Either
- * way the external DTD resolves to empty instead of being fetched. Tagged 
{@code trax}, so it runs on the stock JDK, Apache Xalan, Saxon, and the Android
- * runtime.</p>
+ * <p>The scan parses the prolog, where a {@code DOCTYPE} with an external 
subset is processed before the root element. Apache Xalan runs it on a reader 
the
+ * engine provisions itself, ignoring one passed in a {@link SAXSource} 
(XALANJ-2849), and the JDK's XSLTC did the same before 8u162; the wrapper hands 
those a
+ * {@code DOMSource} it pre-parsed through a secure {@code DocumentBuilder}, 
so the external DTD resolves to empty instead of being fetched.</p>
+ *
+ * <p>The href is attacker-controlled content, so the wrapper routes it 
through the same floor as any other content-named reference: unresolved by 
default,
+ * fetched only where a caller's {@code URIResolver} opts it in. Compiling the 
returned Source is the one documented use of this method, so returning it live
+ * would be handing back a URI the document chose. Tagged {@code trax}, so it 
runs on the stock JDK, Apache Xalan, Saxon, and the Android runtime.</p>
  */
 @Tag("trax")
 class AssociatedStylesheetTest {
 
-    /** The PI was found (non-null); where the engine exposes a system id, it 
points at the declared stylesheet. */
-    private static void assertAssociatedStylesheet(final Source associated) {
-        assertNotNull(associated, "expected the associated stylesheet PI to be 
found");
-        if (associated.getSystemId() != null) {
-            assertTrue(associated.getSystemId().endsWith("included.xsl"), 
"unexpected associated stylesheet: " + associated.getSystemId());
-        }
-    }
-
     private static TransformerFactory secureFactory() {
         final TransformerFactory factory = 
SecureTransformerFactory.newInstance();
         factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
@@ -58,20 +55,46 @@ private static TransformerFactory secureFactory() {
 
     @Test
     void secureGetAssociatedStylesheetIgnoresExternalDtd() throws 
TransformerConfigurationException {
-        // The prolog declares an unreachable external DTD; the secure parse 
resolves it to empty rather than fetching it, so the PI scan completes and finds
-        // the stylesheet instead of throwing on a fetch. (The returned 
Source's shape is engine-specific: XSLTC and Xalan point it at included.xsl, 
while Saxon
-        // resolves the href through its own floor and returns an empty 
source; both mean the scan ran without fetching the DTD.)
+        // The prolog declares an unreachable external DTD; the secure scan 
resolves it to empty rather than fetching it, so the lookup completes instead of
+        // throwing. The PI is found, and its href is floored, so what comes 
back names no URI.
         final Source associated = secureFactory()
                 
.getAssociatedStylesheet(AttackTestSupport.resourceSource("associated-stylesheet.xml"),
 null, null, null);
-        assertAssociatedStylesheet(associated);
+        assertNotNull(associated, "expected the associated stylesheet PI to be 
found");
+        assertNull(associated.getSystemId(), "the PI href must not come back 
as a live URI: " + associated.getSystemId());
+    }
+
+    @Test
+    void secureGetAssociatedStylesheetIgnoresExternalDtdWithCallerReader() 
throws Exception {
+        // Same scan through a SAXSource carrying a caller-supplied secure 
reader. Xalan and Java 8 XSLTC drop that reader, so this shape has to be 
pre-parsed
+        // like the reader-less one rather than passed through.
+        final SAXSource source = new 
SAXSource(SecureSAXParserFactory.newInstance().newSAXParser().getXMLReader(),
+                new 
InputSource(AttackTestSupport.resourceUrl("associated-stylesheet.xml").toString()));
+        final Source associated = 
secureFactory().getAssociatedStylesheet(source, null, null, null);
+        assertNotNull(associated, "expected the associated stylesheet PI to be 
found");
+        assertNull(associated.getSystemId(), "the PI href must not come back 
as a live URI: " + associated.getSystemId());
+    }
+
+    @Test
+    void secureGetAssociatedStylesheetOptsInThroughResolver() throws 
TransformerConfigurationException {
+        // A caller resolver is consulted for the href exactly as for any 
other reference. What comes back names the opted-in stylesheet rather than 
nothing,
+        // which is what separates an opt-in from the floored default; the 
floor still re-parses it through a secure reader, so the shape is its own.
+        final StreamSource opted = new 
StreamSource(AttackTestSupport.resourceUrl("included.xsl").toString());
+        final TransformerFactory factory = secureFactory();
+        factory.setURIResolver((href, base) -> href != null && 
href.endsWith("included.xsl") ? opted : null);
+        final Source associated = factory
+                
.getAssociatedStylesheet(AttackTestSupport.resourceSource("associated-stylesheet-plain.xml"),
 null, null, null);
+        assertNotNull(associated, "expected the associated stylesheet PI to be 
found");
+        assertNotNull(associated.getSystemId(), "an opted-in href must resolve 
to the caller's stylesheet, not to the empty default");
+        assertTrue(associated.getSystemId().endsWith("included.xsl"), 
"unexpected associated stylesheet: " + associated.getSystemId());
     }
 
     @Test
     void secureGetAssociatedStylesheetReturnsStylesheet() throws 
TransformerConfigurationException {
-        // Positive control: a plain document with no DOCTYPE resolves its 
xml-stylesheet PI end to end.
+        // Positive control: a plain document with no DOCTYPE is scanned end 
to end and its PI found, with the href floored.
         final Source associated = secureFactory()
                 
.getAssociatedStylesheet(AttackTestSupport.resourceSource("associated-stylesheet-plain.xml"),
 null, null, null);
-        assertAssociatedStylesheet(associated);
+        assertNotNull(associated, "expected the associated stylesheet PI to be 
found");
+        assertNull(associated.getSystemId(), "the PI href must not come back 
as a live URI: " + associated.getSystemId());
     }
 
     @Test
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 1ada79b..d5b640e 100644
--- a/src/test/java/org/apache/commons/xml/secure/OverrideDefaultParserTest.java
+++ b/src/test/java/org/apache/commons/xml/secure/OverrideDefaultParserTest.java
@@ -22,6 +22,7 @@
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import java.io.StringWriter;
 
@@ -54,6 +55,23 @@ class OverrideDefaultParserTest {
     /** Package prefix of the JDK's built-in parsers, the family a {@code 
false} feature value pins. */
     private static final String JDK_INTERNAL_PREFIX = 
"com.sun.org.apache.xerces.internal.";
 
+    /** {@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();
+
+    private static boolean probeFeature() {
+        try {
+            TransformerFactory.newInstance().setFeature(FEATURE, true);
+            return true;
+        } catch (final Exception e) {
+            return false;
+        }
+    }
+
+    /** 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();
@@ -73,6 +91,7 @@ private static boolean xercesOnClasspath() {
     @Test
     void schemaFactoryReadsFeatureAtCreation() throws Exception {
         assumeFalse(AttackTestSupport.IS_ANDROID);
+        assumeFeatureSupported();
         final SchemaFactory factory = SecureSchemaFactory.newDefaultInstance();
         assertFalse(factory.getFeature(FEATURE));
         assertFalse(((SecureSchema) 
factory.newSchema(AttackTestSupport.streamSource(AttackTestSupport.BENIGN_SCHEMA))).overrideDefaultParser);
@@ -97,6 +116,7 @@ void secureReaderFollowsFlag() throws Exception {
     @Test
     void transformerFactoryReadsFeatureAtCreation() throws Exception {
         assumeFalse(AttackTestSupport.IS_ANDROID);
+        assumeFeatureSupported();
         final TransformerFactory factory = 
SecureTransformerFactory.newDefaultInstance();
         assertFalse(factory.getFeature(FEATURE));
         assertFalse(((SecureTemplates) 
factory.newTemplates(AttackTestSupport.streamSource(AttackTestSupport.xsltBody("probe")))).overrideDefaultParser);
@@ -110,6 +130,7 @@ void transformerFactoryReadsFeatureAtCreation() throws 
Exception {
     @DisabledInNativeImage
     void transformSucceedsUnderBothParserFamilies() throws Exception {
         assumeFalse(AttackTestSupport.IS_ANDROID);
+        assumeFeatureSupported();
         final TransformerFactory factory = 
SecureTransformerFactory.newDefaultInstance();
         // Feature false (the JDK's default): stylesheet and source parse 
through the pinned platform parser.
         assertTrue(transform(factory, "pinned").contains("pinned"));
@@ -121,6 +142,7 @@ void transformSucceedsUnderBothParserFamilies() throws 
Exception {
     @Test
     void xPathFactoryReadsFeatureAtCreation() throws Exception {
         assumeFalse(AttackTestSupport.IS_ANDROID);
+        assumeFeatureSupported();
         final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
         assertFalse(factory.getFeature(FEATURE));
         assertFalse(((SecureXPath) factory.newXPath()).overrideDefaultParser);

Reply via email to