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 &
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());