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

ppkarwasz pushed a commit to branch feature/schema-factory-language
in repository https://gitbox.apache.org/repos/asf/commons-xml.git

commit 26638d7f8e3f4153a4dfdb2d7690a27402e0df73
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sun Aug 2 20:30:42 2026 +0200

    Add a schemaLanguage parameter to newSchemaFactory
    
    Mirror SchemaFactory.newInstance(String): the caller picks the schema
    language instead of the method hardcoding W3C XML Schema. The hardening
    is enforced through the LSResourceResolver hook and hardened source
    rewriting rather than any implementation-specific setting, so it holds
    for whichever schema language JAXP locates a factory for.
    
    Assisted-By: Claude Fable 5 <[email protected]>
---
 .../apache/commons/xml/HardeningSchemaFactory.java |  2 +-
 .../java/org/apache/commons/xml/XmlFactories.java  | 13 +++++---
 src/site/markdown/index.md                         |  3 +-
 .../org/apache/commons/xml/AttackTestSupport.java  | 24 +++++++-------
 .../commons/xml/EntityResolverFloorTest.java       |  5 +--
 .../commons/xml/SchemaFactoryLanguageTest.java     | 38 ++++++++++++++++++++++
 .../org/apache/commons/xml/XmlFactoriesTest.java   |  4 +--
 7 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java 
b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
index 537f40e..e76c965 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
@@ -31,7 +31,7 @@
 
 /**
  * Capability-driven hardening wrapper for any {@link SchemaFactory} on the 
classpath, the same recipe for every implementation. It is the entry point 
reached
- * by {@link XmlFactories#newSchemaFactory()}; there is no per-implementation 
branching, no {@code FEATURE_SECURE_PROCESSING} and no limit configuration on 
the
+ * by {@link XmlFactories#newSchemaFactory(String)}; there is no 
per-implementation branching, no {@code FEATURE_SECURE_PROCESSING} and no limit 
configuration on the
  * factory itself.
  *
  * <p>Three layers cooperate:</p>
diff --git a/src/main/java/org/apache/commons/xml/XmlFactories.java 
b/src/main/java/org/apache/commons/xml/XmlFactories.java
index 8d8a62f..31f33cd 100644
--- a/src/main/java/org/apache/commons/xml/XmlFactories.java
+++ b/src/main/java/org/apache/commons/xml/XmlFactories.java
@@ -145,7 +145,7 @@ public static SAXParserFactory newSAXParserFactory() {
     }
 
     /**
-     * Returns a fresh, hardened {@link SchemaFactory} configured for W3C XML 
Schema ({@link XMLConstants#W3C_XML_SCHEMA_NS_URI}).
+     * Returns a fresh, hardened {@link SchemaFactory} for the given schema 
language.
      *
      * <p>Beyond the three universal guarantees on {@link XmlFactories}:</p>
      *
@@ -155,12 +155,17 @@ public static SAXParserFactory newSAXParserFactory() {
      * </ul>
      *
      * <p>The same guarantees apply to {@link javax.xml.validation.Validator} 
and {@link javax.xml.validation.ValidatorHandler} instances produced from the
-     * resulting {@link javax.xml.validation.Schema}.</p>
+     * resulting {@link javax.xml.validation.Schema}. They are enforced 
through the factory's {@link org.w3c.dom.ls.LSResourceResolver} hook and 
hardened
+     * source rewriting rather than any implementation-specific setting, so 
they hold for whichever schema language JAXP locates a factory for.</p>
      *
+     * @param schemaLanguage The schema language, as accepted by {@link 
SchemaFactory#newInstance(String)}; use
+     *                       {@link XMLConstants#W3C_XML_SCHEMA_NS_URI} for 
W3C XML Schema.
      * @return A hardened factory.
+     * @throws IllegalArgumentException if no implementation of the schema 
language is available.
+     * @throws NullPointerException     if {@code schemaLanguage} is {@code 
null}.
      */
