check xwpf node depth (#869)

* check xwpf node depth

* Update TestAllFiles.java

* Update TestAllFiles.java
This commit is contained in:
PJ Fanning 2025-08-01 20:28:05 +01:00 committed by GitHub
parent fa573c72da
commit b50ce609ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 121 additions and 15 deletions

View File

@ -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!
};

View File

@ -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.

View File

@ -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);
}

View File

@ -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"));
}
}

View File

@ -0,0 +1,59 @@
/* ====================================================================
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
http://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.poi;
/**
* 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 class POIException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Create a new {@code POIException} with the specified message.
*
* @param msg The error message for the exception.
*/
public POIException(String msg) {
super(msg);
}
/**
* Create a new {@code POIException} with the specified cause.
*
* @param cause the cause of this exception
*/
public POIException(Throwable cause) {
super(cause);
}
/**
* Create a new {@code POIException} with the specified message and cause.
*
* @param msg The error message for the exception.
* @param cause the cause of this exception
*/
public POIException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -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;

Binary file not shown.