diff --git a/legal/LICENSE b/legal/LICENSE index 990301df8a1..7f72e786955 100644 --- a/legal/LICENSE +++ b/legal/LICENSE @@ -261,6 +261,10 @@ which was released under the Apache 2.0 license. Copyright (c) 2002-2023 EPFL Copyright (c) 2011-2023 Lightbend, Inc. +LimitInputStream is based on code from the Guava project which was released under +the Apache 2.0 license. +Copyright (C) 2007 The Guava Authors + The POI Source Release bundles the Gradle Wrapper. (https://docs.gradle.org/current/userguide/gradle_wrapper.html) This is released under the Apache License, v2.0. Copyright © 2015-2021 the original authors. diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java index bbdd30915c7..6d1b4fa8d43 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java @@ -16,6 +16,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more ==================================================================== */ package org.apache.poi.xssf.eventusermodel; +import static org.apache.poi.xssf.model.SharedStringsTable.getInputStreamReadLimit; import static org.apache.poi.xssf.usermodel.XSSFRelation.NS_SPREADSHEETML; import java.io.IOException; @@ -23,14 +24,17 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.PushbackInputStream; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.xml.parsers.ParserConfigurationException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.util.XMLHelper; import org.apache.poi.xssf.model.SharedStrings; +import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.usermodel.XSSFRelation; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.Attributes; @@ -155,6 +159,13 @@ public ReadOnlySharedStringsTable(PackagePart part) throws IOException, SAXExcep public ReadOnlySharedStringsTable(PackagePart part, boolean includePhoneticRuns) throws IOException, SAXException { this.includePhoneticRuns = includePhoneticRuns; + if (getInputStreamReadLimit() >= 0 && part.getSize() > getInputStreamReadLimit()) { + throw new IOException(String.format( + Locale.ROOT, + "SharedStrings part size (%s) exceeds the read limit (%s)", + part.getSize(), + getInputStreamReadLimit())); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } @@ -184,9 +195,12 @@ public ReadOnlySharedStringsTable(InputStream stream, boolean includePhoneticRun * @throws IOException if an error occurs while reading. * @throws SAXException if parsing the XML data fails. */ - public void readFrom(InputStream is) throws IOException, SAXException { + public void readFrom(final InputStream is) throws IOException, SAXException { + final InputStream stream = getInputStreamReadLimit() >= 0 + ? new LimitInputStream(is, getInputStreamReadLimit()) + : is; // test if the file is empty, otherwise parse it - PushbackInputStream pis = new PushbackInputStream(is, 1); + final PushbackInputStream pis = new PushbackInputStream(stream, 1); int emptyTest = pis.read(); if (emptyTest > -1) { pis.unread(emptyTest); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CalculationChain.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CalculationChain.java index 1a3bafcac66..e16f1e66ef0 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CalculationChain.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CalculationChain.java @@ -17,13 +17,16 @@ Licensed to the Apache Software Foundation (ASF) under one or more package org.apache.poi.xssf.model; import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; +import static org.apache.poi.xssf.model.SharedStringsTable.getInputStreamReadLimit; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Locale; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.util.LimitInputStream; import org.apache.xmlbeans.XmlException; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCalcCell; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCalcChain; @@ -34,6 +37,29 @@ Licensed to the Apache Software Foundation (ASF) under one or more * dependencies. The calculation chain object specifies the order in which the cells in a workbook were last calculated. */ public class CalculationChain extends POIXMLDocumentPart { + + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read calculation chain data. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } + private CTCalcChain chain; public CalculationChain() { @@ -46,14 +72,24 @@ public CalculationChain() { */ public CalculationChain(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "Calculation Chain part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } } - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 + ? new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) + : is; try { - CalcChainDocument doc = CalcChainDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + CalcChainDocument doc = CalcChainDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); chain = doc.getCalcChain(); } catch (XmlException e) { throw new IOException(e.getLocalizedMessage()); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CommentsTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CommentsTable.java index 2cd4d494a65..b73c36ac46d 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CommentsTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/CommentsTable.java @@ -23,6 +23,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import com.microsoft.schemas.vml.CTShape; @@ -32,6 +33,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.util.Internal; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.util.Removal; import org.apache.poi.util.Units; import org.apache.poi.xssf.usermodel.OoxmlSheetExtensions; @@ -50,6 +52,28 @@ public class CommentsTable extends POIXMLDocumentPart implements Comments { public static final String DEFAULT_AUTHOR = ""; public static final int DEFAULT_AUTHOR_ID = 0; + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read comments table. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } + private Sheet sheet; private XSSFVMLDrawing vmlDrawing; @@ -76,14 +100,24 @@ public CommentsTable() { */ public CommentsTable(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "Comments Table part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } } - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 + ? new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) + : is; try { - CommentsDocument doc = CommentsDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + CommentsDocument doc = CommentsDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); comments = doc.getComments(); } catch (XmlException e) { throw new IOException(e.getLocalizedMessage()); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java index 45568a23af3..099ae619b7e 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java @@ -23,6 +23,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; @@ -31,6 +32,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.ss.usermodel.Name; import org.apache.poi.util.Internal; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.util.Removal; import org.apache.xmlbeans.XmlException; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalBook; @@ -49,6 +51,29 @@ Licensed to the Apache Software Foundation (ASF) under one or more * along with the most recently seen values for what they point to. */ public class ExternalLinksTable extends POIXMLDocumentPart { + + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read external links table. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } + private CTExternalLink link; public ExternalLinksTable() { @@ -62,14 +87,24 @@ public ExternalLinksTable() { */ public ExternalLinksTable(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "External Links Table part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } } - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 + ? new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) + : is; try { - ExternalLinkDocument doc = ExternalLinkDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + ExternalLinkDocument doc = ExternalLinkDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); link = doc.getExternalLink(); } catch (XmlException e) { throw new IOException(e.getLocalizedMessage()); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SharedStringsTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SharedStringsTable.java index b83f5f297ed..a5de05926a2 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SharedStringsTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SharedStringsTable.java @@ -28,12 +28,14 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.ss.usermodel.RichTextString; import org.apache.poi.util.Internal; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlOptions; @@ -64,6 +66,28 @@ Licensed to the Apache Software Foundation (ASF) under one or more */ public class SharedStringsTable extends POIXMLDocumentPart implements SharedStrings, Closeable { + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read shared strings. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } + /** * Array of individual string items in the Shared String table. */ @@ -108,6 +132,13 @@ public SharedStringsTable() { */ public SharedStringsTable(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "SharedStrings part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } @@ -119,10 +150,12 @@ public SharedStringsTable(PackagePart part) throws IOException { * @param is The input stream containing the XML document. * @throws IOException if an error occurs while reading. */ - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 ? + new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) : is; try { int cnt = 0; - _sstDoc = SstDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + _sstDoc = SstDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); CTSst sst = _sstDoc.getSst(); count = (int)sst.getCount(); uniqueCount = (int)sst.getUniqueCount(); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SingleXmlCells.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SingleXmlCells.java index f66d9196772..09cddde50c8 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SingleXmlCells.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/SingleXmlCells.java @@ -23,10 +23,12 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Locale; import java.util.Vector; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell; import org.apache.xmlbeans.XmlException; @@ -42,13 +44,33 @@ Licensed to the Apache Software Foundation (ASF) under one or more */ public class SingleXmlCells extends POIXMLDocumentPart { + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read Single Cell Tables. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } private CTSingleXmlCells singleXMLCells; public SingleXmlCells() { super(); singleXMLCells = CTSingleXmlCells.Factory.newInstance(); - } /** @@ -56,14 +78,24 @@ public SingleXmlCells() { */ public SingleXmlCells(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "Single Cell Tables part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } } - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 + ? new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) + : is; try { - SingleXmlCellsDocument doc = SingleXmlCellsDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + SingleXmlCellsDocument doc = SingleXmlCellsDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); singleXMLCells = doc.getSingleXmlCells(); } catch (XmlException e) { throw new IOException(e.getLocalizedMessage()); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/StylesTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/StylesTable.java index 3201544192e..2a1abfcd345 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/StylesTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/StylesTable.java @@ -22,16 +22,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; @@ -42,6 +34,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.poi.ss.usermodel.FontScheme; import org.apache.poi.ss.usermodel.TableStyle; import org.apache.poi.util.Internal; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.xssf.usermodel.CustomIndexedColorMap; import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap; import org.apache.poi.xssf.usermodel.IndexedColorMap; @@ -77,6 +70,29 @@ Licensed to the Apache Software Foundation (ASF) under one or more * Table of styles shared across all sheets in a workbook. */ public class StylesTable extends POIXMLDocumentPart implements Styles { + + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } + private final SortedMap numberFormats = new TreeMap<>(); private final List fonts = new ArrayList<>(); private final List fills = new ArrayList<>(); @@ -155,6 +171,13 @@ public StylesTable() { */ public StylesTable(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "StylesTable part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } @@ -214,9 +237,11 @@ public void ensureThemesTable() { * @param is The input stream containing the XML document. * @throws IOException if an error occurs while reading. */ - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 ? + new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) : is; try { - doc = StyleSheetDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + doc = StyleSheetDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); CTStylesheet styleSheet = doc.getStyleSheet(); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ThemesTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ThemesTable.java index 4d6655d5fc2..c2425cd104e 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ThemesTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ThemesTable.java @@ -21,9 +21,11 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Locale; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.util.LimitInputStream; import org.apache.poi.xssf.usermodel.IndexedColorMap; import org.apache.poi.xssf.usermodel.XSSFColor; import org.apache.xmlbeans.XmlException; @@ -65,6 +67,28 @@ public static ThemeElement byId(int idx) { public final String name; } + private static long INPUT_STREAM_READ_LIMIT = -1; // negative means no limit + + /** + * Sets the read limit for input streams used to read themes table. + * Negative values mean no limit. The default is -1 (no limit). + * @param limit + * @since POI 5.4.2 + */ + public static void setInputStreamReadLimit(long limit) { + INPUT_STREAM_READ_LIMIT = limit; + } + + /** + * Gets the read limit for input streams used to read styles. + * Negative values mean no limit. The default is -1 (no limit). + * @return the read limit + * @since POI 5.4.2 + */ + public static long getInputStreamReadLimit() { + return INPUT_STREAM_READ_LIMIT; + } + private IndexedColorMap colorMap; private ThemeDocument theme; @@ -85,6 +109,13 @@ public ThemesTable() { */ public ThemesTable(PackagePart part) throws IOException { super(part); + if (INPUT_STREAM_READ_LIMIT >= 0 && part.getSize() > INPUT_STREAM_READ_LIMIT) { + throw new IOException(String.format( + Locale.ROOT, + "Themes Table part size (%s) exceeds the read limit (%s)", + part.getSize(), + INPUT_STREAM_READ_LIMIT)); + } try (InputStream stream = part.getInputStream()) { readFrom(stream); } @@ -116,9 +147,12 @@ public ThemesTable(ThemeDocument theme) { * @throws IOException if an error occurs while reading. * @since POI 5.2.0 */ - public void readFrom(InputStream is) throws IOException { + public void readFrom(final InputStream is) throws IOException { + final InputStream stream = INPUT_STREAM_READ_LIMIT >= 0 + ? new LimitInputStream(is, INPUT_STREAM_READ_LIMIT) + : is; try { - theme = ThemeDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + theme = ThemeDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS); } catch(XmlException e) { throw new IOException(e.getLocalizedMessage(), e); } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java index 7926912f6c0..69cd92e284f 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -24,6 +24,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.poi.POIDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.ooxml.POIXMLProperties; import org.apache.poi.ooxml.TrackingInputStream; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; @@ -1546,6 +1547,21 @@ void readFromZipStream() throws IOException { } } + @Test + void testStylesTableLimit() throws Exception { + StylesTable.setInputStreamReadLimit(100); + try { + // This file has a styles table that is larger than the limit set above + // It should throw a POIXMLException when trying to read it + assertThrows(POIXMLException.class, () -> + new XSSFWorkbook(openSampleFileStream("github-321.xlsx"))); + } finally { + // reset the limit to default value + StylesTable.setInputStreamReadLimit(-1); + } + } + + private static void expectFormattedContent(Cell cell, String value) { assertEquals(value, new DataFormatter().formatCellValue(cell), "Cell " + ref(cell) + " has wrong formatted content."); diff --git a/poi/src/main/java/org/apache/poi/util/LimitInputStream.java b/poi/src/main/java/org/apache/poi/util/LimitInputStream.java new file mode 100644 index 00000000000..7eae2afa772 --- /dev/null +++ b/poi/src/main/java/org/apache/poi/util/LimitInputStream.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed 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.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +/** + * Copied from guava source code v15 (LimitedInputStream) + * This version is modified to throw an IOException when the limit is reached. + * Internal use only, do not use in new code. + * @since POI 5.4.2 + */ +@Internal +public final class LimitInputStream extends FilterInputStream { + private final long limit; + private long left; + private long mark = -1; + + public LimitInputStream(final InputStream in, final long limit) { + super(in); + if (in == null) { + throw new NullPointerException("InputStream must not be null"); + } + if (limit < 0) { + throw new IllegalArgumentException("limit must be non-negative"); + } + this.limit = limit; + left = limit; + } + + @SuppressForbidden + @Override + public int available() throws IOException { + return (int) Math.min(in.available(), left); + } + + // it's okay to mark even if mark isn't supported, as reset won't work + @Override + public synchronized void mark(int readLimit) { + in.mark(readLimit); + mark = left; + } + + @Override + public int read() throws IOException { + if (left == 0) { + throw new IOException(String.format(Locale.ROOT, "Limit of %d bytes reached", limit)); + } + + int result = in.read(); + if (result != -1) { + --left; + } + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + if (left == 0) { + throw new IOException(String.format(Locale.ROOT, "Limit of %d bytes reached", limit)); + } + + len = (int) Math.min(len, left); + int result = in.read(b, off, len); + if (result != -1) { + left -= result; + } + return result; + } + + @Override + public synchronized void reset() throws IOException { + if (!in.markSupported()) { + throw new IOException("Mark not supported"); + } + if (mark == -1) { + throw new IOException("Mark not set"); + } + + in.reset(); + left = mark; + } + + @Override + public long skip(long n) throws IOException { + n = Math.min(n, left); + long skipped = in.skip(n); + left -= skipped; + return skipped; + } +} diff --git a/poi/src/test/java/org/apache/poi/util/TestLimitInputStream.java b/poi/src/test/java/org/apache/poi/util/TestLimitInputStream.java new file mode 100644 index 00000000000..5afc1e9d3a7 --- /dev/null +++ b/poi/src/test/java/org/apache/poi/util/TestLimitInputStream.java @@ -0,0 +1,47 @@ +/* ==================================================================== + 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.util; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestLimitInputStream { + @Test + void testLimitInputStream() throws IOException { + String text = "test1234567890"; + ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); + try (LimitInputStream lis = new LimitInputStream(bis, 1024)) { + assertEquals(text, new String(IOUtils.toByteArray(lis), StandardCharsets.UTF_8)); + } + } + + @Test + void testLimitReached() throws IOException { + String text = "test1234567890"; + ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); + try (LimitInputStream lis = new LimitInputStream(bis, 5)) { + assertThrows(IOException.class, () -> IOUtils.toByteArray(lis)); + } + } +}