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

fanningpj pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/poi.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b50ce609ca check xwpf node depth (#869)
b50ce609ca is described below

commit b50ce609ca5f41b6958b5c2ab2ab104f83870a99
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Aug 1 20:28:05 2025 +0100

    check xwpf node depth (#869)
    
    * check xwpf node depth
    
    * Update TestAllFiles.java
    
    * Update TestAllFiles.java
---
 .../java/org/apache/poi/stress/TestAllFiles.java   |   3 ++
 .../java/org/apache/poi/ooxml/POIXMLException.java |   2 +-
 .../apache/poi/xwpf/usermodel/XWPFDocument.java    |  26 +++++------
 .../java/org/apache/poi/xwpf/TestXWPFBugs.java     |  10 ++++
 .../src/main/java/org/apache/poi/POIException.java |  52 +++++++++------------
 .../main/java/org/apache/poi/util/XMLHelper.java   |  36 +++++++++++++-
 test-data/document/deep-table-cell.docx            | Bin 0 -> 17198 bytes
 7 files changed, 84 insertions(+), 45 deletions(-)

diff --git 
a/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java 
b/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java
index b3aff917ac..957b804ff5 100644
--- a/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java
+++ b/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java
@@ -97,6 +97,9 @@ public class TestAllFiles {
         "poifs/60320-protected.xlsx",
         "poifs/protected_sha512.xlsx",
 
+        // stress docs
+        "document/deep-table-cell.docx",
+
         // NOTE: Expected failures should usually be added in file 
"stress.xls" instead
         // of being listed here in order to also verify the expected exception 
details!
     };
diff --git a/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java 
b/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java
index f949eccb90..be53202057 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java
@@ -19,7 +19,7 @@ package org.apache.poi.ooxml;
 /**
  * Indicates a generic OOXML error.
  */
-public final class POIXMLException extends RuntimeException{
+public final class POIXMLException extends RuntimeException {
     /**
      * Create a new {@code POIXMLException} with no
      * detail message.
diff --git 
a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java 
b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
index eb8c4c30c9..56db1b0420 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
@@ -23,22 +23,11 @@ import static 
org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Optional;
-import java.util.Spliterator;
+import java.util.*;
 import javax.xml.namespace.QName;
 import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
 import org.apache.logging.log4j.Logger;
+import org.apache.poi.POIException;
 import org.apache.poi.logging.PoiLogManager;
 import org.apache.poi.common.usermodel.PictureType;
 import org.apache.poi.ooxml.POIXMLDocument;
@@ -61,6 +50,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.Removal;
+import org.apache.poi.util.XMLHelper;
 import org.apache.poi.wp.usermodel.HeaderFooterType;
 import org.apache.poi.xddf.usermodel.chart.XDDFChart;
 import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
@@ -105,6 +95,7 @@ import 
org.openxmlformats.schemas.wordprocessingml.x2006.main.StylesDocument;
 @SuppressWarnings("unused")
 public class XWPFDocument extends POIXMLDocument implements Document, IBody {
     private static final Logger LOG = 
PoiLogManager.getLogger(XWPFDocument.class);
+    private static final int MAX_NODE_DEPTH = 1000;
 
     protected List<XWPFFooter> footers = new ArrayList<>();
     protected List<XWPFHeader> headers = new ArrayList<>();
@@ -214,6 +205,13 @@ public class XWPFDocument extends POIXMLDocument 
implements Document, IBody {
                 doc = DocumentDocument.Factory.parse(stream, 
DEFAULT_XML_OPTIONS);
                 ctDocument = doc.getDocument();
             }
+            final int nodeDepth = 
XMLHelper.getDepthOfChildNodes(ctDocument.getDomNode(), MAX_NODE_DEPTH);
+            if (nodeDepth > MAX_NODE_DEPTH) {
+                throw new IOException(String.format(Locale.ROOT,
+                        "The document is too complex, it has a node depth of 
%s, which exceeds the maximum allowed of %s",
+                        nodeDepth,
+                        MAX_NODE_DEPTH));
+            }
 
             initFootnotes();
 
@@ -304,6 +302,8 @@ public class XWPFDocument extends POIXMLDocument implements 
Document, IBody {
                 }
             }
             initHyperlinks();
+        } catch (POIException e) {
+            throw new IOException(e);
         } catch (XmlException e) {
             throw new POIXMLException(e);
         }
diff --git a/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java 
b/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java
index c39c7fe7cd..170473396e 100644
--- a/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java
+++ b/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java
@@ -28,6 +28,7 @@ import javax.crypto.Cipher;
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 import org.apache.commons.compress.archivers.zip.ZipFile;
 import org.apache.poi.POIDataSamples;
+import org.apache.poi.POIException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.openxml4j.opc.PackagePartName;
@@ -274,4 +275,13 @@ class TestXWPFBugs {
             assertEquals(STJcTable.END, 
tbl5.getCTTbl().getTblPr().getJc().xgetVal().getEnumValue());
         }
     }
+
+    @Test
+    public void testDeepTableCell() throws Exception {
+        // Document contains a table with nested cells.
+        IOException ex = assertThrows(IOException.class,
+                () -> 
XWPFTestDataSamples.openSampleDocument("deep-table-cell.docx"));
+        assertInstanceOf(POIException.class, ex.getCause());
+        assertTrue(ex.getMessage().contains("Node depth exceeds maximum 
supported depth"));
+    }
 }
diff --git a/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java 
b/poi/src/main/java/org/apache/poi/POIException.java
similarity index 50%
copy from poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java
copy to poi/src/main/java/org/apache/poi/POIException.java
index f949eccb90..106f82596a 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLException.java
+++ b/poi/src/main/java/org/apache/poi/POIException.java
@@ -14,54 +14,46 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-package org.apache.poi.ooxml;
+package org.apache.poi;
 
 /**
- * Indicates a generic OOXML error.
+ * Indicates a generic POI exception. This is not commonly used in POI
+ * but this is intended to be a base class for some new POI exceptions.
+ * Historically, POI has used {@link RuntimeException} for most of its
+ * exceptions, but this is not a good practice. This class is a checked
+ * class that extends {@link Exception} so needs to be explictly
+ * caught or declared in the method signature.
+ *
+ * @since POI 5.5.0
  */
-public final class POIXMLException extends RuntimeException{
-    /**
-     * Create a new {@code POIXMLException} with no
-     * detail message.
-     */
-    public POIXMLException() {
-        super();
-    }
+public class POIException extends Exception {
+    private static final long serialVersionUID = 1L;
 
     /**
-     * Create a new {@code POIXMLException} with
-     * the {@code String} specified as an error message.
+     * Create a new {@code POIException} with the specified message.
      *
      * @param msg The error message for the exception.
      */
-   public POIXMLException(String msg) {
+    public POIException(String msg) {
         super(msg);
     }
 
     /**
-     * Create a new {@code POIXMLException} with
-     * the {@code String} specified as an error message and the cause.
+     * Create a new {@code POIException} with the specified cause.
      *
-     * @param msg The error message for the exception.
-     * @param  cause the cause (which is saved for later retrieval by the
-     *         {@link #getCause()} method).  (A {@code null} value is
-     *         permitted, and indicates that the cause is nonexistent or
-     *         unknown.)
+     * @param cause the cause of this exception
      */
-    public POIXMLException(String msg, Throwable cause) {
-        super(msg, cause);
+    public POIException(Throwable cause) {
+        super(cause);
     }
 
     /**
-     * Create a new {@code POIXMLException} with
-     * the specified cause.
+     * Create a new {@code POIException} with the specified message and cause.
      *
-     * @param  cause the cause (which is saved for later retrieval by the
-     *         {@link #getCause()} method).  (A {@code null} value is
-     *         permitted, and indicates that the cause is nonexistent or
-     *         unknown.)
+     * @param msg The error message for the exception.
+     * @param cause the cause of this exception
      */
-     public POIXMLException(Throwable cause) {
-        super(cause);
+    public POIException(String msg, Throwable cause) {
+        super(msg, cause);
     }
 }
diff --git a/poi/src/main/java/org/apache/poi/util/XMLHelper.java 
b/poi/src/main/java/org/apache/poi/util/XMLHelper.java
index efeca688a5..071ae08f54 100644
--- a/poi/src/main/java/org/apache/poi/util/XMLHelper.java
+++ b/poi/src/main/java/org/apache/poi/util/XMLHelper.java
@@ -30,6 +30,7 @@ import static 
javax.xml.stream.XMLOutputFactory.IS_REPAIRING_NAMESPACES;
 
 import java.io.StringReader;
 import java.lang.reflect.Method;
+import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
 import javax.xml.parsers.DocumentBuilder;
@@ -49,7 +50,9 @@ import javax.xml.validation.SchemaFactory;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogBuilder;
 import org.apache.logging.log4j.Logger;
+import org.apache.poi.POIException;
 import org.apache.poi.logging.PoiLogManager;
+import org.w3c.dom.Node;
 import org.xml.sax.ErrorHandler;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -77,7 +80,6 @@ public final class XMLHelper {
             "org.apache.xerces.util.SecurityManager"
     };
 
-
     private static final Logger LOG = PoiLogManager.getLogger(XMLHelper.class);
     private static long lastLog;
 
@@ -253,6 +255,38 @@ public final class XMLHelper {
         return factory;
     }
 
+    /**
+     * Counts the depth of the DOM tree starting from the given node.
+     *
+     * @param node the node to check
+     * @param maxSupportedDepth the maximum supported depth of the DOM tree
+     * @return the depth
+     * @throws POIException if the depth exceeds <code>maxSupportedDepth</code>
+     */
+    public static int getDepthOfChildNodes(final Node node, final int 
maxSupportedDepth) throws POIException {
+        return getDepthOfChildNodes(node, maxSupportedDepth, 0);
+    }
+
+    private static int getDepthOfChildNodes(final Node node, final int 
maxSupportedDepth,
+                                            final int nodeDepth) throws 
POIException {
+        final int currentDepth = nodeDepth + 1;
+        int maxDepth = currentDepth;
+        Node child = node.getFirstChild();
+        while (child != null) {
+            int childDepth = getDepthOfChildNodes(child, maxSupportedDepth, 
currentDepth);
+            if (childDepth > maxDepth) {
+                maxDepth = childDepth;
+                if (maxDepth > maxSupportedDepth) {
+                    throw new POIException(String.format(Locale.ROOT,
+                            "Node depth exceeds maximum supported depth of %s" 
,
+                            maxSupportedDepth));
+                }
+            }
+            child = child.getNextSibling();
+        }
+        return maxDepth;
+    }
+
     private static Object _xercesSecurityManager;
     private static volatile boolean _xercesSecurityManagerSet = false;
 
diff --git a/test-data/document/deep-table-cell.docx 
b/test-data/document/deep-table-cell.docx
new file mode 100644
index 0000000000..6bb54e6c6d
Binary files /dev/null and b/test-data/document/deep-table-cell.docx differ


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to