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

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

commit e9c150c2043e2fa9f743e3448f368fb3df4d718c
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Thu Apr 23 23:04:40 2026 +0200

    feat: add provider for Xerces
---
 pom.xml                                            |  10 +-
 .../xml/factory/internal/ProviderRegistry.java     |   3 +-
 .../xml/factory/internal/XercesProvider.java       | 214 +++++++++++++++++++++
 3 files changed, 221 insertions(+), 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5b6f678..55eb204 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,27 +94,27 @@
               
<reportsDirectory>${project.build.directory}/surefire-reports/stockjdk</reportsDirectory>
             </configuration>
           </execution>
-          <!--
           <execution>
             <id>test-xerces</id>
             <goals><goal>test</goal></goals>
             <configuration>
               
<reportsDirectory>${project.build.directory}/surefire-reports/xerces</reportsDirectory>
-              <additionalClassPathDependencies>
+              <additionalClasspathDependencies>
                 <dependency>
                   <groupId>xerces</groupId>
                   <artifactId>xercesImpl</artifactId>
                   <version>${commons.xerces.version}</version>
                 </dependency>
-              </additionalClassPathDependencies>
+              </additionalClasspathDependencies>
             </configuration>
           </execution>
+          <!--
           <execution>
             <id>test-woodstox</id>
             <goals><goal>test</goal></goals>
             <configuration>
               
<reportsDirectory>${project.build.directory}/surefire-reports/woodstox</reportsDirectory>
-              <additionalClassPathDependencies>
+              <additionalClasspathDependencies>
                 <dependency>
                   <groupId>com.fasterxml.woodstox</groupId>
                   <artifactId>woodstox-core</artifactId>
@@ -128,7 +128,7 @@
             <goals><goal>test</goal></goals>
             <configuration>
               
<reportsDirectory>${project.build.directory}/surefire-reports/saxon</reportsDirectory>
-              <additionalClassPathDependencies>
+              <additionalClasspathDependencies>
                 <dependency>
                   <groupId>net.sf.saxon</groupId>
                   <artifactId>Saxon-HE</artifactId>
