]> source.dussan.org Git - poi.git/commitdiff
Bug 56865 - Limit number of bytes (by counting them) while opening office docs
authorAndreas Beeker <kiwiwings@apache.org>
Tue, 23 Jun 2015 23:39:07 +0000 (23:39 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Tue, 23 Jun 2015 23:39:07 +0000 (23:39 +0000)
Bug 50090 - 'zip' bomb prevention

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1687148 13f79535-47bb-0310-9956-ffa450edef68

src/examples/src/org/apache/poi/xssf/usermodel/examples/BigGridDemo.java
src/ooxml/java/org/apache/poi/dev/OOXMLPrettyPrint.java
src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java
src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipHelper.java
src/ooxml/java/org/apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java
src/ooxml/java/org/apache/poi/openxml4j/util/ZipSecureFile.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xssf/dev/XSSFDump.java
src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java
src/ooxml/testcases/org/apache/poi/openxml4j/opc/AllOpenXML4JTests.java
src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestPackage.java
src/testcases/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java

index 00c5342746b6e69125acc48313278150d165820e..2dbfccec41f65f5b7081d894db10e7df78eef637 100644 (file)
@@ -23,6 +23,7 @@ import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
 import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.ss.usermodel.IndexedColors;
 import org.apache.poi.ss.util.CellReference;
@@ -165,7 +166,7 @@ public class BigGridDemo {
      * @param out the stream to write the result to
      */
        private static void substitute(File zipfile, File tmpfile, String entry, OutputStream out) throws IOException {
-        ZipFile zip = new ZipFile(zipfile);
+        ZipFile zip = ZipHelper.openZipFile(zipfile);
 
         ZipOutputStream zos = new ZipOutputStream(out);
 
index 48341d0c9a732404d6c2d3915f18753af29bfbc6..97e16f75895e60122d36d349b2d298e1d884c13f 100644 (file)
@@ -39,6 +39,7 @@ import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;\r
 import javax.xml.transform.stream.StreamResult;\r
 \r
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;\r
 import org.apache.poi.util.IOUtils;\r
 import org.w3c.dom.Document;\r
 import org.xml.sax.InputSource;\r
@@ -82,7 +83,7 @@ public class OOXMLPrettyPrint {
             IOException, TransformerException, ParserConfigurationException {\r
         System.out.println("Reading zip-file " + file + " and writing pretty-printed XML to " + outFile);\r
 \r
-        ZipFile zipFile = new ZipFile(file);\r
+        ZipFile zipFile = ZipHelper.openZipFile(file);\r
                try {\r
                    ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)));\r
                    try {\r
index 888f5013029fa41609d149c094c198f6b4ae14a8..8937f8e53ff46e90db8c9557dcfd410db697bfe9 100644 (file)
@@ -41,6 +41,8 @@ import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
 import org.apache.poi.openxml4j.util.ZipEntrySource;
 import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
 import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
+import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.TempFile;
@@ -85,9 +87,9 @@ public final class ZipPackage extends Package {
     @SuppressWarnings("deprecation")
     ZipPackage(InputStream in, PackageAccess access) throws IOException {
        super(access);
-       this.zipArchive = new ZipInputStreamZipEntrySource(
-                       new ZipInputStream(in)
-                       );
+       InputStream zis = new ZipInputStream(in);
+       ThresholdInputStream tis = ZipSecureFile.addThreshold(zis);
+       this.zipArchive = new ZipInputStreamZipEntrySource(tis);
     }
 
     /**
index 9598b05cbec3a30c416a3bcae62a5d30814d899b..408fa416faa448d9fc60ffb84d16008ffe6ad8fa 100644 (file)
@@ -29,6 +29,7 @@ import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
 import org.apache.poi.openxml4j.opc.PackageRelationship;
 import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
 import org.apache.poi.openxml4j.opc.ZipPackage;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
 
 public final class ZipHelper {
 
@@ -154,7 +155,7 @@ public final class ZipHelper {
          return null;
       }
 
-      return new ZipFile(file);
+      return new ZipSecureFile(file);
    }
 
        /**
@@ -171,6 +172,6 @@ public final class ZipHelper {
                        return null;
                }
 
-               return new ZipFile(f);
+               return new ZipSecureFile(f);
        }
 }
index f7f334c75cfd09f38db8e7a5cfadb7fd926a60f2..7d72212cdd3b907aa2bf670d53c0ae4265a8c262 100644 (file)
@@ -26,6 +26,8 @@ import java.util.Iterator;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
+import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
+
 /**
  * Provides a way to get at all the ZipEntries
  *  from a ZipInputStream, as many times as required.
@@ -43,7 +45,7 @@ public class ZipInputStreamZipEntrySource implements ZipEntrySource {
         * We'll then eat lots of memory, but be able to
         *  work with the entries at-will.
         */
-       public ZipInputStreamZipEntrySource(ZipInputStream inp) throws IOException {
+       public ZipInputStreamZipEntrySource(ThresholdInputStream inp) throws IOException {
                zipEntries = new ArrayList<FakeZipEntry>();
                
                boolean going = true;
@@ -105,7 +107,7 @@ public class ZipInputStreamZipEntrySource implements ZipEntrySource {
        public static class FakeZipEntry extends ZipEntry {
                private byte[] data;
                
-               public FakeZipEntry(ZipEntry entry, ZipInputStream inp) throws IOException {
+               public FakeZipEntry(ZipEntry entry, InputStream inp) throws IOException {
                        super(entry.getName());
                        
                        // Grab the de-compressed contents for later
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/util/ZipSecureFile.java b/src/ooxml/java/org/apache/poi/openxml4j/util/ZipSecureFile.java
new file mode 100644 (file)
index 0000000..b2f1d82
--- /dev/null
@@ -0,0 +1,227 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.openxml4j.util;\r
+\r
+import java.io.File;\r
+import java.io.FilterInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.PushbackInputStream;\r
+import java.lang.reflect.Field;\r
+import java.nio.charset.Charset;\r
+import java.util.zip.InflaterInputStream;\r
+import java.util.zip.ZipEntry;\r
+import java.util.zip.ZipException;\r
+import java.util.zip.ZipFile;\r
+import java.util.zip.ZipInputStream;\r
+\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
+\r
+/**\r
+ * This class wraps a {@link ZipFile} in order to check the\r
+ * entries for <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a>\r
+ * while reading the archive.\r
+ * If a {@link ZipInputStream} is directly used, the wrapper\r
+ * can be applied via {@link #addThreshold(InputStream)}.\r
+ * The alert limits can be globally defined via {@link #setMaxEntrySize(long)}\r
+ * and {@link #setMinInflateRatio(double)}.\r
+ */\r
+public class ZipSecureFile extends ZipFile {\r
+    private static POILogger logger = POILogFactory.getLogger(ZipSecureFile.class);\r
+    \r
+    private static double MIN_INFLATE_RATIO = 0.01d;\r
+    private static long MAX_ENTRY_SIZE = 0xFFFFFFFFl;\r
+\r
+    /**\r
+     * Sets the ratio between de- and inflated bytes to detect zipbomb.\r
+     * It defaults to 1% (= 0.01d), i.e. when the compression is better than\r
+     * 1% for any given read package part, the parsing will fail\r
+     *\r
+     * @param ratio the ratio between de- and inflated bytes to detect zipbomb\r
+     */\r
+    public static void setMinInflateRatio(double ratio) {\r
+        MIN_INFLATE_RATIO = ratio;\r
+    }\r
+\r
+    /**\r
+     * Sets the maximum file size of a single zip entry. It defaults to 4GB,\r
+     * i.e. the 32-bit zip format maximum.\r
+     *\r
+     * @param maxEntrySize the max. file size of a single zip entry\r
+     */\r
+    public static void setMaxEntrySize(long maxEntrySize) {\r
+        if (maxEntrySize < 0 || maxEntrySize > 0xFFFFFFFFl) {\r
+            throw new IllegalArgumentException("Max entry size is bounded [0-4GB].");\r
+        }\r
+        MAX_ENTRY_SIZE = maxEntrySize;\r
+    }\r
+\r
+    public ZipSecureFile(File file, Charset charset) throws IOException {\r
+        super(file, charset);\r
+    }\r
+\r
+    public ZipSecureFile(File file, int mode, Charset charset) throws IOException {\r
+        super(file, mode, charset);\r
+    }\r
+\r
+    public ZipSecureFile(File file, int mode) throws IOException {\r
+        super(file, mode);\r
+    }\r
+\r
+    public ZipSecureFile(File file) throws ZipException, IOException {\r
+        super(file);\r
+    }\r
+\r
+    public ZipSecureFile(String name, Charset charset) throws IOException {\r
+        super(name, charset);\r
+    }\r
+\r
+    public ZipSecureFile(String name) throws IOException {\r
+        super(name);\r
+    }\r
+\r
+    /**\r
+     * Returns an input stream for reading the contents of the specified\r
+     * zip file entry.\r
+     *\r
+     * <p> Closing this ZIP file will, in turn, close all input\r
+     * streams that have been returned by invocations of this method.\r
+     *\r
+     * @param entry the zip file entry\r
+     * @return the input stream for reading the contents of the specified\r
+     * zip file entry.\r
+     * @throws ZipException if a ZIP format error has occurred\r
+     * @throws IOException if an I/O error has occurred\r
+     * @throws IllegalStateException if the zip file has been closed\r
+     */\r
+    public InputStream getInputStream(ZipEntry entry) throws IOException {\r
+        InputStream zipIS = super.getInputStream(entry);\r
+        return addThreshold(zipIS);\r
+    }\r
+\r
+    public static ThresholdInputStream addThreshold(InputStream zipIS) throws IOException {\r
+        ThresholdInputStream newInner;\r
+        if (zipIS instanceof InflaterInputStream) {\r
+            try {\r
+                Field f = FilterInputStream.class.getDeclaredField("in");\r
+                f.setAccessible(true);\r
+                InputStream oldInner = (InputStream)f.get(zipIS);\r
+                newInner = new ThresholdInputStream(oldInner, null);\r
+                f.set(zipIS, newInner);\r
+            } catch (Exception ex) {\r
+                logger.log(POILogger.WARN, "SecurityManager doesn't allow manipulation via reflection for zipbomb detection - continue with original input stream", ex);\r
+                newInner = null;\r
+            }\r
+        } else {\r
+            // the inner stream is a ZipFileInputStream, i.e. the data wasn't compressed\r
+            newInner = null;\r
+        }\r
+\r
+        return new ThresholdInputStream(zipIS, newInner);\r
+    }\r
+\r
+    public static class ThresholdInputStream extends PushbackInputStream {\r
+        long counter = 0;\r
+        ThresholdInputStream cis;\r
+\r
+        public ThresholdInputStream(InputStream is, ThresholdInputStream cis) {\r
+            super(is,1);\r
+            this.cis = cis;\r
+        }\r
+\r
+        public int read() throws IOException {\r
+            int b = in.read();\r
+            if (b > -1) advance(1);\r
+            return b;\r
+        }\r
+\r
+        public int read(byte b[], int off, int len) throws IOException {\r
+            int cnt = in.read(b, off, len);\r
+            if (cnt > -1) advance(cnt);\r
+            return cnt;\r
+\r
+        }\r
+\r
+        public long skip(long n) throws IOException {\r
+            counter = 0;\r
+            return in.skip(n);\r
+        }\r
+\r
+        public synchronized void reset() throws IOException {\r
+            counter = 0;\r
+            in.reset();\r
+        }\r
+\r
+        public void advance(int advance) throws IOException {\r
+            counter += advance;\r
+            // check the file size first, in case we are working on uncompressed streams\r
+            if (counter < MAX_ENTRY_SIZE) {\r
+                if (cis == null) return;\r
+                double ratio = (double)cis.counter/(double)counter;\r
+                if (ratio > MIN_INFLATE_RATIO) return;\r
+            }\r
+            throw new IOException("Zip bomb detected! Exiting.");\r
+        }\r
+\r
+        public ZipEntry getNextEntry() throws IOException {\r
+            if (!(in instanceof ZipInputStream)) {\r
+                throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");\r
+            }\r
+            counter = 0;\r
+            return ((ZipInputStream)in).getNextEntry();\r
+        }\r
+\r
+        public void closeEntry() throws IOException {\r
+            if (!(in instanceof ZipInputStream)) {\r
+                throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");\r
+            }\r
+            counter = 0;\r
+            ((ZipInputStream)in).closeEntry();\r
+        }\r
+\r
+        public void unread(int b) throws IOException {\r
+            if (!(in instanceof PushbackInputStream)) {\r
+                throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");\r
+            }\r
+            if (--counter < 0) counter = 0;\r
+            ((PushbackInputStream)in).unread(b);\r
+        }\r
+\r
+        public void unread(byte[] b, int off, int len) throws IOException {\r
+            if (!(in instanceof PushbackInputStream)) {\r
+                throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");\r
+            }\r
+            counter -= len;\r
+            if (--counter < 0) counter = 0;\r
+            ((PushbackInputStream)in).unread(b, off, len);\r
+        }\r
+\r
+        public int available() throws IOException {\r
+            return in.available();\r
+        }\r
+\r
+        public boolean markSupported() {\r
+            return in.markSupported();\r
+        }\r
+\r
+        public synchronized void mark(int readlimit) {\r
+            in.mark(readlimit);\r
+        }\r
+    }\r
+}\r
index 41fc335af077922cd1c253d43efedf00d1088bb9..a3e04eb24ca846eecbe67d9d7dfb1b0d172de460 100644 (file)
@@ -24,6 +24,7 @@ import java.util.Enumeration;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
 import org.apache.poi.util.IOUtils;
 import org.apache.xmlbeans.XmlObject;
 import org.apache.xmlbeans.XmlOptions;
@@ -38,7 +39,7 @@ public final class XSSFDump {
     public static void main(String[] args) throws Exception {
         for (int i = 0; i < args.length; i++) {
             System.out.println("Dumping " + args[i]);
-            ZipFile zip = new ZipFile(args[i]);
+            ZipFile zip = ZipHelper.openZipFile(args[i]);
             try {
                dump(zip);
             } finally {
index e3a0363dedb06b3cdf68d839885ddb5f7b0d98ea..bd75179de48da0536455e6def95eef438b123140 100644 (file)
@@ -32,6 +32,7 @@ import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
 import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
 import org.apache.poi.ss.formula.udf.UDFFinder;
 import org.apache.poi.ss.usermodel.CellStyle;
 import org.apache.poi.ss.usermodel.CreationHelper;
@@ -334,7 +335,7 @@ public class SXSSFWorkbook implements Workbook
     }
     private void injectData(File zipfile, OutputStream out) throws IOException 
     {
-        ZipFile zip = new ZipFile(zipfile);
+        ZipFile zip = ZipHelper.openZipFile(zipfile);
         try
         {
             ZipOutputStream zos = new ZipOutputStream(out);
index 1dcf93a32d54d722e6afe84db42abe6d5d4ad6cd..a1a3ccc8877953a2a9fbae7bc63a860d50610b3f 100644 (file)
 
 package org.apache.poi.openxml4j.opc;
 
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
 import org.apache.poi.openxml4j.opc.compliance.AllOpenXML4JComplianceTests;
 import org.apache.poi.openxml4j.opc.internal.AllOpenXML4JInternalTests;
-
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+      TestContentType.class
+    , TestFileHelper.class
+    , TestListParts.class
+    , TestPackage.class
+    , TestPackageCoreProperties.class
+    , TestPackagePartName.class
+    , TestPackageThumbnail.class
+    , TestPackagingURIHelper.class
+    , TestRelationships.class
+    , AllOpenXML4JComplianceTests.class
+    , AllOpenXML4JInternalTests.class
+})
 public final class AllOpenXML4JTests {
-
-       public static Test suite() {
-
-               TestSuite suite = new TestSuite(AllOpenXML4JTests.class.getName());
-               suite.addTestSuite(TestContentType.class);
-               suite.addTestSuite(TestFileHelper.class);
-               suite.addTestSuite(TestListParts.class);
-               suite.addTestSuite(TestPackage.class);
-               suite.addTestSuite(TestPackageCoreProperties.class);
-               suite.addTestSuite(TestPackagePartName.class);
-               suite.addTestSuite(TestPackageThumbnail.class);
-               suite.addTestSuite(TestPackagingURIHelper.class);
-               suite.addTestSuite(TestRelationships.class);
-               suite.addTest(AllOpenXML4JComplianceTests.suite());
-               suite.addTest(AllOpenXML4JInternalTests.suite());
-               return suite;
-       }
 }
index 07a2b333d65faec8b9c2834a11a8063099f4935a..01c1b7707d32bcfe9d055c9164785f129f23539c 100644 (file)
 
 package org.apache.poi.openxml4j.opc;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -25,35 +33,47 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.net.URI;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.TreeMap;
 import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
 
-import junit.framework.TestCase;
-
+import org.apache.poi.POIXMLException;
 import org.apache.poi.openxml4j.OpenXML4JTestDataSamples;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
 import org.apache.poi.openxml4j.opc.internal.ContentTypeManager;
 import org.apache.poi.openxml4j.opc.internal.FileHelper;
 import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
 import org.apache.poi.util.DocumentHelper;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.TempFile;
+import org.junit.Ignore;
+import org.junit.Test;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
-public final class TestPackage extends TestCase {
+public final class TestPackage {
     private static final POILogger logger = POILogFactory.getLogger(TestPackage.class);
 
        /**
         * Test that just opening and closing the file doesn't alter the document.
         */
-       public void testOpenSave() throws Exception {
+    @Test
+       public void openSave() throws Exception {
                String originalFile = OpenXML4JTestDataSamples.getSampleFileName("TestPackageCommon.docx");
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestPackageOpenSaveTMP.docx");
 
@@ -75,7 +95,8 @@ public final class TestPackage extends TestCase {
         * Test that when we create a new Package, we give it
         *  the correct default content types
         */
-       public void testCreateGetsContentTypes() throws Exception {
+    @Test
+       public void createGetsContentTypes() throws Exception {
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestCreatePackageTMP.docx");
 
                // Zap the target file, in case of an earlier run
@@ -107,7 +128,8 @@ public final class TestPackage extends TestCase {
        /**
         * Test package creation.
         */
-       public void testCreatePackageAddPart() throws Exception {
+    @Test
+       public void createPackageAddPart() throws Exception {
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestCreatePackageTMP.docx");
 
                File expectedFile = OpenXML4JTestDataSamples.getSampleFile("TestCreatePackageOUTPUT.docx");
@@ -153,7 +175,8 @@ public final class TestPackage extends TestCase {
         *  document and another part, save and re-load and
         *  have everything setup as expected
         */
-       public void testCreatePackageWithCoreDocument() throws Exception {
+    @Test
+       public void createPackageWithCoreDocument() throws Exception {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                OPCPackage pkg = OPCPackage.create(baos);
 
@@ -247,7 +270,8 @@ public final class TestPackage extends TestCase {
     /**
         * Test package opening.
         */
-       public void testOpenPackage() throws Exception {
+    @Test
+       public void openPackage() throws Exception {
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestOpenPackageTMP.docx");
 
                File inputFile = OpenXML4JTestDataSamples.getSampleFile("TestOpenPackageINPUT.docx");
@@ -306,7 +330,8 @@ public final class TestPackage extends TestCase {
         *  OutputStream, in addition to the normal writing
         *  to a file
         */
-       public void testSaveToOutputStream() throws Exception {
+    @Test
+       public void saveToOutputStream() throws Exception {
                String originalFile = OpenXML4JTestDataSamples.getSampleFileName("TestPackageCommon.docx");
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestPackageOpenSaveTMP.docx");
 
@@ -334,7 +359,8 @@ public final class TestPackage extends TestCase {
         *  simple InputStream, in addition to the normal
         *  reading from a file
         */
-       public void testOpenFromInputStream() throws Exception {
+    @Test
+       public void openFromInputStream() throws Exception {
                String originalFile = OpenXML4JTestDataSamples.getSampleFileName("TestPackageCommon.docx");
 
                FileInputStream finp = new FileInputStream(originalFile);
@@ -353,7 +379,9 @@ public final class TestPackage extends TestCase {
     /**
      * TODO: fix and enable
      */
-    public void disabled_testRemovePartRecursive() throws Exception {
+    @Test
+    @Ignore
+    public void removePartRecursive() throws Exception {
                String originalFile = OpenXML4JTestDataSamples.getSampleFileName("TestPackageCommon.docx");
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestPackageRemovePartRecursiveOUTPUT.docx");
                File tempFile = OpenXML4JTestDataSamples.getOutputFile("TestPackageRemovePartRecursiveTMP.docx");
@@ -369,7 +397,8 @@ public final class TestPackage extends TestCase {
                assertTrue(targetFile.delete());
        }
 
-       public void testDeletePart() throws InvalidFormatException {
+    @Test
+       public void deletePart() throws InvalidFormatException {
                TreeMap<PackagePartName, String> expectedValues;
                TreeMap<PackagePartName, String> values;
 
@@ -426,7 +455,8 @@ public final class TestPackage extends TestCase {
                p.revert();
        }
 
-       public void testDeletePartRecursive() throws InvalidFormatException {
+    @Test
+       public void deletePartRecursive() throws InvalidFormatException {
                TreeMap<PackagePartName, String> expectedValues;
                TreeMap<PackagePartName, String> values;
 
@@ -468,7 +498,8 @@ public final class TestPackage extends TestCase {
         * Test that we can open a file by path, and then
         *  write changes to it.
         */
-       public void testOpenFileThenOverwrite() throws Exception {
+    @Test
+       public void openFileThenOverwrite() throws Exception {
         File tempFile = TempFile.createTempFile("poiTesting","tmp");
         File origFile = OpenXML4JTestDataSamples.getSampleFile("TestPackageCommon.docx");
         FileHelper.copyFile(origFile, tempFile);
@@ -505,7 +536,8 @@ public final class TestPackage extends TestCase {
      * Test that we can open a file by path, save it
      *  to another file, then delete both
      */
-    public void testOpenFileThenSaveDelete() throws Exception {
+    @Test
+    public void openFileThenSaveDelete() throws Exception {
         File tempFile = TempFile.createTempFile("poiTesting","tmp");
         File tempFile2 = TempFile.createTempFile("poiTesting","tmp");
         File origFile = OpenXML4JTestDataSamples.getSampleFile("TestPackageCommon.docx");
@@ -529,7 +561,8 @@ public final class TestPackage extends TestCase {
                return (ContentTypeManager)f.get(pkg);
        }
 
-    public void testGetPartsByName() throws Exception {
+    @Test
+    public void getPartsByName() throws Exception {
         String filepath =  OpenXML4JTestDataSamples.getSampleFileName("sample.docx");
 
         OPCPackage pkg = OPCPackage.open(filepath, PackageAccess.READ_WRITE);
@@ -553,7 +586,8 @@ public final class TestPackage extends TestCase {
         }
     }
     
-    public void testGetPartSize() throws Exception {
+    @Test
+    public void getPartSize() throws Exception {
        String filepath =  OpenXML4JTestDataSamples.getSampleFileName("sample.docx");
        OPCPackage pkg = OPCPackage.open(filepath, PackageAccess.READ);
        try {
@@ -585,7 +619,8 @@ public final class TestPackage extends TestCase {
        }
     }
 
-    public void testReplaceContentType() throws Exception {
+    @Test
+    public void replaceContentType() throws Exception {
         InputStream is = OpenXML4JTestDataSamples.openSampleStream("sample.xlsx");
         OPCPackage p = OPCPackage.open(is);
 
@@ -603,4 +638,113 @@ public final class TestPackage extends TestCase {
         assertFalse(mgr.isContentTypeRegister("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"));
         assertTrue(mgr.isContentTypeRegister("application/vnd.ms-excel.sheet.macroEnabled.main+xml"));
     }
+
+    @Test(expected=IOException.class)
+    public void zipBombCreateAndHandle() throws Exception {
+        // #50090 / #56865
+        ZipFile zipFile = ZipHelper.openZipFile(OpenXML4JTestDataSamples.getSampleFile("sample.xlsx"));
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ZipOutputStream append = new ZipOutputStream(bos);
+        // first, copy contents from existing war
+        Enumeration<? extends ZipEntry> entries = zipFile.entries();
+        while (entries.hasMoreElements()) {
+            ZipEntry e2 = entries.nextElement();
+            ZipEntry e = new ZipEntry(e2.getName());
+            e.setTime(e2.getTime());
+            e.setComment(e2.getComment());
+            e.setSize(e2.getSize());
+            
+            append.putNextEntry(e);
+            if (!e.isDirectory()) {
+                InputStream is = zipFile.getInputStream(e);
+                if (e.getName().equals("[Content_Types].xml")) {
+                    ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
+                    IOUtils.copy(is, bos2);
+                    long size = bos2.size()-"</Types>".length();
+                    append.write(bos2.toByteArray(), 0, (int)size);
+                    byte spam[] = new byte[0x7FFF];
+                    for (int i=0; i<spam.length; i++) spam[i] = ' ';
+                    while (size < 0x7FFF0000) {
+                        append.write(spam);
+                        size += spam.length;
+                    }
+                    append.write("</Types>".getBytes());
+                    size += 8;
+                    e.setSize(size);
+                } else {
+                    IOUtils.copy(is, append);
+                }
+            }
+            append.closeEntry();
+        }
+        
+        append.close();
+        zipFile.close();
+
+        byte buf[] = bos.toByteArray();
+        bos = null;
+        
+        Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(buf));
+        wb.getSheetAt(0);
+        wb.close();
+    }
+    
+    @Test
+    public void zipBombCheckSizes() throws Exception {
+        File file = OpenXML4JTestDataSamples.getSampleFile("sample.xlsx");
+
+        try {
+            double min_ratio = Double.MAX_VALUE;
+            long max_size = 0;
+            ZipFile zf = ZipHelper.openZipFile(file);
+            Enumeration<? extends ZipEntry> entries = zf.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry ze = entries.nextElement();
+                double ratio = (double)ze.getCompressedSize() / (double)ze.getSize();
+                min_ratio = Math.min(min_ratio, ratio);
+                max_size = Math.max(max_size, ze.getSize());
+            }
+            zf.close();
+    
+            // use values close to, but within the limits 
+            ZipSecureFile.setMinInflateRatio(min_ratio-0.002);
+            ZipSecureFile.setMaxEntrySize(max_size+1);
+            Workbook wb = WorkbookFactory.create(file);
+            wb.close();
+    
+            // check ratio ouf of bounds
+            ZipSecureFile.setMinInflateRatio(min_ratio+0.002);
+            try {
+                wb = WorkbookFactory.create(file);
+                wb.close();
+                // this is a bit strange, as there will be different exceptions thrown
+                // depending if this executed via "ant test" or within eclipse
+                // maybe a difference in JDK ...
+            } catch (InvalidFormatException e) {
+                assertEquals("Zip bomb detected! Exiting.", e.getMessage());
+            } catch (POIXMLException e) {
+                InvocationTargetException t = (InvocationTargetException)e.getCause();
+                IOException t2 = (IOException)t.getTargetException();
+                assertEquals("Zip bomb detected! Exiting.", t2.getMessage());
+            }
+    
+            // check max entry size ouf of bounds
+            ZipSecureFile.setMinInflateRatio(min_ratio-0.002);
+            ZipSecureFile.setMaxEntrySize(max_size-1);
+            try {
+                wb = WorkbookFactory.create(file, null, true);
+                wb.close();
+            } catch (InvalidFormatException e) {
+                assertEquals("Zip bomb detected! Exiting.", e.getMessage());
+            } catch (POIXMLException e) {
+                InvocationTargetException t = (InvocationTargetException)e.getCause();
+                IOException t2 = (IOException)t.getTargetException();
+                assertEquals("Zip bomb detected! Exiting.", t2.getMessage());
+            }
+        } finally {
+            // reset otherwise a lot of ooxml tests will fail
+            ZipSecureFile.setMinInflateRatio(0.01d);
+            ZipSecureFile.setMaxEntrySize(0xFFFFFFFFl);            
+        }
+    }
 }
index 13581c8a4f2f5b5def55345cc77dc09562acb1ed..617f7b052d66501856c8cbcc1d37dd4137b91a52 100644 (file)
@@ -516,6 +516,7 @@ public final class ExcelFileFormatDocFunctionExtractor {
                ps.println("#Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote )");
                ps.println("");
                try {
+                   // can't use ZipHelper here, because its in a different module
                        ZipFile zf = new ZipFile(effDocFile);
                        InputStream is = zf.getInputStream(zf.getEntry("content.xml"));
                        extractFunctionData(new FunctionDataCollector(ps), is);