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

THausherr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 2b9e2dbea1 Harden XML processing through Apache Commons Secure XML 
(#3261)
2b9e2dbea1 is described below

commit 2b9e2dbea121996168d736e093b04d8d29195f6c
Author: Gary Gregory <[email protected]>
AuthorDate: Sun Sep 27 06:59:00 2026 -0400

    Harden XML processing through Apache Commons Secure XML (#3261)
---
 .gitignore                                         |   4 +
 .../test/java/org/apache/tika/bundle/BundleIT.java |   2 +
 tika-bundles/tika-bundle-standard/test-bundles.xml |   1 +
 tika-core/pom.xml                                  |   4 +
 .../java/org/apache/tika/mime/MimeTypesReader.java |  11 +--
 .../java/org/apache/tika/utils/XMLReaderUtils.java | 103 +++------------------
 .../apache/tika/sax/OfflineContentHandlerTest.java |   4 +-
 .../org/apache/tika/utils/XMLReaderUtilsTest.java  |  84 +++++++++++++++++
 tika-parent/pom.xml                                |   5 +
 tika-parsers/tika-parsers-ml/tika-vlm/pom.xml      |   4 +
 .../parser/vlm/MarkdownToXHTMLEmitterTest.java     |   4 +-
 .../apache/tika/parser/TestXMLEntityExpansion.java |   5 +-
 .../java/org/apache/tika/parser/XMLTestBase.java   |   5 +-
 .../tika/async/cli/TikaConfigAsyncWriter.java      |  11 +--
 .../tika/async/cli/TikaConfigAsyncWriterTest.java  |  34 +++++++
 15 files changed, 163 insertions(+), 118 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5033761d95..00c9477de0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target
 dependency-reduced-pom.xml
 .idea
 .classpath
+.checkstyle
 .project
 .settings
 *.iml
@@ -25,3 +26,6 @@ tika-grpc/ignite
 # Python bytecode (e.g. from .skills scripts)
 __pycache__/
 *.pyc
+
+_remote.repositories
+/.local_m2_repo/
diff --git 
a/tika-bundles/tika-bundle-standard/src/test/java/org/apache/tika/bundle/BundleIT.java
 
b/tika-bundles/tika-bundle-standard/src/test/java/org/apache/tika/bundle/BundleIT.java
index 76123b9b24..baf5fb4fdc 100644
--- 
a/tika-bundles/tika-bundle-standard/src/test/java/org/apache/tika/bundle/BundleIT.java
+++ 
b/tika-bundles/tika-bundle-standard/src/test/java/org/apache/tika/bundle/BundleIT.java
@@ -88,6 +88,7 @@ public class BundleIT {
         // before either can resolve. tika-core also imports the org.commonmark
         // packages (Markdown serialization), so those bundles must be present 
too.
         Bundle commonsIo = install("commons-io.jar");
+        Bundle commonsSecureXml = install("commons-secure-xml.jar");
         Bundle commonmark = install("commonmark.jar");
         Bundle commonmarkTables = install("commonmark-ext-gfm-tables.jar");
         Bundle commonmarkStrikethrough = 
install("commonmark-ext-gfm-strikethrough.jar");
@@ -95,6 +96,7 @@ public class BundleIT {
         Bundle tikaBundle = install("tika-bundle-standard.jar");
 
         commonsIo.start();
+        commonsSecureXml.start();
         commonmark.start();
         commonmarkTables.start();
         commonmarkStrikethrough.start();
diff --git a/tika-bundles/tika-bundle-standard/test-bundles.xml 
b/tika-bundles/tika-bundle-standard/test-bundles.xml
index 821846ec9c..4335b28a58 100644
--- a/tika-bundles/tika-bundle-standard/test-bundles.xml
+++ b/tika-bundles/tika-bundle-standard/test-bundles.xml
@@ -30,6 +30,7 @@
         <include>org.apache.tika:tika-core</include>
         <include>org.apache.tika:tika-bundle-standard</include>
         <include>commons-io:commons-io</include>
+        <include>org.apache.commons:commons-secure-xml</include>
         <include>org.commonmark:commonmark</include>
         <include>org.commonmark:commonmark-ext-gfm-tables</include>
         <include>org.commonmark:commonmark-ext-gfm-strikethrough</include>
diff --git a/tika-core/pom.xml b/tika-core/pom.xml
index 264e5a136d..aef0860055 100644
--- a/tika-core/pom.xml
+++ b/tika-core/pom.xml
@@ -44,6 +44,10 @@
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-secure-xml</artifactId>
+    </dependency>
     <!-- Markdown serialization (ToMarkdownContentHandler). BSD 2-Clause, ASF 
Category A,
          zero transitive dependencies. -->
     <dependency>
diff --git a/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java 
b/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java
index 04c780fa1a..c1bc031c74 100644
--- a/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java
+++ b/tika-core/src/main/java/org/apache/tika/mime/MimeTypesReader.java
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
-import javax.xml.XMLConstants;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
@@ -36,6 +35,7 @@ import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.sax.SAXResult;
 
 import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -211,14 +211,7 @@ public class MimeTypesReader extends DefaultHandler 
implements MimeTypesReaderMe
     }
 
     private static SAXParser newSAXParser() throws TikaException {
-        SAXParserFactory factory = SAXParserFactory.newInstance();
-        factory.setNamespaceAware(false);
-        try {
-            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-        } catch (ParserConfigurationException | SAXException e) {
-            LOG.warn("can't set secure processing feature on: " + 
factory.getClass() +
-                    ". User assumes responsibility for consequences.");
-        }
+        SAXParserFactory factory = SecureSAXParserFactory.newInstance();
         try {
             return factory.newSAXParser();
         } catch (ParserConfigurationException | SAXException e) {
diff --git a/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java 
b/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java
index fb1f32515c..f519481358 100644
--- a/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java
@@ -36,14 +36,16 @@ import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLResolver;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.TransformerFactoryConfigurationError;
 import javax.xml.transform.sax.SAXTransformerFactory;
 
-import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
+import org.apache.commons.xml.secure.SecureDocumentBuilderFactory;
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
+import org.apache.commons.xml.secure.SecureTransformerFactory;
+import org.apache.commons.xml.secure.SecureXMLInputFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -125,10 +127,6 @@ public class XMLReaderUtils implements Serializable {
     private static final EntityResolver IGNORING_SAX_ENTITY_RESOLVER =
             (publicId, systemId) -> new InputSource(new StringReader(""));
 
-    //BE CAREFUL with the return type. Some parsers will silently ignore an 
unexpected return type: CVE-2025-54988
-    private static final XMLResolver IGNORING_STAX_ENTITY_RESOLVER =
-            (publicID, systemID, baseURI, namespace) ->
-                    UnsynchronizedByteArrayInputStream.nullInputStream();
     /**
      * Parser pool size
      */
@@ -222,20 +220,11 @@ public class XMLReaderUtils implements Serializable {
      * @since Apache Tika 0.8
      */
     public static SAXParserFactory getSAXParserFactory() {
-        SAXParserFactory factory = SAXParserFactory.newInstance();
+        SAXParserFactory factory = SecureSAXParserFactory.newNSInstance();
         if (LOG.isDebugEnabled()) {
             LOG.debug("SAXParserFactory class {}", factory.getClass());
         }
-        factory.setNamespaceAware(true);
         factory.setValidating(false);
-        trySetSAXFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, 
true);
-        trySetSAXFeature(factory, 
"http://xml.org/sax/features/external-general-entities";, false);
-        trySetSAXFeature(factory, 
"http://xml.org/sax/features/external-parameter-entities";, false);
-        trySetSAXFeature(factory, 
"http://apache.org/xml/features/nonvalidating/load-external-dtd";,
-                false);
-        trySetSAXFeature(factory, 
"http://apache.org/xml/features/nonvalidating/load-dtd-grammar";,
-                false);
-
         return factory;
     }
 
@@ -251,22 +240,14 @@ public class XMLReaderUtils implements Serializable {
      */
     public static DocumentBuilderFactory getDocumentBuilderFactory() {
         //borrowed from Apache POI
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        DocumentBuilderFactory factory = 
SecureDocumentBuilderFactory.newNSInstance();
         if (LOG.isDebugEnabled()) {
             LOG.debug("DocumentBuilderFactory class {}", factory.getClass());
         }
 
         factory.setExpandEntityReferences(false);
-        factory.setNamespaceAware(true);
         factory.setValidating(false);
 
-        trySetSAXFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, 
true);
-        trySetSAXFeature(factory, 
"http://xml.org/sax/features/external-general-entities";, false);
-        trySetSAXFeature(factory, 
"http://xml.org/sax/features/external-parameter-entities";, false);
-        trySetSAXFeature(factory, 
"http://apache.org/xml/features/nonvalidating/load-external-dtd";,
-                false);
-        trySetSAXFeature(factory, 
"http://apache.org/xml/features/nonvalidating/load-dtd-grammar";,
-                false);
         trySetXercesSecurityManager(factory);
         return factory;
     }
@@ -304,7 +285,7 @@ public class XMLReaderUtils implements Serializable {
      * @since Apache Tika 1.13
      */
     public static XMLInputFactory getXMLInputFactory() {
-        XMLInputFactory factory = XMLInputFactory.newFactory();
+        XMLInputFactory factory = SecureXMLInputFactory.newFactory();
         if (LOG.isDebugEnabled()) {
             LOG.debug("XMLInputFactory class {}", factory.getClass());
         }
@@ -312,58 +293,14 @@ public class XMLReaderUtils implements Serializable {
         tryToSetStaxProperty(factory, XMLInputFactory.IS_NAMESPACE_AWARE, 
true);
 
         //try to configure secure processing
-        tryToSetStaxProperty(factory, XMLConstants.ACCESS_EXTERNAL_DTD, "");
         tryToSetStaxProperty(factory, XMLInputFactory.IS_VALIDATING, false);
         tryToSetStaxProperty(factory, XMLInputFactory.SUPPORT_DTD, false);
         tryToSetStaxProperty(factory, 
XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
 
-        //defense in depth
-        factory.setXMLResolver(IGNORING_STAX_ENTITY_RESOLVER);
         trySetStaxSecurityManager(factory);
         return factory;
     }
 
-    private static void trySetTransformerAttribute(TransformerFactory 
transformerFactory,
-                                                   String attribute, String 
value) {
-        try {
-            transformerFactory.setAttribute(attribute, value);
-        } catch (SecurityException e) {
-            throw e;
-        } catch (Exception e) {
-            LOG.warn("Transformer Attribute unsupported: {}", attribute, e);
-        } catch (AbstractMethodError ame) {
-            LOG.warn(
-                    "Cannot set Transformer attribute because outdated XML 
parser in classpath: {}",
-                    attribute, ame);
-        }
-    }
-
-    private static void trySetSAXFeature(SAXParserFactory saxParserFactory, 
String feature,
-                                         boolean enabled) {
-        try {
-            saxParserFactory.setFeature(feature, enabled);
-        } catch (SecurityException e) {
-            throw e;
-        } catch (Exception e) {
-            LOG.warn("SAX Feature unsupported: {}", feature, e);
-        } catch (AbstractMethodError ame) {
-            LOG.warn("Cannot set SAX feature because outdated XML parser in 
classpath: {}", feature,
-                    ame);
-        }
-    }
-
-    private static void trySetSAXFeature(DocumentBuilderFactory 
documentBuilderFactory,
-                                         String feature, boolean enabled) {
-        try {
-            documentBuilderFactory.setFeature(feature, enabled);
-        } catch (Exception e) {
-            LOG.warn("SAX Feature unsupported: {}", feature, e);
-        } catch (AbstractMethodError ame) {
-            LOG.warn("Cannot set SAX feature because outdated XML parser in 
classpath: {}", feature,
-                    ame);
-        }
-    }
-
     private static void tryToSetStaxProperty(XMLInputFactory factory, String 
key, boolean value) {
         try {
             factory.setProperty(key, value);
@@ -372,14 +309,6 @@ public class XMLReaderUtils implements Serializable {
         }
     }
 
-    private static void tryToSetStaxProperty(XMLInputFactory factory, String 
key, String value) {
-        try {
-            factory.setProperty(key, value);
-        } catch (IllegalArgumentException e) {
-            LOG.warn("StAX Feature unsupported: {}", key, e);
-        }
-    }
-
     /**
      * Returns a new transformer
      * <p>
@@ -409,13 +338,8 @@ public class XMLReaderUtils implements Serializable {
      */
     public static TransformerFactory getTransformerFactory() throws 
TikaException {
         try {
-
-            TransformerFactory transformerFactory = 
TransformerFactory.newInstance();
-            
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-            trySetTransformerAttribute(transformerFactory, 
XMLConstants.ACCESS_EXTERNAL_DTD, "");
-            trySetTransformerAttribute(transformerFactory, 
XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
-            return transformerFactory;
-        } catch (TransformerConfigurationException | 
TransformerFactoryConfigurationError e) {
+            return SecureTransformerFactory.newInstance();
+        } catch (TransformerFactoryConfigurationError e) {
             throw new TikaException("Transformer not available", e);
         }
     }
@@ -430,13 +354,8 @@ public class XMLReaderUtils implements Serializable {
      */
     public static SAXTransformerFactory getSAXTransformerFactory() throws 
TikaException {
         try {
-
-            SAXTransformerFactory transformerFactory = (SAXTransformerFactory) 
SAXTransformerFactory.newInstance();
-            
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-            trySetTransformerAttribute(transformerFactory, 
XMLConstants.ACCESS_EXTERNAL_DTD, "");
-            trySetTransformerAttribute(transformerFactory, 
XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
-            return transformerFactory;
-        } catch (TransformerConfigurationException | 
TransformerFactoryConfigurationError e) {
+            return (SAXTransformerFactory) 
SecureTransformerFactory.newInstance();
+        } catch (TransformerFactoryConfigurationError e) {
             throw new TikaException("Transformer not available", e);
         }
     }
diff --git 
a/tika-core/src/test/java/org/apache/tika/sax/OfflineContentHandlerTest.java 
b/tika-core/src/test/java/org/apache/tika/sax/OfflineContentHandlerTest.java
index 6c7e94513e..8ef2686851 100644
--- a/tika-core/src/test/java/org/apache/tika/sax/OfflineContentHandlerTest.java
+++ b/tika-core/src/test/java/org/apache/tika/sax/OfflineContentHandlerTest.java
@@ -21,8 +21,8 @@ import static org.junit.jupiter.api.Assertions.fail;
 import java.io.StringReader;
 import java.net.ConnectException;
 import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
 
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.xml.sax.InputSource;
@@ -39,7 +39,7 @@ public class OfflineContentHandlerTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        parser = SAXParserFactory.newInstance().newSAXParser();
+        parser = SecureSAXParserFactory.newInstance().newSAXParser();
         offline = new OfflineContentHandler(new DefaultHandler());
     }
 
diff --git 
a/tika-core/src/test/java/org/apache/tika/utils/XMLReaderUtilsTest.java 
b/tika-core/src/test/java/org/apache/tika/utils/XMLReaderUtilsTest.java
index 310a8b158e..42eb3a84d0 100644
--- a/tika-core/src/test/java/org/apache/tika/utils/XMLReaderUtilsTest.java
+++ b/tika-core/src/test/java/org/apache/tika/utils/XMLReaderUtilsTest.java
@@ -20,16 +20,29 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.net.ConnectException;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Locale;
 import java.util.NoSuchElementException;
 import javax.xml.stream.XMLEventReader;
 import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
 
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -233,6 +246,77 @@ public class XMLReaderUtilsTest {
         }
     }
 
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testTransformerExternalEntity(boolean saxFactory, @TempDir 
Path dir) throws Exception {
+        Path external = dir.resolve("external.txt");
+        Files.writeString(external, "EXTERNAL_CONTENT");
+        String xml = "<!DOCTYPE root [<!ENTITY external SYSTEM '" + 
external.toUri() + "'>]>" +
+                "<root>before&external;after</root>";
+        TransformerFactory factory = saxFactory ? 
XMLReaderUtils.getSAXTransformerFactory() : 
XMLReaderUtils.getTransformerFactory();
+        Transformer transformer = factory.newTransformer();
+        DOMResult output = new DOMResult();
+        transformer.transform(new StreamSource(new StringReader(xml)), output);
+        assertEquals("beforeafter", ((Document) 
output.getNode()).getDocumentElement().getTextContent());
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testTransformerDocumentResolver(boolean saxFactory, @TempDir 
Path dir) throws Exception {
+        Path external = dir.resolve("external.xml");
+        Files.writeString(external, "<secret>EXTERNAL_CONTENT</secret>");
+        String uri = external.toUri().toString();
+        String stylesheet = String.format(Locale.ROOT, """
+                <xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+                  <xsl:output method="text"/>
+                  <xsl:template match="/">before<xsl:value-of 
select="document('%s')/secret"/>after</xsl:template>
+                </xsl:stylesheet>
+                """, uri);
+        TransformerFactory factory = saxFactory ? 
XMLReaderUtils.getSAXTransformerFactory() : 
XMLReaderUtils.getTransformerFactory();
+        Transformer transformer = factory.newTransformer(new StreamSource(new 
StringReader(stylesheet)));
+        StringWriter output = new StringWriter();
+        transformer.transform(new StreamSource(new StringReader("<root/>")), 
new StreamResult(output));
+        assertEquals("beforeafter", output.toString());
+
+        transformer.setURIResolver((href, base) -> uri.equals(href) ?
+                new StreamSource(new 
StringReader("<secret>ALLOWED_CONTENT</secret>")) : null);
+        output = new StringWriter();
+        transformer.transform(new StreamSource(new StringReader("<root/>")), 
new StreamResult(output));
+        assertEquals("beforeALLOWED_CONTENTafter", output.toString());
+    }
+
+    @ParameterizedTest
+    @CsvSource({"false, include", "true, include", "false, import", "true, 
import"})
+    public void testTransformerStylesheetResolver(boolean saxFactory, String 
directive, @TempDir Path dir) throws Exception {
+        String externalStylesheet = """
+                <xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+                  <xsl:template match="payload" 
mode="external">EXTERNAL_CONTENT</xsl:template>
+                </xsl:stylesheet>
+                """;
+        Path external = dir.resolve("external.xsl");
+        Files.writeString(external, externalStylesheet);
+        String uri = external.toUri().toString();
+        String stylesheet = String.format(Locale.ROOT, """
+                <xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+                  <xsl:%s href="%s"/>
+                  <xsl:output method="text"/>
+                  <xsl:template match="/">before<xsl:apply-templates 
select="root/payload" mode="external"/>after</xsl:template>
+                </xsl:stylesheet>
+                """, directive, uri);
+        TransformerFactory factory = saxFactory ? 
XMLReaderUtils.getSAXTransformerFactory() : 
XMLReaderUtils.getTransformerFactory();
+        Transformer transformer = factory.newTransformer(new StreamSource(new 
StringReader(stylesheet)));
+        StringWriter output = new StringWriter();
+        transformer.transform(new StreamSource(new 
StringReader("<root><payload/></root>")), new StreamResult(output));
+        assertEquals("beforeafter", output.toString());
+
+        factory.setURIResolver((href, base) -> uri.equals(href) ?
+                new StreamSource(new 
StringReader(externalStylesheet.replace("EXTERNAL_CONTENT", 
"ALLOWED_CONTENT"))) : null);
+        transformer = factory.newTransformer(new StreamSource(new 
StringReader(stylesheet)));
+        output = new StringWriter();
+        transformer.transform(new StreamSource(new 
StringReader("<root><payload/></root>")), new StreamResult(output));
+        assertEquals("beforeALLOWED_CONTENTafter", output.toString());
+    }
+
     private void limitCheck(SAXException e) throws SAXException {
         String msg = e.getLocalizedMessage();
         if (msg == null) {
diff --git a/tika-parent/pom.xml b/tika-parent/pom.xml
index 4c420034bc..af92e82398 100644
--- a/tika-parent/pom.xml
+++ b/tika-parent/pom.xml
@@ -1260,6 +1260,11 @@
         <artifactId>jspecify</artifactId>
         <version>1.0.1</version>
       </dependency>      
+      <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-secure-xml</artifactId>
+        <version>1.0.0</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
diff --git a/tika-parsers/tika-parsers-ml/tika-vlm/pom.xml 
b/tika-parsers/tika-parsers-ml/tika-vlm/pom.xml
index eee3ddece3..01ccc2d8b5 100644
--- a/tika-parsers/tika-parsers-ml/tika-vlm/pom.xml
+++ b/tika-parsers/tika-parsers-ml/tika-vlm/pom.xml
@@ -59,6 +59,10 @@
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-secure-xml</artifactId>
+    </dependency>
 
     <!-- test -->
     <dependency>
diff --git 
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/MarkdownToXHTMLEmitterTest.java
 
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/MarkdownToXHTMLEmitterTest.java
index d5d421238f..783b4f020d 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/MarkdownToXHTMLEmitterTest.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-vlm/src/test/java/org/apache/tika/parser/vlm/MarkdownToXHTMLEmitterTest.java
@@ -22,11 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.StringWriter;
 import javax.xml.transform.OutputKeys;
-import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.sax.SAXTransformerFactory;
 import javax.xml.transform.sax.TransformerHandler;
 import javax.xml.transform.stream.StreamResult;
 
+import org.apache.commons.xml.secure.SecureTransformerFactory;
 import org.junit.jupiter.api.Test;
 
 public class MarkdownToXHTMLEmitterTest {
@@ -302,7 +302,7 @@ public class MarkdownToXHTMLEmitterTest {
     private String emit(String markdown) throws Exception {
         StringWriter sw = new StringWriter();
         SAXTransformerFactory tf =
-                (SAXTransformerFactory) TransformerFactory.newInstance();
+                (SAXTransformerFactory) SecureTransformerFactory.newInstance();
         TransformerHandler th = tf.newTransformerHandler();
         th.getTransformer().setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, 
"no");
         th.getTransformer().setOutputProperty(OutputKeys.INDENT, "no");
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/TestXMLEntityExpansion.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/TestXMLEntityExpansion.java
index 1247efffe4..a101390617 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/TestXMLEntityExpansion.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/TestXMLEntityExpansion.java
@@ -20,9 +20,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.nio.charset.StandardCharsets;
-import javax.xml.XMLConstants;
 import javax.xml.parsers.SAXParserFactory;
 
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
@@ -117,11 +117,10 @@ public class TestXMLEntityExpansion extends XMLTestBase {
         // create a new SAXParserFactory
         SAXParserFactory factory = null;
         try {
-            factory = SAXParserFactory.newInstance(provider, null);
+            factory = SecureSAXParserFactory.newInstance(provider, null);
         } catch (Exception e) {
             return;
         }
-        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
         context.set(SAXParserFactory.class, factory);
         for (int i = 0; i < XMLReaderUtils.getPoolSize() * 2; i++) {
             test("built-in SAX", injected, AUTO_DETECT_PARSER, context);
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/XMLTestBase.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/XMLTestBase.java
index 34ce4b0ad7..13468a25eb 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/XMLTestBase.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/XMLTestBase.java
@@ -33,6 +33,7 @@ import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
@@ -116,7 +117,7 @@ public class XMLTestBase extends TikaTest {
 
             TaggedContentHandler tagged = new TaggedContentHandler(handler);
             try {
-                SAXParserFactory saxParserFactory = SAXParserFactory
+                SAXParserFactory saxParserFactory = SecureSAXParserFactory
                         .newInstance("org.apache.xerces.parsers.SAXParser",
                                 this.getClass().getClassLoader());
                 SAXParser parser = saxParserFactory.newSAXParser();
@@ -141,7 +142,7 @@ public class XMLTestBase extends TikaTest {
 
             TaggedContentHandler tagged = new TaggedContentHandler(handler);
             try {
-                SAXParserFactory saxParserFactory = SAXParserFactory
+                SAXParserFactory saxParserFactory = SecureSAXParserFactory
                         .newInstance();
                 SAXParser parser = saxParserFactory.newSAXParser();
                 parser.parse(stream, new TextContentHandler(handler, true));
diff --git 
a/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaConfigAsyncWriter.java
 
b/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaConfigAsyncWriter.java
index 85c323ec61..020a5dce98 100644
--- 
a/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaConfigAsyncWriter.java
+++ 
b/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaConfigAsyncWriter.java
@@ -22,12 +22,9 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 
@@ -63,7 +60,7 @@ class TikaConfigAsyncWriter {
         }
     }
 
-    void _write(Path output) throws ParserConfigurationException, 
TransformerException, IOException, TikaException, SAXException {
+    void _write(Path output) throws TransformerException, IOException, 
TikaException, SAXException {
         Document document = null;
         Element properties = null;
         if (simpleAsyncConfig.getTikaConfig() != null) {
@@ -74,8 +71,7 @@ class TikaConfigAsyncWriter {
                         simpleAsyncConfig.getTikaConfig());
             }
         } else {
-            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-            document = dbf.newDocumentBuilder().newDocument();
+            document = XMLReaderUtils.getDocumentBuilder().newDocument();
             properties = document.createElement("properties");
             document.appendChild(properties);
         }
@@ -88,8 +84,7 @@ class TikaConfigAsyncWriter {
             }
         }
 
-        Transformer transformer = TransformerFactory
-                .newInstance().newTransformer();
+        Transformer transformer = XMLReaderUtils.getTransformer();
         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
         
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount";, "2");
         try (Writer writer = Files.newBufferedWriter(output, 
StandardCharsets.UTF_8)) {
diff --git 
a/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/TikaConfigAsyncWriterTest.java
 
b/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/TikaConfigAsyncWriterTest.java
index fa6c69fb73..4738fbc362 100644
--- 
a/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/TikaConfigAsyncWriterTest.java
+++ 
b/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/TikaConfigAsyncWriterTest.java
@@ -27,14 +27,48 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 
 import org.apache.tika.config.loader.TikaJsonConfig;
 import org.apache.tika.pipes.core.PipesConfig;
 import org.apache.tika.sax.BasicContentHandlerFactory;
+import org.apache.tika.utils.XMLReaderUtils;
 
 public class TikaConfigAsyncWriterTest {
 
 
+    @Test
+    public void testWriteNewXMLConfig(@TempDir Path dir) throws Exception {
+        SimpleAsyncConfig config = new SimpleAsyncConfig(dir.toString(), 
dir.toString(), 4,
+                10000L, null, null, null, 
BasicContentHandlerFactory.HANDLER_TYPE.TEXT,
+                SimpleAsyncConfig.ExtractBytesMode.NONE, null);
+        Path output = dir.resolve("output.xml");
+        new TikaConfigAsyncWriter(config).write(output);
+
+        Document document = XMLReaderUtils.buildDOM(output);
+        assertEquals("properties", document.getDocumentElement().getTagName());
+        assertEquals(0, 
document.getDocumentElement().getElementsByTagName("*").getLength());
+    }
+
+    @Test
+    public void testWriteExistingXMLConfig(@TempDir Path dir) throws Exception 
{
+        Path input = dir.resolve("input.xml");
+        Files.writeString(input, "<properties><entry key=\"test\">value &amp; 
more</entry></properties>");
+        SimpleAsyncConfig config = new SimpleAsyncConfig(dir.toString(), 
dir.toString(), 4,
+                10000L, null, null, input.toString(), 
BasicContentHandlerFactory.HANDLER_TYPE.TEXT,
+                SimpleAsyncConfig.ExtractBytesMode.NONE, null);
+        Path output = dir.resolve("output.xml");
+        new TikaConfigAsyncWriter(config).write(output);
+
+        Document document = XMLReaderUtils.buildDOM(output);
+        assertEquals("properties", document.getDocumentElement().getTagName());
+        assertEquals(1, document.getElementsByTagName("entry").getLength());
+        Element entry = (Element) 
document.getElementsByTagName("entry").item(0);
+        assertEquals("test", entry.getAttribute("key"));
+        assertEquals("value & more", entry.getTextContent());
+    }
+
     @Test
     public void testBasic(@TempDir Path dir) throws Exception {
         Path p = 
Paths.get(TikaConfigAsyncWriter.class.getResource("/configs/TIKA-4508-parsers.json").toURI());

Reply via email to