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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jelly.git


The following commit(s) were added to refs/heads/master by this push:
     new 6fa89a1a Keep XMLUnit from re-parsing documents with an unhardened 
parser (#110)
6fa89a1a is described below

commit 6fa89a1a91bf7129104b49ab09b4ad3732aea5a7
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Fri Sep 18 00:45:02 2026 +0200

    Keep XMLUnit from re-parsing documents with an unhardened parser (#110)
    
    `<xu:assertDocumentsEqual>` fed the XML text of both dom4j documents to
    XMLUnit's `Diff`, which re-parses it with XMLUnit's own JAXP
    DocumentBuilder. That parser fetches the external DTD of any document
    with a DOCTYPE, regardless of how the documents were read in the first
    place.
    
    The tag now builds the two DOM trees itself with a DocumentBuilder from
    Commons Secure XML and hands those to XMLUnit, so no external entity is
    fetched on the comparison path either. The ignoreWhitespace attribute
    still reaches the builder, as it did through XMLUnit's global setting.
    
    A regression test compares a document whose DOCTYPE points to a DTD
    that does not exist, with and without ignoreWhitespace, since the
    latter routes the trees through XMLUnit's whitespace-stripping
    transform.
    
    Assisted-By: Claude Fable 5.1 <[email protected]>
    
    Co-authored-by: Gary Gregory <[email protected]>
---
 .../tags/xmlunit/AssertDocumentsEqualTag.java      | 31 +++++++++--
 .../tags/xmlunit/TestAssertDocumentsEqualTag.java  | 62 ++++++++++++++++++++++
 src/changes/changes.xml                            |  1 +
 3 files changed, 91 insertions(+), 3 deletions(-)

diff --git 
a/jelly-tags/xmlunit/src/main/java/org/apache/commons/jelly/tags/xmlunit/AssertDocumentsEqualTag.java
 
b/jelly-tags/xmlunit/src/main/java/org/apache/commons/jelly/tags/xmlunit/AssertDocumentsEqualTag.java
index f4875c09..cb1f8df5 100644
--- 
a/jelly-tags/xmlunit/src/main/java/org/apache/commons/jelly/tags/xmlunit/AssertDocumentsEqualTag.java
+++ 
b/jelly-tags/xmlunit/src/main/java/org/apache/commons/jelly/tags/xmlunit/AssertDocumentsEqualTag.java
@@ -17,12 +17,26 @@
 
 package org.apache.commons.jelly.tags.xmlunit;
 
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.jelly.JellyTagException;
+import org.apache.commons.jelly.XMLOutput;
+import org.apache.commons.xml.secure.SecureDocumentBuilderFactory;
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
+
 import org.apache.commons.jelly.JellyTagException;
 import org.apache.commons.jelly.XMLOutput;
 import org.custommonkey.xmlunit.Diff;
 import org.custommonkey.xmlunit.XMLUnit;
 import org.dom4j.Document;
 import org.dom4j.io.SAXReader;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
 
 /**
  * Compares two XML documents using XMLUnit (http://xmlunit.sourceforge.net/).
@@ -79,9 +93,12 @@ public class AssertDocumentsEqualTag extends 
XMLUnitTagSupport {
 
             Diff delta = null;
             try {
-                delta = new Diff(
-                    expectedDocument.asXML(),
-                    actualDocument.asXML());
+                // Handed XML text, XMLUnit re-parses it with its own JAXP 
parser, which fetches external DTDs.
+                // Build the DOM trees with Commons Secure XML instead and let 
XMLUnit compare those.
+                final DocumentBuilderFactory factory = 
SecureDocumentBuilderFactory.newNSInstance();
+                factory.setIgnoringElementContentWhitespace(ignoreWhitespace);
+                final DocumentBuilder builder = factory.newDocumentBuilder();
+                delta = new Diff(toDom(builder, expectedDocument), 
toDom(builder, actualDocument));
             }
             catch (final Throwable e) {
                 throw new JellyTagException(e);
@@ -94,6 +111,14 @@ public class AssertDocumentsEqualTag extends 
XMLUnitTagSupport {
         }
     }
 
+    /**
+     * Re-parses a dom4j document into a W3C DOM tree with the given builder.
+     */
+    private static org.w3c.dom.Document toDom(final DocumentBuilder builder, 
final Document document)
+            throws SAXException, IOException {
+        return builder.parse(new InputSource(new 
StringReader(document.asXML())));
+    }
+
     /**
      * Sets the actual XML document which is either a Document, String (of an
      * URI), URI, Reader, or InputStream.
diff --git 
a/jelly-tags/xmlunit/src/test/java/org/apache/commons/jelly/tags/xmlunit/TestAssertDocumentsEqualTag.java
 
b/jelly-tags/xmlunit/src/test/java/org/apache/commons/jelly/tags/xmlunit/TestAssertDocumentsEqualTag.java
new file mode 100644
index 00000000..7b1af192
--- /dev/null
+++ 
b/jelly-tags/xmlunit/src/test/java/org/apache/commons/jelly/tags/xmlunit/TestAssertDocumentsEqualTag.java
@@ -0,0 +1,62 @@
+/*
+ * 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.jelly.tags.xmlunit;
+
+import java.io.StringReader;
+
+import org.apache.commons.jelly.JellyContext;
+import org.apache.commons.jelly.XMLOutput;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.junit.Test;
+import org.xml.sax.InputSource;
+
+/**
+ * Tests {@link AssertDocumentsEqualTag}.
+ */
+public class TestAssertDocumentsEqualTag {
+
+    private static final String SCRIPT = "<j:jelly xmlns:j=\"jelly:core\" 
xmlns:xu=\"jelly:xmlunit\">"
+            + "<xu:assertDocumentsEqual actual=\"${doc}\" expected=\"${doc}\" 
ignoreWhitespace=\"${ignoreWhitespace}\"/>"
+            + "</j:jelly>";
+
+    /**
+     * The DOCTYPE of the compared documents points to a DTD that does not 
exist, so the comparison only succeeds if
+     * XMLUnit is not left to re-parse the documents with a parser that 
fetches external DTDs.
+     */
+    private static void assertExternalDtdIsNotFetched(final boolean 
ignoreWhitespace) throws Exception {
+        final Document document = DocumentHelper.createDocument();
+        document.addDocType("a", null, "missing.dtd");
+        document.addElement("a").addText("text");
+
+        final JellyContext context = new JellyContext();
+        context.setVariable("doc", document);
+        context.setVariable("ignoreWhitespace", ignoreWhitespace);
+        context.runScript(new InputSource(new StringReader(SCRIPT)), 
XMLOutput.createDummyXMLOutput());
+    }
+
+    @Test
+    public void testExternalDtdIsNotFetched() throws Exception {
+        assertExternalDtdIsNotFetched(false);
+    }
+
+    @Test
+    public void testExternalDtdIsNotFetchedWhenIgnoringWhitespace() throws 
Exception {
+        assertExternalDtdIsNotFetched(true);
+    }
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 167fb838..0b77f46a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -49,6 +49,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="add" dev="ggregory" due-to="Gary Gregory">Extract commons 
version into new POM property ${mx4j.version}.</action>
       <!-- FIX -->
       <action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz, Gary 
Gregory">Create XML parsers and readers through 
org.apache.commons:commons-secure-xml, so external entities and DTDs are no 
longer fetched unless JellyContext.setAllowDtdToCallExternalEntities(true) is 
used (#108).</action>
+      <action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz">Compare 
documents in &lt;xu:assertDocumentsEqual&gt; through DOM trees built with 
commons-secure-xml, so XMLUnit no longer re-parses them with a parser that 
fetches external DTDs.</action>
       <action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz, Gary 
Gregory">Set an EntityResolver on every dom4j SAXReader, so its default 
resolver no longer fetches external DTDs and entities behind the back of 
commons-secure-xml.</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Throw 
specialized RuntimeExceptions instead of plain RuntimeExceptions.</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
NestedRuntimeException for RuntimeException.</action>

Reply via email to