-    public static SchemaFactory newSchemaFactory() {
-        return new 
HardeningSchemaFactory(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI));
+    public static SchemaFactory newSchemaFactory(final String schemaLanguage) {
+        return new 
HardeningSchemaFactory(SchemaFactory.newInstance(schemaLanguage));
     }
 
     /**
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 98cc59e..e018cbe 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -121,10 +121,11 @@ NodeList hits = (NodeList) XmlFactories.newXPathFactory()
 **W3C XML Schema validation** via `SchemaFactory`:
 
 ```java
+import javax.xml.XMLConstants;
 import javax.xml.transform.stream.StreamSource;
 import org.apache.commons.xml.XmlFactories;
 
-XmlFactories.newSchemaFactory()
+XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI)
         .newSchema(new StreamSource(xsdStream))
         .newValidator()
         .validate(new StreamSource(inputStream));
diff --git a/src/test/java/org/apache/commons/xml/AttackTestSupport.java 
b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
index 4c80d2e..20f5351 100644
--- a/src/test/java/org/apache/commons/xml/AttackTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
@@ -463,30 +463,30 @@ static void assertSaxParses(final String payload) {
     /**
      * Asserts a hardened Schema compilation throws.
      *
-     * <p>{@link SchemaFactory#newSchema(Source)} via {@link 
XmlFactories#newSchemaFactory()}; only a thrown exception passes.</p>
+     * <p>{@link SchemaFactory#newSchema(Source)} via {@link 
XmlFactories#newSchemaFactory(String)}; only a thrown exception passes.</p>
      */
     static void assertSchemaBlocks(final Source xsd) {
-        assertParseFails(() -> strictSchema(XmlFactories.newSchemaFactory(), 
xsd), "Schema compile", SAXException.class, SecurityException.class);
+        assertParseFails(() -> 
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI), 
xsd), "Schema compile", SAXException.class, SecurityException.class);
     }
 
     /**
      * Asserts a hardened Schema compilation succeeds.
      *
-     * <p>{@link SchemaFactory#newSchema(Source)} via {@link 
XmlFactories#newSchemaFactory()}; positive control for DOCTYPE-only 
payloads.</p>
+     * <p>{@link SchemaFactory#newSchema(Source)} via {@link 
XmlFactories#newSchemaFactory(String)}; positive control for DOCTYPE-only 
payloads.</p>
      */
     static void assertSchemaCompiles(final Source xsd) {
-        assertParseSucceeds(() -> 
strictSchema(XmlFactories.newSchemaFactory(), xsd), "Schema compile");
+        assertParseSucceeds(() -> 
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI), 
xsd), "Schema compile");
     }
 
     /**
      * Asserts a hardened Schema compilation completes without throwing.
      *
-     * <p>{@link SchemaFactory#newSchema(Source)} via {@link 
XmlFactories#newSchemaFactory()}; use this when the hardening contract 
guarantees the compile
+     * <p>{@link SchemaFactory#newSchema(Source)} via {@link 
XmlFactories#newSchemaFactory(String)}; use this when the hardening contract 
guarantees the compile
      * succeeds but never resolves the external resource (for example, {@code 
XERCES_LOAD_EXTERNAL_DTD=false} silently skipping the external subset, with the 
body's
      * undeclared entity reference dropped per XML 1.0 ยง4.1).</p>
      */
     static void assertSchemaDoesNotLeak(final Source xsd) {
-        assertParseSucceeds(() -> 
strictSchema(XmlFactories.newSchemaFactory(), xsd), "Schema compile");
+        assertParseSucceeds(() -> 
strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI), 
xsd), "Schema compile");
     }
 
     /**
@@ -594,36 +594,36 @@ static void assertTransformerTransforms(final String 
payload) {
     /**
      * Asserts a hardened Validator validation throws.
      *
-     * <p>{@link Validator#validate(Source)} on a validator from {@link 
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory()}; only a 
thrown
+     * <p>{@link Validator#validate(Source)} on a validator from {@link 
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory(String)}; 
only a thrown
      * exception passes (the schema is benign; the attack lives in the 
instance document).</p>
      */
     static void assertValidatorBlocks(final String xml) {
         assertParseFails(
-                () -> 
strictValidator(strictSchema(XmlFactories.newSchemaFactory(), 
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
+                () -> 
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
 streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
                 "Validator", SAXException.class, SecurityException.class);
     }
 
     /**
      * Asserts a hardened Validator validation completes without throwing.
      *
-     * <p>{@link Validator#validate(Source)} on a validator from {@link 
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory()}; use this 
when the
+     * <p>{@link Validator#validate(Source)} on a validator from {@link 
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory(String)}; use 
this when the
      * hardening contract guarantees the validate succeeds but never resolves 
the external resource.</p>
      */
     static void assertValidatorDoesNotLeak(final String xml) {
         assertParseSucceeds(
-                () -> 
strictValidator(strictSchema(XmlFactories.newSchemaFactory(), 
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
+                () -> 
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
 streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
                 "Validator");
     }
 
     /**
      * Asserts a hardened Validator validation succeeds.
      *
-     * <p>{@link Validator#validate(Source)} on a validator from {@link 
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory()}; positive 
control
+     * <p>{@link Validator#validate(Source)} on a validator from {@link 
#BENIGN_SCHEMA} compiled via {@link XmlFactories#newSchemaFactory(String)}; 
positive control
      * for DOCTYPE-only payloads.</p>
      */
     static void assertValidatorValidates(final String xml) {
         assertParseSucceeds(
-                () -> 
strictValidator(strictSchema(XmlFactories.newSchemaFactory(), 
streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
+                () -> 
strictValidator(strictSchema(XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI),
 streamSource(BENIGN_SCHEMA))).validate(streamSource(xml)),
                 "Validator");
     }
 
diff --git a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java 
b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
index 42a473f..9ef2d69 100644
--- a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
+++ b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
@@ -27,6 +27,7 @@
 import java.io.StringReader;
 import java.net.URL;
 
+import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.SAXParser;
@@ -323,7 +324,7 @@ private static LSInput lsInput(final String systemId) {
     void schemaResolvesAllowListed() {
         // with-import.xsd references an element defined only in the imported 
included.xsd, so it compiles only if the import is resolved.
         assertParseSucceeds(() -> {
-            final SchemaFactory factory = XmlFactories.newSchemaFactory();
+            final SchemaFactory factory = 
XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
             factory.setResourceResolver(SCHEMA_ALLOW_LIST);
             
factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
         }, "Schema import via caller resolver");
@@ -333,7 +334,7 @@ void schemaResolvesAllowListed() {
     @Tag("schema")
     void schemaDeniesUnlisted() {
         assertParseFails(() -> {
-            final SchemaFactory factory = XmlFactories.newSchemaFactory();
+            final SchemaFactory factory = 
XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
             factory.setResourceResolver((type, namespaceURI, publicId, 
systemId, baseURI) -> null);
             
factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
         }, "Schema import", SAXException.class, SecurityException.class);
diff --git 
a/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java 
b/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java
new file mode 100644
index 0000000..80b50e2
--- /dev/null
+++ b/src/test/java/org/apache/commons/xml/SchemaFactoryLanguageTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.xml;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that {@link XmlFactories#newSchemaFactory(String)} passes the schema 
language through to {@link javax.xml.validation.SchemaFactory#newInstance}.
+ *
+ * <p>The working W3C XML Schema path is exercised by the whole schema suite; 
this test covers only the language-selection contract.</p>
+ */
+@Tag("schema")
+class SchemaFactoryLanguageTest {
+
+    @Test
+    void unknownSchemaLanguageThrows() {
+        assertThrows(IllegalArgumentException.class, () -> 
XmlFactories.newSchemaFactory("urn:example:unknown-schema-language"),
+                "an unsupported schema language should surface 
SchemaFactory.newInstance's IllegalArgumentException");
+    }
+}
diff --git a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java 
b/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
index 8665e1c..e7d133a 100644
--- a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
+++ b/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
@@ -113,8 +113,8 @@ void newXPathFactoryReturnsFreshInstance() throws Exception 
{
 
     @Test
     void newSchemaFactoryReturnsFreshInstance() throws Exception {
-        final SchemaFactory a = XmlFactories.newSchemaFactory();
-        final SchemaFactory b = XmlFactories.newSchemaFactory();
+        final SchemaFactory a = 
XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        final SchemaFactory b = 
XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
         assertNotSame(a, b);
         assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
     }

Reply via email to