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]