diff --git 
a/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java 
b/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java
index 954bb68..636eaaf 100644
--- 
a/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java
+++ 
b/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java
@@ -17,6 +17,7 @@
 package org.apache.commons.xml.factory.internal;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -97,7 +98,7 @@ private XmlProvider lookup(final Class<?> factoryClass) {
     }
 
     private static List<XmlProvider> bundledProviders() {
-        return Collections.singletonList(new StockJdkProvider());
+        return Arrays.asList(new StockJdkProvider(), new XercesProvider());
     }
 
     private static Iterable<XmlProvider> serviceLoaderProviders() {
diff --git 
a/src/main/java/org/apache/commons/xml/factory/internal/XercesProvider.java 
b/src/main/java/org/apache/commons/xml/factory/internal/XercesProvider.java
new file mode 100644
index 0000000..bcb8e97
--- /dev/null
+++ b/src/main/java/org/apache/commons/xml/factory/internal/XercesProvider.java
@@ -0,0 +1,214 @@
+/*
+ * 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.factory.internal;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Source;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import javax.xml.validation.ValidatorHandler;
+
+import org.apache.commons.xml.factory.spi.XmlProvider;
+import org.w3c.dom.ls.LSInput;
+import org.w3c.dom.ls.LSResourceResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+/**
+ * {@link XmlProvider} for the external Apache Xerces distribution (the {@code 
xerces:xercesImpl} artifact).
+ *
+ * <p>Factory classes live in the {@code org.apache.xerces.*} package.</p>
+ *
+ * <p>External Xerces does not ship a {@code TransformerFactory}, {@code 
XMLInputFactory} or {@code XPathFactory}; this provider therefore only 
overrides the
+ * DOM, SAX and Schema {@code configure} methods.</p>
+ *
+ * <p>For {@link SchemaFactory}, Xerces does not implement the JAXP 1.5 {@code 
ACCESS_EXTERNAL_*} properties and does not propagate SchemaFactory-level
+ * hardening (including {@code disallow-doctype-decl} and the resource 
resolver) to {@link Validator} / {@link ValidatorHandler} instances produced by 
a
+ * compiled {@link Schema}. {@link #configure(SchemaFactory)} installs a 
deny-all {@link LSResourceResolver} on the inner factory (which closes the
+ * {@code xs:import}/{@code xs:include}/{@code xs:redefine} vectors at 
schema-compilation time) and returns a delegating wrapper whose
+ * {@link Schema#newValidator()} and {@link Schema#newValidatorHandler()} 
re-install the factory's current resolver on every product (which closes the
+ * DOCTYPE SYSTEM vector inside the instance document).</p>
+ *
+ * <p>Must be declared {@code public} so {@link java.util.ServiceLoader} can 
load it from {@code META-INF/services/}.</p>
+ */
+public final class XercesProvider extends AbstractXmlProvider {
+
+    /**
+     * Refuses every resource-resolution request with a {@link 
SecurityException}. Stateless, shared singleton.
+     */
+    private static final class DenyAllResourceResolver implements 
LSResourceResolver {
+
+        static final DenyAllResourceResolver INSTANCE = new 
DenyAllResourceResolver();
+
+        private DenyAllResourceResolver() {
+        }
+
+        @Override
+        public LSInput resolveResource(final String type, final String 
namespaceURI, final String publicId, final String systemId, final String 
baseURI) {
+            throw new SecurityException(String.format("External %s resolution 
forbidden by hardening: namespace=%s, publicId=%s, systemId=%s, baseURI=%s",
+                    type, namespaceURI, publicId, systemId, baseURI));
+        }
+    }
+
+    /**
+     * Delegating {@link Schema} that applies {@link 
XMLConstants#FEATURE_SECURE_PROCESSING FEATURE_SECURE_PROCESSING} and 
re-installs the resource resolver
+     * captured at schema-creation time on every {@link Validator} / {@link 
ValidatorHandler} it produces.
+     *
+     * <p>Xerces honours {@code setResourceResolver} on validators, so any 
DOCTYPE SYSTEM or external-entity reference in the instance document is routed
+     * through it. With {@link DenyAllResourceResolver} in place that means 
the fetch throws {@link SecurityException} before any I/O.</p>
+     */
+    private static final class HardeningSchema extends Schema {
+
+        private final Schema delegate;
+        private final LSResourceResolver resolver;
+
+        HardeningSchema(final Schema delegate, final LSResourceResolver 
resolver) {
+            this.delegate = delegate;
+            this.resolver = resolver;
+        }
+
+        @Override
+        public Validator newValidator() {
+            final Validator validator = delegate.newValidator();
+            setFeature(validator, XMLConstants.FEATURE_SECURE_PROCESSING, 
true);
+            validator.setResourceResolver(resolver);
+            return validator;
+        }
+
+        @Override
+        public ValidatorHandler newValidatorHandler() {
+            final ValidatorHandler handler = delegate.newValidatorHandler();
+            setFeature(handler, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+            handler.setResourceResolver(resolver);
+            return handler;
+        }
+    }
+
+    /**
+     * Delegating {@link SchemaFactory} whose {@code newSchema(...)} methods 
capture the current resource resolver and wrap the result in
+     * {@link HardeningSchema}. The non-abstract {@code newSchema(Source)} / 
{@code newSchema(URL)} / {@code newSchema(File)} overloads route through the
+     * abstract {@code newSchema(Source[])} via the default implementations in 
{@link SchemaFactory} and therefore also produce a {@link HardeningSchema}.
+     */
+    private static final class HardeningSchemaFactory extends SchemaFactory {
+
+        private final SchemaFactory delegate;
+
+        HardeningSchemaFactory(final SchemaFactory delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public ErrorHandler getErrorHandler() {
+            return delegate.getErrorHandler();
+        }
+
+        @Override
+        public boolean getFeature(final String name) throws 
SAXNotRecognizedException, SAXNotSupportedException {
+            return delegate.getFeature(name);
+        }
+
+        @Override
+        public Object getProperty(final String name) throws 
SAXNotRecognizedException, SAXNotSupportedException {
+            return delegate.getProperty(name);
+        }
+
+        @Override
+        public LSResourceResolver getResourceResolver() {
+            return delegate.getResourceResolver();
+        }
+
+        @Override
+        public boolean isSchemaLanguageSupported(final String schemaLanguage) {
+            return delegate.isSchemaLanguageSupported(schemaLanguage);
+        }
+
+        @Override
+        public Schema newSchema() throws SAXException {
+            return new HardeningSchema(delegate.newSchema(), 
delegate.getResourceResolver());
+        }
+
+        @Override
+        public Schema newSchema(final Source[] schemas) throws SAXException {
+            return new HardeningSchema(delegate.newSchema(schemas), 
delegate.getResourceResolver());
+        }
+
+        @Override
+        public void setErrorHandler(final ErrorHandler errorHandler) {
+            delegate.setErrorHandler(errorHandler);
+        }
+
+        @Override
+        public void setFeature(final String name, final boolean value) throws 
SAXNotRecognizedException, SAXNotSupportedException {
+            delegate.setFeature(name, value);
+        }
+
+        @Override
+        public void setProperty(final String name, final Object object) throws 
SAXNotRecognizedException, SAXNotSupportedException {
+            delegate.setProperty(name, object);
+        }
+
+        @Override
+        public void setResourceResolver(final LSResourceResolver 
resourceResolver) {
+            delegate.setResourceResolver(resourceResolver);
+        }
+    }
+
+    private static final String FEATURE_DISALLOW_DOCTYPE = 
"http://apache.org/xml/features/disallow-doctype-decl";;
+
+    /**
+     * Default constructor; invoked by {@link java.util.ServiceLoader} and the 
registry.
+     */
+    public XercesProvider() {
+        super("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl",
+                "org.apache.xerces.jaxp.SAXParserFactoryImpl",
+                "org.apache.xerces.jaxp.validation.XMLSchemaFactory");
+    }
+
+    @Override
+    public DocumentBuilderFactory configure(final DocumentBuilderFactory 
factory) {
+        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        setFeature(factory, FEATURE_DISALLOW_DOCTYPE, true);
+        factory.setXIncludeAware(false);
+        factory.setValidating(false);
+        return factory;
+    }
+
+    @Override
+    public SAXParserFactory configure(final SAXParserFactory factory) {
+        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        setFeature(factory, FEATURE_DISALLOW_DOCTYPE, true);
+        factory.setXIncludeAware(false);
+        factory.setValidating(false);
+        return factory;
+    }
+
+    @Override
+    public SchemaFactory configure(final SchemaFactory factory) {
+        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        // Rejects DOCTYPE in the schema document itself; Xerces does not 
propagate this feature to Validators, so we do not bother setting it on them.
+        setFeature(factory, FEATURE_DISALLOW_DOCTYPE, true);
+        // Blocks xs:import/xs:include/xs:redefine resolution during schema 
compilation. The same resolver is captured at schema-creation time and 
re-installed
+        // on every Validator/ValidatorHandler produced from the resulting 
Schema (see HardeningSchema), because Xerces does not propagate it 
automatically.
+        factory.setResourceResolver(DenyAllResourceResolver.INSTANCE);
+        return new HardeningSchemaFactory(factory);
+    }
+}

Reply via email to