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 d1d360b66f31cc503b960c3d9d04125b16c3a8a6 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Thu Apr 23 00:06:16 2026 +0200 Initial implementation Introduce the basic project structure (Maven POM, README, license and notice files, .gitignore). No source code yet. Assisted-By: Claude Opus 4.7 (1M context) <[email protected]> --- .gitignore | 22 +++++---- NOTICE.txt | 5 ++ README.md | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++- pom.xml | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 91824e5..b7187a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,25 @@ -target/ +# Build output +target pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties - site-content -/.classpath -/.project -/.settings/ -# Ignore IntelliJ files -/.idea/ -*.iml +# Eclipse files +.project +.classpath +.settings/ /bin/ -# Ignore Visual Studio code files +# Visual Studio code files /.vscode/ -# NetBeans files +# IntelliJ IDEA files +.idea/ +*.iml + +# NetBeans files files nb-configuration.xml nbactions.xml diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..23f38ef --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Commons XML Factory +Copyright 2026 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (https://www.apache.org/). diff --git a/README.md b/README.md index 0082ff5..9bcec33 100644 --- a/README.md +++ b/README.md @@ -14,4 +14,147 @@ See the License for the specific language governing permissions and limitations under the License. --> -More to come soon... + +# Apache Commons XML Factory + +> [!WARNING] +> This is a draft of a new [Apache Commons](https://commons.apache.org) project base on +> [this proposal](https://lists.apache.org/thread/b2tjc15vjkgsrxxkc8phlnt6801hx4xz). +> +> It is **not** an official Apache Commons library yet. + +Secure-by-default JAXP factory creation for Java. +A single method call returns a hardened JAXP factory that can be used to **safely** parse XML files. + +## Why + +Any Java library that parses XML has to harden JAXP before handing a factory to user code, and every library ends up +copy-pasting the same hardening snippet. The snippet is fragile: the attributes and features needed to harden a factory +are not standardised, each JAXP implementation exposes a slightly different set, and setting an unknown one throws an +exception that callers routinely swallow. Writing this block correctly for every implementation is real work, and +duplicating it across projects means every project owns the maintenance burden on its own. + +Defaults are also uneven. The stock JDK SAX and DOM parsers already prevent external entity resolution through +`FEATURE_SECURE_PROCESSING`, and JAXP 1.5 conformant implementations ship reasonable defaults for most attacks. Others, +such as standalone Xerces, Woodstox, or Saxon's TrAX, need further configuration before they reach the same baseline. A +library author has no control over which implementation is on the classpath at runtime, so the effective security +posture of their code depends on a deployment decision made elsewhere. + +This library provides that baseline. Each `XmlFactories` call returns a fresh factory hardened by a provider-specific +SPI, so the returned object behaves the same way security-wise regardless of which JAXP implementation resolved. +Security becomes a property of the call, not of the classpath, and there is one place to update when a new hardening +setting becomes available or a default changes. + +## Usage + +Add the library to your build: + +```xml +<dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-xml-factory</artifactId> + <version>0.1.0</version> +</dependency> +``` + +Every method on `XmlFactories` returns a fresh, hardened factory. Pick the one that matches the API you already use; no +other configuration is required. On hardened factories any attempt to resolve an external resource (DTD, entity, schema, +stylesheet) is blocked, and DOCTYPE input is rejected wherever the underlying implementation allows it. + +### Supported implementations + +Out of the box the library recognises the stock JDK JAXP implementations, Apache Xerces 2.x, Woodstox, and Saxon-HE. If +a factory resolves to an implementation not covered by any bundled or registered provider, every `XmlFactories` method +throws `UnsupportedXmlImplementationException`. + +Support for additional implementations can be plugged in by publishing an +`org.apache.commons.xml.factory.spi.XmlProvider` through the standard Java `ServiceLoader` mechanism. Bundled providers +always take precedence, so a third-party provider cannot hijack hardening for a factory class this library already +supports. + +**DOM parsing** via `DocumentBuilderFactory`: + +```java +import org.w3c.dom.Document; +import org.apache.commons.xml.factory.XmlFactories; + +Document doc = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(inputStream); +``` + +**SAX parsing** via `SAXParserFactory`: + +```java +import org.apache.commons.xml.factory.XmlFactories; + +XmlFactories.newSAXParserFactory().newSAXParser().parse(inputStream, myDefaultHandler); +``` + +**Streaming (StAX) parsing** via `XMLInputFactory`: + +```java +import javax.xml.stream.XMLStreamReader; +import org.apache.commons.xml.factory.XmlFactories; + +XMLStreamReader reader = XmlFactories.newXMLInputFactory().createXMLStreamReader(inputStream); +``` + +**XSLT transforms** via `TransformerFactory`: + +```java +import javax.xml.transform.stream.StreamSource; +import javax.xml.transform.stream.StreamResult; +import org.apache.commons.xml.factory.XmlFactories; + +XmlFactories.newTransformerFactory() + .newTransformer(new StreamSource(stylesheet)) + .transform(new StreamSource(inputStream), new StreamResult(outputStream)); +``` + +**XPath queries** via `XPathFactory`: + +```java +import javax.xml.xpath.XPathConstants; +import org.w3c.dom.NodeList; +import org.apache.commons.xml.factory.XmlFactories; + +NodeList hits = (NodeList) XmlFactories.newXPathFactory() + .newXPath() + .evaluate("//item", doc, XPathConstants.NODESET); +``` + +**W3C XML Schema validation** via `SchemaFactory`: + +```java +import javax.xml.transform.stream.StreamSource; +import org.apache.commons.xml.factory.XmlFactories; + +XmlFactories.newSchemaFactory() + .newSchema(new StreamSource(xsdStream)) + .newValidator() + .validate(new StreamSource(inputStream)); +``` + +### Stylesheets and schemas + +The hardening applies to documents parsed through the returned factory. Stylesheets given to +`TransformerFactory.newTransformer(Source)` and schemas given to `SchemaFactory.newSchema(Source)` are read by a parser +the implementation picks internally, and that parser may not be hardened (Saxon's TrAX is one such case, see Building +below). Treat stylesheets and schemas as trusted input, or pre-parse them through a hardened `XmlFactories` parser and +pass the result as a `DOMSource` or `SAXSource`. + +### Caching and thread-safety + +There is no caching or pooling inside `XmlFactories`; callers on a hot path are responsible for their own caching. The +returned factories inherit the thread-safety properties of the underlying JAXP implementation, which in practice means +they are not thread-safe. Create a new factory per thread or synchronise externally. + +## Building + +Building requires a Java JDK and [Apache Maven](https://maven.apache.org/). +The required Java version is found in the `pom.xml` as the `maven.compiler.source` property. + +From a command shell, run `mvn` without arguments to invoke the default Maven goal to run all tests and checks + +## Licensing + +Licensed under the Apache License, Version 2.0. See `LICENSE.txt` and `NOTICE.txt`. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6264ec5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.commons</groupId> + <artifactId>commons-parent</artifactId> + <version>98</version> + </parent> + <artifactId>commons-xml-factory</artifactId> + <version>0.1.0-SNAPSHOT</version> + <name>Apache Commons XML Factory</name> + <url>https://commons.apache.org/proper/commons-xml-factory/</url> + <inceptionYear>2026</inceptionYear> + <description>Apache Commons XML Factory provides secure-by-default JAXP factory creation, abstracting over + implementation-specific XXE hardening differences between the stock JDK and external JAXP implementations + (Apache Xerces, Woodstox, Saxon-HE).</description> + + <properties> + <!-- Release-related properties --> + <commons.release.version>0.1.0</commons.release.version> + <commons.release.desc>(Java 8+)</commons.release.desc> + <commons.rc.version>RC1</commons.rc.version> + <!-- Not relevant for first release: reenable after 0.1.0 and reenable japicmp + <commons.bc.version>0.1.0</commons.bc.version> --> + <japicmp.skip>true</japicmp.skip> + <commons.release.next>0.1.1</commons.release.next> + <commons.componentid>xml-factory</commons.componentid> + <commons.packageId>xml.factory</commons.packageId> + <project.build.outputTimestamp>2026-04-22T00:00:00Z</project.build.outputTimestamp> + + <!-- JIRA coordinates (to be assigned) --> + <commons.jira.id>COMMONSXMLFACTORY</commons.jira.id> + <commons.jira.pid>12300000</commons.jira.pid> + + <!-- Java baseline --> + <maven.compiler.source>1.8</maven.compiler.source> + <maven.compiler.target>1.8</maven.compiler.target> + + <!-- Optional JAXP implementations used by bundled providers. --> + <commons.xerces.version>2.12.2</commons.xerces.version> + <commons.woodstox.version>7.1.1</commons.woodstox.version> + <commons.saxon.version>12.9</commons.saxon.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <defaultGoal>clean verify</defaultGoal> + <plugins> + + <!-- + Run the test suite four times, each with exactly one JAXP implementation on the classpath: + + - test-stockjdk: stock JDK factories only + - test-xerces: stock JDK + external Apache Xerces + - test-woodstox: stock JDK + Woodstox (StAX) + - test-saxon: stock JDK + Saxon-HE (TrAX, XPath) + --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <executions> + <execution> + <id>default-test</id> + <phase>none</phase> + </execution> + <execution> + <id>test-stockjdk</id> + <goals><goal>test</goal></goals> + <configuration> + <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> + <dependency> + <groupId>xerces</groupId> + <artifactId>xercesImpl</artifactId> + <version>${commons.xerces.version}</version> + </dependency> + </additionalClassPathDependencies> + </configuration> + </execution> + <execution> + <id>test-woodstox</id> + <goals><goal>test</goal></goals> + <configuration> + <reportsDirectory>${project.build.directory}/surefire-reports/woodstox</reportsDirectory> + <additionalClassPathDependencies> + <dependency> + <groupId>com.fasterxml.woodstox</groupId> + <artifactId>woodstox-core</artifactId> + <version>${commons.woodstox.version}</version> + </dependency> + </additionalClassPathDependencies> + </configuration> + </execution> + <execution> + <id>test-saxon</id> + <goals><goal>test</goal></goals> + <configuration> + <reportsDirectory>${project.build.directory}/surefire-reports/saxon</reportsDirectory> + <additionalClassPathDependencies> + <dependency> + <groupId>net.sf.saxon</groupId> + <artifactId>Saxon-HE</artifactId> + <version>${commons.saxon.version}</version> + </dependency> + </additionalClassPathDependencies> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.moditect</groupId> + <artifactId>moditect-maven-plugin</artifactId> + <executions> + <execution> + <id>add-module-infos</id> + <configuration> + <module> + <moduleInfo> + <exports> + !*.internal; + *; + </exports> + </moduleInfo> + </module> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project>
