]> source.dussan.org Git - poi.git/commitdiff
Bug 62187 - commit Commons Compress unrelated changes
authorAndreas Beeker <kiwiwings@apache.org>
Wed, 25 Apr 2018 10:03:39 +0000 (10:03 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Wed, 25 Apr 2018 10:03:39 +0000 (10:03 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1830061 13f79535-47bb-0310-9956-ffa450edef68

30 files changed:
src/examples/src/org/apache/poi/xssf/usermodel/examples/BigGridDemo.java
src/java/org/apache/poi/UnsupportedFileFormatException.java
src/ooxml/java/org/apache/poi/POIXMLFactory.java
src/ooxml/java/org/apache/poi/openxml4j/exceptions/NotOfficeXmlFileException.java
src/ooxml/java/org/apache/poi/openxml4j/opc/CompressionOption.java [deleted file]
src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java
src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java
src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java
src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java
src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackagePart.java
src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipContentTypeManager.java
src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipHelper.java
src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPackagePropertiesMarshaller.java
src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java
src/ooxml/java/org/apache/poi/openxml4j/util/ZipArchiveFakeEntry.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/openxml4j/util/ZipEntrySource.java
src/ooxml/java/org/apache/poi/openxml4j/util/ZipFileZipEntrySource.java
src/ooxml/java/org/apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java
src/ooxml/java/org/apache/poi/openxml4j/util/ZipSecureFile.java
src/ooxml/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java
src/ooxml/java/org/apache/poi/poifs/crypt/temp/EncryptedTempData.java
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/TestPackage.java
src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestZipPackage.java [deleted file]
src/ooxml/testcases/org/apache/poi/openxml4j/opc/ZipFileAssert.java
src/ooxml/testcases/org/apache/poi/openxml4j/opc/internal/marshallers/TestZipPackagePropertiesMarshaller.java
src/ooxml/testcases/org/apache/poi/poifs/crypt/TestDecryptor.java
src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFWorkbook.java

index 439b16ce85512c9f19fc4decc1256cea75ad3ccd..75e116a589569044f36908c66d1335e2112cd1e0 100644 (file)
@@ -74,12 +74,11 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  * </p>
  * See <a "http://poi.apache.org/spreadsheet/how-to.html#sxssf">
  *     http://poi.apache.org/spreadsheet/how-to.html#sxssf</a>.
-
- *
- * @author Yegor Kozlov
  */
-public class BigGridDemo {
+public final class BigGridDemo {
     private static final String XML_ENCODING = "UTF-8";
+
+    private BigGridDemo() {}
     
     public static void main(String[] args) throws Exception {
 
@@ -229,17 +228,17 @@ public class BigGridDemo {
         private final Writer _out;
         private int _rownum;
 
-        public SpreadsheetWriter(Writer out){
+        SpreadsheetWriter(Writer out){
             _out = out;
         }
 
-        public void beginSheet() throws IOException {
+        void beginSheet() throws IOException {
             _out.write("<?xml version=\"1.0\" encoding=\""+XML_ENCODING+"\"?>" +
                     "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">" );
             _out.write("<sheetData>\n");
         }
 
-        public void endSheet() throws IOException {
+        void endSheet() throws IOException {
             _out.write("</sheetData>");
             _out.write("</worksheet>");
         }
@@ -249,7 +248,7 @@ public class BigGridDemo {
          *
          * @param rownum 0-based row number
          */
-        public void insertRow(int rownum) throws IOException {
+        void insertRow(int rownum) throws IOException {
             _out.write("<row r=\""+(rownum+1)+"\">\n");
             this._rownum = rownum;
         }
@@ -257,7 +256,7 @@ public class BigGridDemo {
         /**
          * Insert row end marker
          */
-        public void endRow() throws IOException {
+        void endRow() throws IOException {
             _out.write("</row>\n");
         }
 
index a8caebb4bf1de254f65e6f4e9cbb971f9278cf2b..b89363951464155b86076f6c56eaf9f322a10e9a 100644 (file)
@@ -23,7 +23,11 @@ package org.apache.poi;
 public abstract class UnsupportedFileFormatException extends IllegalArgumentException {
     private static final long serialVersionUID = -8281969197282030046L;
 
-    public UnsupportedFileFormatException(String s) {
+    protected UnsupportedFileFormatException(String s) {
                super(s);
        }
+
+    protected UnsupportedFileFormatException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
\ No newline at end of file
index eefa591877a367e5c54cbe81255a7a550f63a1af..651f40cbc149fe09bd41f7027373e3844412cf5f 100644 (file)
@@ -60,7 +60,7 @@ public abstract class POIXMLFactory {
                 return createDocumentPart(cls, ORPHAN_PART, new Object[]{part});
             }
         } catch (Exception e) {
-            throw new POIXMLException(e);
+            throw new POIXMLException((e.getCause() != null ? e.getCause() : e).getMessage(), e);
         }
     }
     
index 901967e202feeb9734feca7649abef86ff2dd89b..bd6c6378baa49cd3c6210d8b4109544aee6c8026 100644 (file)
@@ -26,4 +26,8 @@ public class NotOfficeXmlFileException extends UnsupportedFileFormatException {
     public NotOfficeXmlFileException(String message) {
         super(message);
     }
+
+    public NotOfficeXmlFileException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/CompressionOption.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/CompressionOption.java
deleted file mode 100644 (file)
index 583b5c5..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/* ====================================================================
-   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.openxml4j.opc;
-
-import java.util.zip.Deflater;
-
-/**
- * Specifies the compression level for content that is stored in a PackagePart.
- *
- * @author Julien Chable
- * @version 1.0
- */
-public enum CompressionOption {
-       /** Compression is optimized for performance. */
-       FAST(Deflater.BEST_SPEED),
-       /** Compression is optimized for size. */
-       MAXIMUM(Deflater.BEST_COMPRESSION),
-       /** Compression is optimized for a balance between size and performance. */
-       NORMAL(Deflater.DEFAULT_COMPRESSION),
-       /** Compression is turned off. */
-       NOT_COMPRESSED(Deflater.NO_COMPRESSION);
-
-       private final int value;
-
-       CompressionOption(int value) {
-               this.value = value;
-       }
-
-       public int value() {
-               return this.value;
-       }
-}
index ec5892040abd374cfb45887b7d0fdd5283792149..7bb7e1367aaaee3da6c2b83e45b3978a054364c6 100644 (file)
@@ -76,12 +76,12 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
        /**
         * Package access.
         */
-       private PackageAccess packageAccess;
+       private final PackageAccess packageAccess;
 
        /**
         * Package parts collection.
         */
-       protected PackagePartCollection partList;
+       private PackagePartCollection partList;
 
        /**
         * Package relationships.
@@ -91,17 +91,17 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
        /**
         * Part marshallers by content type.
         */
-       protected Map<ContentType, PartMarshaller> partMarshallers;
+       protected final Map<ContentType, PartMarshaller> partMarshallers = new HashMap<>(5);
 
        /**
         * Default part marshaller.
         */
-       protected PartMarshaller defaultPartMarshaller;
+       protected final PartMarshaller defaultPartMarshaller = new DefaultMarshaller();
 
        /**
         * Part unmarshallers by content type.
         */
-       protected Map<ContentType, PartUnmarshaller> partUnmarshallers;
+       protected final Map<ContentType, PartUnmarshaller> partUnmarshallers = new HashMap<>(2);
 
        /**
         * Core package properties.
@@ -138,41 +138,27 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
                if (getClass() != ZipPackage.class) {
                        throw new IllegalArgumentException("PackageBase may not be subclassed");
                }
-               init();
                this.packageAccess = access;
-       }
 
-       /**
-        * Initialize the package instance.
-        */
-       private void init() {
-               this.partMarshallers = new HashMap<>(5);
-               this.partUnmarshallers = new HashMap<>(2);
+               final ContentType contentType = newCorePropertiesPart();
+               // TODO Delocalize specialized marshallers
+               this.partUnmarshallers.put(contentType, new PackagePropertiesUnmarshaller());
+               this.partMarshallers.put(contentType, new ZipPackagePropertiesMarshaller());
+       }
 
+       private static ContentType newCorePropertiesPart() {
                try {
-                       // Add 'default' unmarshaller
-                       this.partUnmarshallers.put(new ContentType(
-                                       ContentTypes.CORE_PROPERTIES_PART),
-                                       new PackagePropertiesUnmarshaller());
-
-                       // Add default marshaller
-                       this.defaultPartMarshaller = new DefaultMarshaller();
-                       // TODO Delocalize specialized marshallers
-                       this.partMarshallers.put(new ContentType(
-                                       ContentTypes.CORE_PROPERTIES_PART),
-                                       new ZipPackagePropertiesMarshaller());
+                       return new ContentType(ContentTypes.CORE_PROPERTIES_PART);
                } catch (InvalidFormatException e) {
                        // Should never happen
                        throw new OpenXML4JRuntimeException(
-                                       "Package.init() : this exception should never happen, " +
-                                       "if you read this message please send a mail to the developers team. : " +
-                                       e.getMessage(),
-                                       e
+                               "Package.init() : this exception should never happen, " +
+                               "if you read this message please send a mail to the developers team. : " +
+                               e.getMessage(), e
                        );
                }
        }
 
-
        /**
         * Open a package with read/write permission.
         *
@@ -625,7 +611,8 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
                                return null;
                        }
                }
-               return getPartImpl(partName);
+
+               return partList.get(partName);
        }
 
        /**
@@ -737,25 +724,16 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
 
                        // Check rule M4.1 -> A format consumer shall consider more than
                        // one core properties relationship for a package to be an error
-                  // (We just log it and move on, as real files break this!)
+                   // (We just log it and move on, as real files break this!)
                        boolean hasCorePropertiesPart = false;
                        boolean needCorePropertiesPart = true;
 
-                       PackagePart[] parts = this.getPartsImpl();
-                       this.partList = new PackagePartCollection();
-                       for (PackagePart part : parts) {
-                               if (partList.containsKey(part._partName)) {
-                                       throw new InvalidFormatException(
-                                                       "A part with the name '" +
-                                                       part._partName +
-                                                       "' already exist : Packages shall not contain equivalent " +
-                                                       "part names and package implementers shall neither create " +
-                                                       "nor recognize packages with equivalent part names. [M1.12]");
-                               }
+                       partList = getPartsImpl();
+                       for (PackagePart part : new ArrayList<>(partList.sortedValues())) {
+                           part.loadRelationships();
 
                                // Check OPC compliance rule M4.1
-                               if (part.getContentType().equals(
-                                               ContentTypes.CORE_PROPERTIES_PART)) {
+                               if (ContentTypes.CORE_PROPERTIES_PART.equals(part.getContentType())) {
                                        if (!hasCorePropertiesPart) {
                                                hasCorePropertiesPart = true;
                                        } else {
@@ -768,11 +746,10 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
                                PartUnmarshaller partUnmarshaller = partUnmarshallers.get(part._contentType);
 
                                if (partUnmarshaller != null) {
-                                       UnmarshallContext context = new UnmarshallContext(this,
-                                                       part._partName);
+                                       UnmarshallContext context = new UnmarshallContext(this, part._partName);
                                        try {
-                                               PackagePart unmarshallPart = partUnmarshaller
-                                                               .unmarshall(context, part.getInputStream());
+                                               PackagePart unmarshallPart = partUnmarshaller.unmarshall(context, part.getInputStream());
+                                               partList.remove(part.getPartName());
                                                partList.put(unmarshallPart._partName, unmarshallPart);
 
                                                // Core properties case-- use first CoreProperties part we come across
@@ -790,12 +767,6 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
                                        } catch (InvalidOperationException invoe) {
                                                throw new InvalidFormatException(invoe.getMessage(), invoe);
                                        }
-                               } else {
-                                       try {
-                                               partList.put(part._partName, part);
-                                       } catch (InvalidOperationException e) {
-                                               throw new InvalidFormatException(e.getMessage(), e);
-                                       }
                                }
                        }
                }
@@ -1457,6 +1428,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
         }
        }
 
+
        /* Accesseurs */
 
        /**
@@ -1575,21 +1547,12 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
        protected abstract void saveImpl(OutputStream outputStream)
                        throws IOException;
 
-       /**
-        * Get the package part mapped to the specified URI.
-        *
-        * @param partName
-        *            The URI of the part to retrieve.
-        * @return The package part located by the specified URI, else <b>null</b>.
-        */
-       protected abstract PackagePart getPartImpl(PackagePartName partName);
-
        /**
         * Get all parts link to the package.
         *
         * @return A list of the part owned by the package.
         */
-       protected abstract PackagePart[] getPartsImpl()
+       protected abstract PackagePartCollection getPartsImpl()
                        throws InvalidFormatException;
 
     /**
index 09fb30f70bede63db424f4dc48d9f7edcf9af97e..3a19020a3aca83a594f054e59014a14185278e4c 100644 (file)
@@ -106,8 +106,9 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
                _isRelationshipPart = this._partName.isRelationshipPartURI();
 
                // Load relationships if any
-               if (loadRelationships)
+               if (loadRelationships) {
                        loadRelationships();
+               }
        }
 
        /**
@@ -558,7 +559,7 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
         * @throws InvalidFormatException
         *             Throws if
         */
-       private void loadRelationships() throws InvalidFormatException {
+       /* package */ void loadRelationships() throws InvalidFormatException {
                if (this._relationships == null && !this._isRelationshipPart) {
                        this.throwExceptionIfRelationship();
                        _relationships = new PackageRelationshipCollection(this);
index 953af91eeb081c89429ac95380290b7d7ccc4663..8e5cf9163e2abdb58de61951899b93dc5df33bc2 100644 (file)
@@ -45,12 +45,12 @@ public final class PackageRelationshipCollection implements
     /**
      * Package relationships ordered by ID.
      */
-    private TreeMap<String, PackageRelationship> relationshipsByID;
+    private final TreeMap<String, PackageRelationship> relationshipsByID = new TreeMap<>();
 
     /**
      * Package relationships ordered by type.
      */
-    private TreeMap<String, PackageRelationship> relationshipsByType;
+    private final TreeMap<String, PackageRelationship> relationshipsByType = new TreeMap<>();
 
     /**
      * A lookup of internal relationships to avoid
@@ -88,8 +88,6 @@ public final class PackageRelationshipCollection implements
      * Constructor.
      */
     PackageRelationshipCollection() {
-        relationshipsByID = new TreeMap<>();
-        relationshipsByType = new TreeMap<>();
     }
 
     /**
@@ -149,8 +147,6 @@ public final class PackageRelationshipCollection implements
      */
     public PackageRelationshipCollection(OPCPackage container, PackagePart part)
             throws InvalidFormatException {
-        this();
-
         if (container == null)
             throw new IllegalArgumentException("container needs to be specified");
 
index 7dcf8b5f9156bfc7be22b22057a68ac18e41180d..37750afc6166aff8988e20edb8dc8e296e9e7305 100644 (file)
 
 package org.apache.poi.openxml4j.opc;
 
+import static org.apache.poi.openxml4j.opc.ContentTypes.RELATIONSHIPS_PART;
+import static org.apache.poi.openxml4j.opc.internal.ContentTypeManager.CONTENT_TYPES_PART_NAME;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Enumeration;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
+import org.apache.poi.UnsupportedFileFormatException;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
 import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
@@ -41,10 +47,10 @@ import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
 import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
 import org.apache.poi.openxml4j.opc.internal.ZipHelper;
 import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
+import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream;
 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.ThresholdInputStream;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
@@ -95,12 +101,12 @@ public final class ZipPackage extends OPCPackage {
      */
     ZipPackage(InputStream in, PackageAccess access) throws IOException {
         super(access);
-        ThresholdInputStream zis = ZipHelper.openZipStream(in);
+        ZipArchiveThresholdInputStream zis = ZipHelper.openZipStream(in);
         try {
             this.zipArchive = new ZipInputStreamZipEntrySource(zis);
         } catch (final IOException e) {
             IOUtils.closeQuietly(zis);
-            throw new IOException("Failed to read zip entry source", e);
+            throw e;
         }
     }
 
@@ -138,6 +144,9 @@ public final class ZipPackage extends OPCPackage {
             if (access == PackageAccess.WRITE) {
                 throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e);
             }
+            if ("java.util.zip.ZipException: archive is not a ZIP archive".equals(e.getMessage())) {
+                throw new NotOfficeXmlFileException("archive is not a ZIP archive", e);
+            }
             LOG.log(POILogger.ERROR, "Error in zip file "+file+" - falling back to stream processing (i.e. ignoring zip central directory)");
             ze = openZipEntrySourceStream(file);
         }
@@ -159,19 +168,19 @@ public final class ZipPackage extends OPCPackage {
         try {
             // read from the file input stream
             return openZipEntrySourceStream(fis);
+        } catch (final InvalidOperationException|UnsupportedFileFormatException e) {
+            // abort: close the zip input stream
+            IOUtils.closeQuietly(fis);
+            throw e;
         } catch (final Exception e) {
             // abort: close the file input stream
             IOUtils.closeQuietly(fis);
-            if (e instanceof InvalidOperationException) {
-                throw (InvalidOperationException)e;
-            } else {
-                throw new InvalidOperationException("Failed to read the file input stream from file: '" + file + "'", e);
-            }
+            throw new InvalidOperationException("Failed to read the file input stream from file: '" + file + "'", e);
         }
     }
     
     private static ZipEntrySource openZipEntrySourceStream(FileInputStream fis) throws InvalidOperationException {
-        final ThresholdInputStream zis;
+        final ZipArchiveThresholdInputStream zis;
         // Acquire a resource that is needed to read the next level of openZipEntrySourceStream
         try {
             // open the zip input stream
@@ -185,18 +194,18 @@ public final class ZipPackage extends OPCPackage {
         try {
             // read from the zip input stream
             return openZipEntrySourceStream(zis);
+        } catch (final InvalidOperationException|UnsupportedFileFormatException e) {
+            // abort: close the zip input stream
+            IOUtils.closeQuietly(zis);
+            throw e;
         } catch (final Exception e) {
             // abort: close the zip input stream
             IOUtils.closeQuietly(zis);
-            if (e instanceof InvalidOperationException) {
-                throw (InvalidOperationException)e;
-            } else {
-                throw new InvalidOperationException("Failed to read the zip entry source stream", e);
-            }
+            throw new InvalidOperationException("Failed to read the zip entry source stream", e);
         }
     }
     
-    private static ZipEntrySource openZipEntrySourceStream(ThresholdInputStream zis) throws InvalidOperationException {
+    private static ZipEntrySource openZipEntrySourceStream(ZipArchiveThresholdInputStream zis) throws InvalidOperationException {
         // Acquire the final level resource. If this is acquired successfully, the zip package was read successfully from the input stream
         try {
             // open the zip entry source stream
@@ -224,149 +233,115 @@ public final class ZipPackage extends OPCPackage {
     /**
      * Retrieves the parts from this package. We assume that the package has not
      * been yet inspect to retrieve all the parts, this method will open the
-     * archive and look for all parts contain inside it. If the package part
-     * list is not empty, it will be emptied.
+     * archive and look for all parts contain inside it.
      *
      * @return All parts contain in this package.
      * @throws InvalidFormatException if the package is not valid.
      */
     @Override
-    protected PackagePart[] getPartsImpl() throws InvalidFormatException {
-        if (this.partList == null) {
-            // The package has just been created, we create an empty part
-            // list.
-            this.partList = new PackagePartCollection();
-        }
+    protected PackagePartCollection getPartsImpl() throws InvalidFormatException {
+        final PackagePartCollection newPartList = new PackagePartCollection();
 
-        if (this.zipArchive == null) {
-            return this.partList.sortedValues().toArray(
-                    new PackagePart[this.partList.size()]);
+        if (zipArchive == null) {
+            return newPartList;
         }
 
         // First we need to parse the content type part
-        Enumeration<? extends ZipEntry> entries = this.zipArchive.getEntries();
-        while (entries.hasMoreElements()) {
-            ZipEntry entry = entries.nextElement();
-            if (entry.getName().equalsIgnoreCase(
-                    ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
-                try {
-                    this.contentTypeManager = new ZipContentTypeManager(
-                            getZipArchive().getInputStream(entry), this);
-                } catch (IOException e) {
-                    throw new InvalidFormatException(e.getMessage(), e);
-                }
-                break;
+        final ZipEntry contentTypeEntry =
+                zipArchive.getEntry(CONTENT_TYPES_PART_NAME);
+        if (contentTypeEntry != null) {
+            try {
+                this.contentTypeManager = new ZipContentTypeManager(
+                        zipArchive.getInputStream(contentTypeEntry), this);
+            } catch (IOException e) {
+                throw new InvalidFormatException(e.getMessage(), e);
             }
-        }
-
-        // At this point, we should have loaded the content type part
-        if (this.contentTypeManager == null) {
+        } else {
             // Is it a different Zip-based format?
-            int numEntries = 0;
-            boolean hasMimetype = false;
-            boolean hasSettingsXML = false;
-            entries = this.zipArchive.getEntries();
-            while (entries.hasMoreElements()) {
-                final ZipEntry entry = entries.nextElement();
-                final String name = entry.getName();
-                if (MIMETYPE.equals(name)) {
-                    hasMimetype = true;
-                }
-                if (SETTINGS_XML.equals(name)) {
-                    hasSettingsXML = true;
-                }
-                numEntries++;
-            }
+            final boolean hasMimetype = zipArchive.getEntry(MIMETYPE) != null;
+            final boolean hasSettingsXML = zipArchive.getEntry(SETTINGS_XML) != null;
             if (hasMimetype && hasSettingsXML) {
                 throw new ODFNotOfficeXmlFileException(
-                   "The supplied data appears to be in ODF (Open Document) Format. " +
-                   "Formats like these (eg ODS, ODP) are not supported, try Apache ODFToolkit");
+                        "The supplied data appears to be in ODF (Open Document) Format. " +
+                                "Formats like these (eg ODS, ODP) are not supported, try Apache ODFToolkit");
             }
-            if (numEntries == 0) {
+            if (!zipArchive.getEntries().hasMoreElements()) {
                 throw new NotOfficeXmlFileException(
-                   "No valid entries or contents found, this is not a valid OOXML " +
-                   "(Office Open XML) file");
+                        "No valid entries or contents found, this is not a valid OOXML " +
+                                "(Office Open XML) file");
             }
-
             // Fallback exception
             throw new InvalidFormatException(
-                "Package should contain a content type part [M1.13]");
+                    "Package should contain a content type part [M1.13]");
         }
 
         // Now create all the relationships
         // (Need to create relationships before other
         //  parts, otherwise we might create a part before
         //  its relationship exists, and then it won't tie up)
-        entries = this.zipArchive.getEntries();
-        while (entries.hasMoreElements()) {
-            ZipEntry entry = entries.nextElement();
-            PackagePartName partName = buildPartName(entry);
-            if(partName == null) {
-                continue;
-            }
+        final List<EntryTriple> entries =
+                Collections.list(zipArchive.getEntries()).stream()
+                        .map(zae -> new EntryTriple(zae, contentTypeManager))
+                        .filter(mm -> mm.partName != null)
+                        .sorted()
+                        .collect(Collectors.toList());
+
+        for (final EntryTriple et : entries) {
+            et.register(newPartList);
+        }
 
-            // Only proceed for Relationships at this stage
-            String contentType = contentTypeManager.getContentType(partName);
-            if (contentType != null && contentType.equals(ContentTypes.RELATIONSHIPS_PART)) {
-                try {
-                    PackagePart part = new ZipPackagePart(this, entry, partName, contentType);
-                    partList.put(partName, part);
-                } catch (InvalidOperationException e) {
-                    throw new InvalidFormatException(e.getMessage(), e);
-                }
+        return newPartList;
+    }
+
+    private class EntryTriple implements Comparable<EntryTriple> {
+        final ZipEntry zipArchiveEntry;
+        final PackagePartName partName;
+        final String contentType;
+
+        EntryTriple(final ZipEntry zipArchiveEntry, final ContentTypeManager contentTypeManager) {
+            this.zipArchiveEntry = zipArchiveEntry;
+
+            final String entryName = zipArchiveEntry.getName();
+            PackagePartName ppn = null;
+            try {
+                // We get an error when we parse [Content_Types].xml
+                // because it's not a valid URI.
+                ppn = (CONTENT_TYPES_PART_NAME.equalsIgnoreCase(entryName)) ? null
+                    : PackagingURIHelper.createPartName(ZipHelper.getOPCNameFromZipItemName(entryName));
+            } catch (Exception e) {
+                // We assume we can continue, even in degraded mode ...
+                LOG.log(POILogger.WARN,"Entry " + entryName + " is not valid, so this part won't be add to the package.", e);
             }
+
+            this.partName = ppn;
+            this.contentType = (ppn == null) ? null : contentTypeManager.getContentType(partName);
         }
 
-        // Then we can go through all the other parts
-        entries = this.zipArchive.getEntries();
-        while (entries.hasMoreElements()) {
-            ZipEntry entry = entries.nextElement();
-            PackagePartName partName = buildPartName(entry);
-            if(partName == null) {
-                continue;
+        void register(final PackagePartCollection partList) throws InvalidFormatException {
+            if (contentType == null) {
+                throw new InvalidFormatException("The part " + partName.getURI().getPath() + " does not have any " +
+                        "content type ! Rule: Package require content types when retrieving a part from a package. [M.1.14]");
             }
 
-            String contentType = contentTypeManager.getContentType(partName);
-            if (contentType != null && contentType.equals(ContentTypes.RELATIONSHIPS_PART)) {
-                // Already handled
-            }
-            else if (contentType != null) {
-                try {
-                    PackagePart part = new ZipPackagePart(this, entry, partName, contentType);
-                    partList.put(partName, part);
-                } catch (InvalidOperationException e) {
-                    throw new InvalidFormatException(e.getMessage(), e);
-                }
-            } else {
+            if (partList.containsKey(partName)) {
                 throw new InvalidFormatException(
-                    "The part " + partName.getURI().getPath()
-                    + " does not have any content type ! Rule: Package require content types when retrieving a part from a package. [M.1.14]");
+                    "A part with the name '"+partName+"' already exist : Packages shall not contain equivalent part names " +
+                    "and package implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");
             }
-        }
-
-        return partList.sortedValues().toArray(new PackagePart[partList.size()]);
-    }
 
-    /**
-     * Builds a PackagePartName for the given ZipEntry,
-     *  or null if it's the content types / invalid part
-     */
-    private PackagePartName buildPartName(ZipEntry entry) {
-        try {
-            // We get an error when we parse [Content_Types].xml
-            // because it's not a valid URI.
-            if (entry.getName().equalsIgnoreCase(
-                    ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
-                return null;
+            try {
+                partList.put(partName, new ZipPackagePart(ZipPackage.this, zipArchiveEntry, partName, contentType, false));
+            } catch (InvalidOperationException e) {
+                throw new InvalidFormatException(e.getMessage(), e);
             }
-            return PackagingURIHelper.createPartName(ZipHelper
-                    .getOPCNameFromZipItemName(entry.getName()));
-        } catch (Exception e) {
-            // We assume we can continue, even in degraded mode ...
-            LOG.log(POILogger.WARN,"Entry "
-                                      + entry.getName()
-                                      + " is not valid, so this part won't be add to the package.", e);
-            return null;
+        }
+
+        @Override
+        public int compareTo(EntryTriple o) {
+            final int contentTypeOrder1 = RELATIONSHIPS_PART.equals(contentType) ? -1 : 1;
+            final int contentTypeOrder2 = RELATIONSHIPS_PART.equals(o.contentType) ? -1 : 1;
+            final int cmpCT = Integer.compare(contentTypeOrder1, contentTypeOrder2);
+            return cmpCT != 0 ? cmpCT : partName.compareTo(o.partName);
         }
     }
 
@@ -494,21 +469,6 @@ public final class ZipPackage extends OPCPackage {
                }
        }
 
-    /**
-     * Implement the getPart() method to retrieve a part from its URI in the
-     * current package
-     *
-     *
-     * @see #getPart(PackageRelationship)
-     */
-    @Override
-    protected PackagePart getPartImpl(PackagePartName partName) {
-        if (partList.containsKey(partName)) {
-            return partList.get(partName);
-        }
-        return null;
-    }
-
        /**
         * Save this package into the specified stream
         *
@@ -523,13 +483,8 @@ public final class ZipPackage extends OPCPackage {
                // Check that the document was open in write mode
                throwExceptionIfReadOnly();
 
-               final ZipOutputStream zos;
-               try {
-                       if (!(outputStream instanceof ZipOutputStream)) {
-                zos = new ZipOutputStream(outputStream);
-            } else {
-                zos = (ZipOutputStream) outputStream;
-            }
+               try (final ZipOutputStream zos = (outputStream instanceof ZipOutputStream)
+                ? (ZipOutputStream) outputStream : new ZipOutputStream(outputStream)) {
 
                        // If the core properties part does not exist in the part list,
                        // we save it as well
@@ -574,20 +529,14 @@ public final class ZipPackage extends OPCPackage {
 
                                final PackagePartName ppn = part.getPartName();
                                LOG.log(POILogger.DEBUG,"Save part '" + ZipHelper.getZipItemNameFromOPCName(ppn.getName()) + "'");
-                               PartMarshaller marshaller = partMarshallers.get(part._contentType);
-                               String errMsg = "The part " + ppn.getURI() + " failed to be saved in the stream with marshaller ";
-
-                               if (marshaller != null) {
-                                       if (!marshaller.marshall(part, zos)) {
-                                               throw new OpenXML4JException(errMsg + marshaller);
-                                       }
-                               } else {
-                                       if (!defaultPartMarshaller.marshall(part, zos)) {
-                        throw new OpenXML4JException(errMsg + defaultPartMarshaller);
-                    }
-                               }
+                               final PartMarshaller marshaller = partMarshallers.get(part._contentType);
+
+                               final PartMarshaller pm = (marshaller != null) ? marshaller : defaultPartMarshaller;
+                if (!pm.marshall(part, zos)) {
+                    String errMsg = "The part " + ppn.getURI() + " failed to be saved in the stream with marshaller ";
+                    throw new OpenXML4JException(errMsg + pm);
+                }
                        }
-                       zos.close();
                } catch (OpenXML4JRuntimeException e) {
                        // no need to wrap this type of Exception
                        throw e;
index 58bbbfe225455d5e1f8f9ab7dd512fe1f7c1abc6..57e649c281e969f9e92da5818772a8f68b4c5998 100644 (file)
@@ -25,6 +25,7 @@ import java.util.zip.ZipEntry;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.internal.ContentType;
 import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
 import org.apache.poi.util.NotImplemented;
 
@@ -48,11 +49,11 @@ public class ZipPackagePart extends PackagePart {
         * @param container
         *            The container package.
         * @param partName
-        *            Part name.
+        *            The part name.
         * @param contentType
         *            Content type.
         * @throws InvalidFormatException
-        *             Throws if the content of this part invalid.
+        *             Throws if the content of this part is invalid.
         */
        public ZipPackagePart(OPCPackage container, PackagePartName partName,
                        String contentType) throws InvalidFormatException {
@@ -73,10 +74,10 @@ public class ZipPackagePart extends PackagePart {
         * @throws InvalidFormatException
         *             Throws if the content of this part is invalid.
         */
-       public ZipPackagePart(OPCPackage container, ZipEntry zipEntry,
-                       PackagePartName partName, String contentType)
+       /* package */ ZipPackagePart(OPCPackage container, ZipEntry zipEntry,
+                                                 PackagePartName partName, String contentType, boolean loadRelationships)
                        throws InvalidFormatException {
-               super(container, partName, contentType);
+               super(container, partName, new ContentType(contentType), loadRelationships);
                this.zipEntry = zipEntry;
        }
 
index 70bdb39e12ea902d716f0b3c9869b3a74bc43e5b..a51d6353b4ab443effa9485d8e5de7630d77c4ed 100644 (file)
@@ -57,26 +57,23 @@ public class ZipContentTypeManager extends ContentTypeManager {
        @SuppressWarnings("resource")
     @Override
        public boolean saveImpl(Document content, OutputStream out) {
-               ZipOutputStream zos = null;
-               if (out instanceof ZipOutputStream)
-                       zos = (ZipOutputStream) out;
-               else
-                       zos = new ZipOutputStream(out);
+               final ZipOutputStream zos = (out instanceof ZipOutputStream)
+                               ? (ZipOutputStream) out : new ZipOutputStream(out);
 
                ZipEntry partEntry = new ZipEntry(CONTENT_TYPES_PART_NAME);
                try {
                        // Referenced in ZIP
                        zos.putNextEntry(partEntry);
-                       // Saving data in the ZIP file
-                       if (!StreamHelper.saveXmlInStream(content, zos)) {
-                           return false;
+                       try {
+                               // Saving data in the ZIP file
+                               return StreamHelper.saveXmlInStream(content, zos);
+                       } finally {
+                               zos.closeEntry();
                        }
-                       zos.closeEntry();
                } catch (IOException ioe) {
                        logger.log(POILogger.ERROR, "Cannot write: " + CONTENT_TYPES_PART_NAME
                                        + " in Zip !", ioe);
                        return false;
                }
-               return true;
        }
 }
index a5c741e287e1acfdc261472f66d1d97e32e13669..cd38adcea77d97bf58a98a6cf3a304bcf0010a8b 100644 (file)
@@ -24,9 +24,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Enumeration;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 import java.util.zip.ZipInputStream;
 
 import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
@@ -34,8 +32,8 @@ import org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException;
 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.ZipArchiveThresholdInputStream;
 import org.apache.poi.openxml4j.util.ZipSecureFile;
-import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
 import org.apache.poi.poifs.filesystem.FileMagic;
 import org.apache.poi.util.Internal;
 
@@ -72,24 +70,6 @@ public final class ZipHelper {
         return new ZipEntry(corePropsRel.getTargetURI().getPath());
     }
 
-    /**
-     * Retrieve the Zip entry of the content types part.
-     */
-    public static ZipEntry getContentTypeZipEntry(ZipPackage pkg) {
-        Enumeration<? extends ZipEntry> entries = pkg.getZipArchive().getEntries();
-
-        // Enumerate through the Zip entries until we find the one named
-        // '[Content_Types].xml'.
-        while (entries.hasMoreElements()) {
-            ZipEntry entry = entries.nextElement();
-            if (entry.getName().equals(
-                    ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
-                return entry;
-            }
-        }
-        return null;
-    }
-
     /**
      * Convert a zip name into an OPC name by adding a leading forward slash to
      * the specified item name.
@@ -158,7 +138,7 @@ public final class ZipHelper {
      * Warning - this will consume the first few bytes of the stream,
      *  you should push-back or reset the stream after use!
      */
-    public static void verifyZipHeader(InputStream stream) throws NotOfficeXmlFileException, IOException {
+    private static void verifyZipHeader(InputStream stream) throws NotOfficeXmlFileException, IOException {
         InputStream is = FileMagic.prepareToCheckMagic(stream);
         FileMagic fm = FileMagic.valueOf(is);
 
@@ -191,14 +171,14 @@ public final class ZipHelper {
      * @return The zip stream freshly open.
      */
     @SuppressWarnings("resource")
-    public static ThresholdInputStream openZipStream(InputStream stream) throws IOException {
+    public static ZipArchiveThresholdInputStream openZipStream(InputStream stream) throws IOException {
         // Peek at the first few bytes to sanity check
         InputStream checkedStream = FileMagic.prepareToCheckMagic(stream);
         verifyZipHeader(checkedStream);
         
         // Open as a proper zip stream
         InputStream zis = new ZipInputStream(checkedStream);
-        return ZipSecureFile.addThreshold(zis);
+        return new ZipArchiveThresholdInputStream(zis);
     }
 
     /**
@@ -211,7 +191,7 @@ public final class ZipHelper {
      * @throws IOException if the zip file cannot be opened or closed to read the header signature
      * @throws NotOfficeXmlFileException if stream does not start with zip header signature
      */
-    public static ZipFile openZipFile(File file) throws IOException, NotOfficeXmlFileException {
+    public static ZipSecureFile openZipFile(File file) throws IOException, NotOfficeXmlFileException {
         if (!file.exists()) {
             throw new FileNotFoundException("File does not exist");
         }
@@ -235,7 +215,7 @@ public final class ZipHelper {
      *            The file path.
      * @return The zip archive freshly open.
      */
-    public static ZipFile openZipFile(String path) throws IOException {
+    public static ZipSecureFile openZipFile(String path) throws IOException {
         return openZipFile(new File(path));
     }
 }
index 6b4b215518550d6f39b5db8545d23ddcd4bef5c4..7b50139604c0b12758d1ab85237a4f8132833bea 100644 (file)
@@ -49,15 +49,15 @@ public final class ZipPackagePropertiesMarshaller extends PackagePropertiesMarsh
                try {
                        // Save in ZIP
                        zos.putNextEntry(ctEntry); // Add entry in ZIP
-                       super.marshall(part, out); // Marshall the properties inside a XML
-                       // Document
-                       if (!StreamHelper.saveXmlInStream(xmlDoc, out)) {
-                               return false;
+                       try {
+                               super.marshall(part, out); // Marshall the properties inside a XML
+                               // Document
+                               return StreamHelper.saveXmlInStream(xmlDoc, out);
+                       } finally {
+                               zos.closeEntry();
                        }
-                       zos.closeEntry();
                } catch (IOException e) {
                        throw new OpenXML4JException(e.getLocalizedMessage(), e);
                }
-               return true;
        }
 }
index 6836d3c55a4aeddcdd64d9317a8735a7ba9b96a5..6ba8339d1a2b5261dc535aebacfd94f4e035ce5a 100644 (file)
@@ -36,6 +36,7 @@ import org.apache.poi.openxml4j.opc.TargetMode;
 import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
 import org.apache.poi.openxml4j.opc.internal.ZipHelper;
 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.xssf.usermodel.XSSFRelation;
@@ -47,7 +48,6 @@ import org.w3c.dom.Element;
  */
 public final class ZipPartMarshaller implements PartMarshaller {
        private final static POILogger logger = POILogFactory.getLogger(ZipPartMarshaller.class);
-       private final static int READ_WRITE_FILE_BUFFER_SIZE = 8192;
 
        /**
         * Save the specified part.
@@ -80,17 +80,11 @@ public final class ZipPartMarshaller implements PartMarshaller {
                        zos.putNextEntry(partEntry);
 
                        // Saving data in the ZIP file
-                       InputStream ins = part.getInputStream();
-                       byte[] buff = new byte[READ_WRITE_FILE_BUFFER_SIZE];
-                       while (ins.available() > 0) {
-                               int resultRead = ins.read(buff);
-                               if (resultRead == -1) {
-                                       // End of file reached
-                                       break;
-                               }
-                               zos.write(buff, 0, resultRead);
+                       try (final InputStream ins = part.getInputStream()) {
+                               IOUtils.copy(ins, zos);
+                       } finally {
+                               zos.closeEntry();
                        }
-                       zos.closeEntry();
                } catch (IOException ioe) {
                        logger.log(POILogger.ERROR,"Cannot write: " + part.getPartName() + ": in ZIP",
                                        ioe);
@@ -178,14 +172,14 @@ public final class ZipPartMarshaller implements PartMarshaller {
                                relPartName.getURI().toASCIIString()).getPath());
                try {
                        zos.putNextEntry(ctEntry);
-                       if (!StreamHelper.saveXmlInStream(xmlOutDoc, zos)) {
-                               return false;
+                       try {
+                               return StreamHelper.saveXmlInStream(xmlOutDoc, zos);
+                       } finally {
+                               zos.closeEntry();
                        }
-                       zos.closeEntry();
                } catch (IOException e) {
                        logger.log(POILogger.ERROR,"Cannot create zip entry " + relPartName, e);
                        return false;
                }
-               return true; // success
        }
 }
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/util/ZipArchiveFakeEntry.java b/src/ooxml/java/org/apache/poi/openxml4j/util/ZipArchiveFakeEntry.java
new file mode 100644 (file)
index 0000000..347ea50
--- /dev/null
@@ -0,0 +1,53 @@
+/* ====================================================================
+   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.openxml4j.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+
+import org.apache.poi.util.IOUtils;
+
+
+/**
+ * So we can close the real zip entry and still
+ *  effectively work with it.
+ * Holds the (decompressed!) data in memory, so
+ *  close this as soon as you can!
+ */
+/* package */ class ZipArchiveFakeEntry extends ZipEntry {
+    private final byte[] data;
+
+    ZipArchiveFakeEntry(ZipEntry entry, InputStream inp) throws IOException {
+        super(entry.getName());
+
+        final long entrySize = entry.getSize();
+
+        if (entrySize < -1 || entrySize>=Integer.MAX_VALUE) {
+            throw new IOException("ZIP entry size is too large or invalid");
+        }
+
+        // Grab the de-compressed contents for later
+        data = (entrySize == -1) ? IOUtils.toByteArray(inp) : IOUtils.toByteArray(inp, (int)entrySize);
+    }
+
+    public InputStream getInputStream() {
+        return new ByteArrayInputStream(data);
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java b/src/ooxml/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java
new file mode 100644 (file)
index 0000000..447baa0
--- /dev/null
@@ -0,0 +1,243 @@
+/* ====================================================================
+   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.openxml4j.util;
+
+import static org.apache.poi.openxml4j.util.ZipSecureFile.MAX_ENTRY_SIZE;
+import static org.apache.poi.openxml4j.util.ZipSecureFile.MIN_INFLATE_RATIO;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Locale;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.SuppressForbidden;
+
+public class ZipArchiveThresholdInputStream extends PushbackInputStream {
+    private static final POILogger LOG = POILogFactory.getLogger(ZipArchiveThresholdInputStream.class);
+
+    // don't alert for expanded sizes smaller than 100k
+    private static final long GRACE_ENTRY_SIZE = 100*1024L;
+
+    private static final String MAX_ENTRY_SIZE_MSG =
+        "Zip bomb detected! The file would exceed the max size of the expanded data in the zip-file.\n" +
+        "This may indicates that the file is used to inflate memory usage and thus could pose a security risk.\n" +
+        "You can adjust this limit via ZipSecureFile.setMaxEntrySize() if you need to work with files which are very large.\n" +
+        "Counter: %d, cis.counter: %d\n" +
+        "Limits: MAX_ENTRY_SIZE: %d, Entry: %s";
+
+    private static final String MIN_INFLATE_RATIO_MSG =
+        "Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.\n" +
+        "This may indicate that the file is used to inflate memory usage and thus could pose a security risk.\n" +
+        "You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.\n" +
+        "Counter: %d, cis.counter: %d, ratio: %f\n" +
+        "Limits: MIN_INFLATE_RATIO: %f, Entry: %s";
+
+    private static final String SECURITY_BLOCKED =
+        "SecurityManager doesn't allow manipulation via reflection for zipbomb detection - continue with original input stream";
+
+    /**
+     * the reference to the current entry is only used for a more detailed log message in case of an error
+     */
+    private ZipEntry entry;
+
+    private long counter;
+    private long markPos;
+    private final ZipArchiveThresholdInputStream cis;
+    private boolean guardState = true;
+
+
+    public ZipArchiveThresholdInputStream(final InputStream zipIS) throws IOException {
+        super(zipIS);
+        if (zipIS instanceof InflaterInputStream) {
+            cis = AccessController.doPrivileged(inject(zipIS));
+        } else {
+            // the inner stream is a ZipFileInputStream, i.e. the data wasn't compressed
+            cis = null;
+        }
+    }
+
+    private ZipArchiveThresholdInputStream(InputStream is, ZipArchiveThresholdInputStream cis) {
+        super(is);
+        this.cis = cis;
+    }
+
+    @SuppressForbidden
+    private static PrivilegedAction<ZipArchiveThresholdInputStream> inject(final InputStream zipIS) {
+        return () -> {
+            try {
+                final Field f = FilterInputStream.class.getDeclaredField("in");
+                f.setAccessible(true);
+                final InputStream oldInner = (InputStream)f.get(zipIS);
+                final ZipArchiveThresholdInputStream inner = new ZipArchiveThresholdInputStream(oldInner, null);
+                f.set(zipIS, inner);
+                return inner;
+            } catch (Exception ex) {
+                LOG.log(POILogger.WARN, SECURITY_BLOCKED, ex);
+            }
+            return null;
+        };
+    }
+
+    @Override
+    public int read() throws IOException {
+        int b = in.read();
+        if (b > -1) {
+            advance(1);
+        }
+        return b;
+    }
+
+    @Override
+    public int read(byte b[], int off, int len) throws IOException {
+        int cnt = in.read(b, off, len);
+        if (cnt > -1) {
+            advance(cnt);
+        }
+        return cnt;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        long s = in.skip(n);
+        counter += s;
+        return s;
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        counter = markPos;
+        super.reset();
+    }
+
+    /**
+     * De-/activate threshold check.
+     * A disabled guard might make sense, when POI is processing its own temporary data (see #59743)
+     *
+     * @param guardState {@code true} (= default) enables the threshold check
+     */
+    public void setGuardState(boolean guardState) {
+        this.guardState = guardState;
+    }
+
+    public void advance(int advance) throws IOException {
+        counter += advance;
+
+        if (!guardState) {
+            return;
+        }
+
+        final String entryName = entry == null ? "not set" : entry.getName();
+        final long cisCount = (cis == null ? 0 : cis.counter);
+
+        // check the file size first, in case we are working on uncompressed streams
+        if(counter > MAX_ENTRY_SIZE) {
+            throw new IOException(String.format(Locale.ROOT, MAX_ENTRY_SIZE_MSG, counter, cisCount, MAX_ENTRY_SIZE, entryName));
+        }
+
+        // no expanded size?
+        if (cis == null) {
+            return;
+        }
+
+        // don't alert for small expanded size
+        if (counter <= GRACE_ENTRY_SIZE) {
+            return;
+        }
+
+        double ratio = (double)cis.counter/(double)counter;
+        if (ratio >= MIN_INFLATE_RATIO) {
+            return;
+        }
+
+        // one of the limits was reached, report it
+        throw new IOException(String.format(Locale.ROOT, MIN_INFLATE_RATIO_MSG, counter, cisCount, ratio, MIN_INFLATE_RATIO, entryName));
+    }
+
+    public ZipEntry getNextEntry() throws IOException {
+        if (!(in instanceof ZipInputStream)) {
+            throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
+        }
+        counter = 0;
+        return ((ZipInputStream)in).getNextEntry();
+    }
+
+    public void closeEntry() throws IOException {
+        if (!(in instanceof ZipInputStream)) {
+            throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
+        }
+        counter = 0;
+        ((ZipInputStream)in).closeEntry();
+    }
+
+    @Override
+    public void unread(int b) throws IOException {
+        if (!(in instanceof PushbackInputStream)) {
+            throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
+        }
+        if (--counter < 0) {
+            counter = 0;
+        }
+        ((PushbackInputStream)in).unread(b);
+    }
+
+    @Override
+    public void unread(byte[] b, int off, int len) throws IOException {
+        if (!(in instanceof PushbackInputStream)) {
+            throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
+        }
+        counter -= len;
+        if (--counter < 0) {
+            counter = 0;
+        }
+        ((PushbackInputStream)in).unread(b, off, len);
+    }
+
+    @Override
+    @SuppressForbidden("just delegating")
+    public int available() throws IOException {
+        return in.available();
+    }
+
+    @Override
+    public boolean markSupported() {
+        return in.markSupported();
+    }
+
+    @Override
+    public synchronized void mark(int readlimit) {
+        markPos = counter;
+        in.mark(readlimit);
+    }
+
+    /**
+     * Sets the zip entry for a detailed logging
+     * @param entry the entry
+     */
+    void setEntry(ZipEntry entry) {
+        this.entry = entry;
+    }
+}
\ No newline at end of file
index 8ce82325c570593881e5aee5914c1818db1dad27..65ba94294123e56e01bc9d0780be62667774932d 100644 (file)
@@ -33,23 +33,32 @@ public interface ZipEntrySource extends Closeable {
        /**
         * Returns an Enumeration of all the Entries
         */
-       public Enumeration<? extends ZipEntry> getEntries();
-       
+       Enumeration<? extends ZipEntry> getEntries();
+
+       /**
+        * Return an entry by its path
+        * @param path the path in unix-notation
+        * @return the entry or {@code null} if not found
+        *
+        * @since POI 4.0.0
+        */
+       ZipEntry getEntry(String path);
+
        /**
         * Returns an InputStream of the decompressed 
         *  data that makes up the entry
         */
-       public InputStream getInputStream(ZipEntry entry) throws IOException;
+       InputStream getInputStream(ZipEntry entry) throws IOException;
        
        /**
         * Indicates we are done with reading, and 
         *  resources may be freed
         */
        @Override
-       public void close() throws IOException;
+       void close() throws IOException;
        
        /**
         * Has close been called already?
         */
-       public boolean isClosed();
+       boolean isClosed();
 }
index 09317d361e200c5be954006ab6551e800197b685..221db9a9ec3baad8441d91e398c1cca3c4905460 100644 (file)
@@ -16,6 +16,9 @@
 ==================================================================== */
 package org.apache.poi.openxml4j.util;
 
+import static org.apache.commons.collections4.IteratorUtils.asIterable;
+import static org.apache.commons.collections4.IteratorUtils.asIterator;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Enumeration;
@@ -33,16 +36,20 @@ public class ZipFileZipEntrySource implements ZipEntrySource {
       this.zipArchive = zipFile;
    }
 
+   @Override
    public void close() throws IOException {
       if(zipArchive != null) {
          zipArchive.close();
       }
       zipArchive = null;
    }
+
+   @Override
    public boolean isClosed() {
        return (zipArchive == null);
    }
 
+   @Override
    public Enumeration<? extends ZipEntry> getEntries() {
       if (zipArchive == null)
          throw new IllegalStateException("Zip File is closed");
@@ -50,10 +57,30 @@ public class ZipFileZipEntrySource implements ZipEntrySource {
       return zipArchive.entries();
    }
 
+   @Override
    public InputStream getInputStream(ZipEntry entry) throws IOException {
       if (zipArchive == null)
          throw new IllegalStateException("Zip File is closed");
       
       return zipArchive.getInputStream(entry);
    }
+
+   @Override
+   public ZipEntry getEntry(final String path) {
+      String normalizedPath = path.replace('\\', '/');
+
+      final ZipEntry entry = zipArchive.getEntry(normalizedPath);
+      if (entry != null) {
+         return entry;
+      }
+
+      // the opc spec allows case-insensitive filename matching (see #49609)
+      for (final ZipEntry ze : asIterable(asIterator(zipArchive.entries()))) {
+         if (normalizedPath.equalsIgnoreCase(ze.getName().replace('\\','/'))) {
+            return ze;
+         }
+      }
+
+      return null;
+   }
 }
index dfa9924617233d82e3765bd1f5e1f23d55b9b163..9c12431ada9dd319a04cf2816c73c45546c87ef3 100644 (file)
 ==================================================================== */
 package org.apache.poi.openxml4j.util;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.Enumeration;
-import java.util.Iterator;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.zip.ZipEntry;
 
-import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
+import org.apache.commons.collections4.IteratorUtils;
 
 /**
  * Provides a way to get at all the ZipEntries
@@ -36,7 +34,7 @@ import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
  *  done, to free up that memory!
  */
 public class ZipInputStreamZipEntrySource implements ZipEntrySource {
-       private ArrayList<FakeZipEntry> zipEntries;
+       private final Map<String, ZipArchiveFakeEntry> zipEntries = new HashMap<>();
        
        /**
         * Reads all the entries from the ZipInputStream 
@@ -44,100 +42,53 @@ public class ZipInputStreamZipEntrySource implements ZipEntrySource {
         * We'll then eat lots of memory, but be able to
         *  work with the entries at-will.
         */
-       public ZipInputStreamZipEntrySource(ThresholdInputStream inp) throws IOException {
-               zipEntries = new ArrayList<>();
-               
-               boolean going = true;
-               while(going) {
-                       ZipEntry zipEntry = inp.getNextEntry();
-                       if(zipEntry == null) {
-                               going = false;
-                       } else {
-                               FakeZipEntry entry = new FakeZipEntry(zipEntry, inp);
-                               inp.closeEntry();
-                               
-                               zipEntries.add(entry);
+       public ZipInputStreamZipEntrySource(ZipArchiveThresholdInputStream inp) throws IOException {
+               for (;;) {
+                       final ZipEntry zipEntry = inp.getNextEntry();
+                       if (zipEntry == null) {
+                               break;
                        }
+                       zipEntries.put(zipEntry.getName(), new ZipArchiveFakeEntry(zipEntry, inp));
                }
                inp.close();
        }
 
+       @Override
        public Enumeration<? extends ZipEntry> getEntries() {
-               return new EntryEnumerator();
+               return IteratorUtils.asEnumeration(zipEntries.values().iterator());
        }
-       
+
+       @Override
        public InputStream getInputStream(ZipEntry zipEntry) {
-           assert (zipEntry instanceof FakeZipEntry);
-               FakeZipEntry entry = (FakeZipEntry)zipEntry;
-               return entry.getInputStream();
+           assert (zipEntry instanceof ZipArchiveFakeEntry);
+               return ((ZipArchiveFakeEntry)zipEntry).getInputStream();
        }
-       
+
+       @Override
        public void close() {
                // Free the memory
-               zipEntries = null;
+               zipEntries.clear();
        }
+
+       @Override
        public boolean isClosed() {
-           return (zipEntries == null);
+           return zipEntries.isEmpty();
        }
-       
-       /**
-        * Why oh why oh why are Iterator and Enumeration
-        *  still not compatible?
-        */
-       private class EntryEnumerator implements Enumeration<ZipEntry> {
-               private Iterator<? extends ZipEntry> iterator;
-               
-               private EntryEnumerator() {
-                       iterator = zipEntries.iterator();
-               }
-               
-               public boolean hasMoreElements() {
-                       return iterator.hasNext();
-               }
 
-               public ZipEntry nextElement() {
-                       return iterator.next();
+       @Override
+       public ZipEntry getEntry(final String path) {
+               final String normalizedPath = path.replace('\\', '/');
+               final ZipEntry ze = zipEntries.get(normalizedPath);
+               if (ze != null) {
+                       return ze;
                }
-       }
-
-       /**
-        * So we can close the real zip entry and still
-        *  effectively work with it.
-        * Holds the (decompressed!) data in memory, so
-        *  close this as soon as you can! 
-        */
-       public static class FakeZipEntry extends ZipEntry {
-               private byte[] data;
-               
-               public FakeZipEntry(ZipEntry entry, InputStream inp) throws IOException {
-                       super(entry.getName());
-                       
-                       // Grab the de-compressed contents for later
-            ByteArrayOutputStream baos;
-
-            long entrySize = entry.getSize();
 
-            if (entrySize !=-1) {
-                if (entrySize>=Integer.MAX_VALUE) {
-                    throw new IOException("ZIP entry size is too large");
-                }
-
-                baos = new ByteArrayOutputStream((int) entrySize);
-            } else {
-                       baos = new ByteArrayOutputStream();
-            }
-
-                       byte[] buffer = new byte[4096];
-                       int read = 0;
-                       while( (read = inp.read(buffer)) != -1 ) {
-                               baos.write(buffer, 0, read);
+               for (final Map.Entry<String, ZipArchiveFakeEntry> fze : zipEntries.entrySet()) {
+                       if (normalizedPath.equalsIgnoreCase(fze.getKey())) {
+                               return fze.getValue();
                        }
-                       
-                       data = baos.toByteArray();
-               }
-               
-               public InputStream getInputStream() {
-                       return new ByteArrayInputStream(data);
                }
+
+               return null;
        }
 }
index 971ba366908b9d01bf015feaf31c99c49f2ef8b6..ae2bc192d42ce546570d887720b72a8337426947 100644 (file)
 package org.apache.poi.openxml4j.util;
 
 import java.io.File;
-import java.io.FilterInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.PushbackInputStream;
-import java.lang.reflect.Field;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.zip.InflaterInputStream;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
 
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
-import org.apache.poi.util.SuppressForbidden;
 
 /**
  * This class wraps a {@link ZipFile} in order to check the
  * entries for <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a>
- * while reading the archive.
- * If a {@link ZipInputStream} is directly used, the wrapper
- * can be applied via {@link #addThreshold(InputStream)}.
+ * while reading the archive.<p>
+ *
  * The alert limits can be globally defined via {@link #setMaxEntrySize(long)}
  * and {@link #setMinInflateRatio(double)}.
  */
 public class ZipSecureFile extends ZipFile {
-    private static final POILogger LOG = POILogFactory.getLogger(ZipSecureFile.class);
+    /* package */ static double MIN_INFLATE_RATIO = 0.01d;
+    /* package */ static long MAX_ENTRY_SIZE = 0xFFFFFFFFL;
     
-    private static double MIN_INFLATE_RATIO = 0.01d;
-    private static long MAX_ENTRY_SIZE = 0xFFFFFFFFL;
-    
-    // don't alert for expanded sizes smaller than 100k
-    private final static long GRACE_ENTRY_SIZE = 100*1024L;
-
-    // The default maximum size of extracted text 
+    // The default maximum size of extracted text
     private static long MAX_TEXT_SIZE = 10*1024*1024L;
+
+    private final String fileName;
     
     /**
      * Sets the ratio between de- and inflated bytes to detect zipbomb.
@@ -134,16 +118,14 @@ public class ZipSecureFile extends ZipFile {
         return MAX_TEXT_SIZE;
     }
 
-    public ZipSecureFile(File file, int mode) throws ZipException, IOException {
-        super(file, mode);
-    }
-
-    public ZipSecureFile(File file) throws ZipException, IOException {
+    public ZipSecureFile(File file) throws IOException {
         super(file);
+        this.fileName = file.getAbsolutePath();
     }
 
-    public ZipSecureFile(String name) throws ZipException, IOException {
+    public ZipSecureFile(String name) throws IOException {
         super(name);
+        this.fileName = new File(name).getAbsolutePath();
     }
 
     /**
@@ -156,176 +138,22 @@ public class ZipSecureFile extends ZipFile {
      * @param entry the zip file entry
      * @return the input stream for reading the contents of the specified
      * zip file entry.
-     * @throws ZipException if a ZIP format error has occurred
      * @throws IOException if an I/O error has occurred
      * @throws IllegalStateException if the zip file has been closed
      */
     @Override
     @SuppressWarnings("resource")
-    public InputStream getInputStream(ZipEntry entry) throws IOException {
-        InputStream zipIS = super.getInputStream(entry);
-        return addThreshold(zipIS);
+    public ZipArchiveThresholdInputStream getInputStream(ZipEntry entry) throws IOException {
+        ZipArchiveThresholdInputStream zatis = new ZipArchiveThresholdInputStream(super.getInputStream(entry));
+        zatis.setEntry(entry);
+        return zatis;
     }
 
-    public static ThresholdInputStream addThreshold(final InputStream zipIS) throws IOException {
-        ThresholdInputStream newInner;
-        if (zipIS instanceof InflaterInputStream) {
-            newInner = AccessController.doPrivileged(new PrivilegedAction<ThresholdInputStream>() { // NOSONAR
-                @Override
-                @SuppressForbidden("TODO: Fix this to not use reflection (it will break in Java 9)! " +
-                        "Better would be to wrap *before* instead of trying to insert wrapper afterwards.")
-                public ThresholdInputStream run() {
-                    try {
-                        Field f = FilterInputStream.class.getDeclaredField("in");
-                        f.setAccessible(true);
-                        InputStream oldInner = (InputStream)f.get(zipIS);
-                        ThresholdInputStream newInner2 = new ThresholdInputStream(oldInner, null);
-                        f.set(zipIS, newInner2);
-                        return newInner2;
-                    } catch (Exception ex) {
-                        LOG.log(POILogger.WARN, "SecurityManager doesn't allow manipulation via reflection for zipbomb detection - continue with original input stream", ex);
-                    }
-                    return null;
-                }
-            });
-        } else {
-            // the inner stream is a ZipFileInputStream, i.e. the data wasn't compressed
-            newInner = null;
-        }
-
-        return new ThresholdInputStream(zipIS, newInner);
-    }
-
-    public static class ThresholdInputStream extends PushbackInputStream {
-        long counter;
-        long markPos;
-        ThresholdInputStream cis;
-
-        public ThresholdInputStream(InputStream is, ThresholdInputStream cis) {
-            super(is);
-            this.cis = cis;
-        }
-
-        @Override
-        public int read() throws IOException {
-            int b = in.read();
-            if (b > -1) {
-                advance(1);
-            }
-            return b;
-        }
-
-        @Override
-        public int read(byte b[], int off, int len) throws IOException {
-            int cnt = in.read(b, off, len);
-            if (cnt > -1) {
-                advance(cnt);
-            }
-            return cnt;
-        }
-
-        @Override
-        public long skip(long n) throws IOException {
-            long s = in.skip(n);
-            counter += s;
-            return s;
-        }
-
-        @Override
-        public synchronized void reset() throws IOException {
-            counter = markPos;
-            super.reset();
-        }
-
-        public void advance(int advance) throws IOException {
-            counter += advance;
-            
-            // check the file size first, in case we are working on uncompressed streams
-            if(counter > MAX_ENTRY_SIZE) {
-                throw new IOException("Zip bomb detected! The file would exceed the max size of the expanded data in the zip-file. "
-                        + "This may indicates that the file is used to inflate memory usage and thus could pose a security risk. "
-                        + "You can adjust this limit via ZipSecureFile.setMaxEntrySize() if you need to work with files which are very large. "
-                        + "Counter: " + counter + ", cis.counter: " + (cis == null ? 0 : cis.counter)
-                        + "Limits: MAX_ENTRY_SIZE: " + MAX_ENTRY_SIZE);
-            }
-
-            // no expanded size?
-            if (cis == null) {
-                return;
-            }
-            
-            // don't alert for small expanded size
-            if (counter <= GRACE_ENTRY_SIZE) {
-                return;
-            }
-
-            double ratio = (double)cis.counter/(double)counter;
-            if (ratio >= MIN_INFLATE_RATIO) {
-                return;
-            }
-
-            // one of the limits was reached, report it
-            throw new IOException("Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.\n"
-                    + "This may indicate that the file is used to inflate memory usage and thus could pose a security risk.\n"
-                    + "You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.\n"
-                    + "Counter: " + counter + ", cis.counter: " + cis.counter + ", ratio: " + ratio + "\n"
-                    + "Limits: MIN_INFLATE_RATIO: " + MIN_INFLATE_RATIO);
-        }
-
-        public ZipEntry getNextEntry() throws IOException {
-            if (!(in instanceof ZipInputStream)) {
-                throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
-            }
-            counter = 0;
-            return ((ZipInputStream)in).getNextEntry();
-        }
-
-        public void closeEntry() throws IOException {
-            if (!(in instanceof ZipInputStream)) {
-                throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
-            }
-            counter = 0;
-            ((ZipInputStream)in).closeEntry();
-        }
-
-        @Override
-        public void unread(int b) throws IOException {
-            if (!(in instanceof PushbackInputStream)) {
-                throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
-            }
-            if (--counter < 0) {
-                counter = 0;
-            }
-            ((PushbackInputStream)in).unread(b);
-        }
-
-        @Override
-        public void unread(byte[] b, int off, int len) throws IOException {
-            if (!(in instanceof PushbackInputStream)) {
-                throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
-            }
-            counter -= len;
-            if (--counter < 0) {
-                counter = 0;
-            }
-            ((PushbackInputStream)in).unread(b, off, len);
-        }
-
-        @Override
-        @SuppressForbidden("just delegating")
-        public int available() throws IOException {
-            return in.available();
-        }
-
-        @Override
-        public boolean markSupported() {
-            return in.markSupported();
-        }
-
-        @Override
-        public synchronized void mark(int readlimit) {
-            markPos = counter;
-            in.mark(readlimit);
-        }
+    /**
+     * Returns the path name of the ZIP file.
+     * @return the path name of the ZIP file
+     */
+    public String getName() {
+        return fileName;
     }
 }
index 3c748a8f63f6fe3ff36446ff7e1890269be3bcd8..ea430bad08b2522ebc965a6dea835d412375a960 100644 (file)
@@ -53,15 +53,17 @@ import org.apache.poi.util.TempFile;
  * sensitive data is not stored in raw format on disk.
  */
 @Beta
-public class AesZipFileZipEntrySource implements ZipEntrySource {
+public final class AesZipFileZipEntrySource implements ZipEntrySource {
     private static final POILogger LOG = POILogFactory.getLogger(AesZipFileZipEntrySource.class);
+
+    private static final String PADDING = "PKCS5Padding";
     
     private final File tmpFile;
     private final ZipFile zipFile;
     private final Cipher ci;
     private boolean closed;
     
-    public AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException {
+    private AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException {
         this.tmpFile = tmpFile;
         this.zipFile = new ZipFile(tmpFile);
         this.ci = ci;
@@ -76,7 +78,12 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
     public Enumeration<? extends ZipEntry> getEntries() {
         return zipFile.entries();
     }
-    
+
+    @Override
+    public ZipEntry getEntry(String path) {
+        return zipFile.getEntry(path);
+    }
+
     @Override
     public InputStream getInputStream(ZipEntry entry) throws IOException {
         InputStream is = zipFile.getInputStream(entry);
@@ -106,14 +113,14 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
         sr.nextBytes(ivBytes);
         sr.nextBytes(keyBytes);
         final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip");
-        copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
+        copyToFile(is, tmpFile, keyBytes, ivBytes);
         IOUtils.closeQuietly(is);
-        return fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
+        return fileToSource(tmpFile, keyBytes, ivBytes);
     }
 
-    private static void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
-        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
-        Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
+    private static void copyToFile(InputStream is, File tmpFile, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
+        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, CipherAlgorithm.aes128.jceId);
+        Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, CipherAlgorithm.aes128, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, PADDING);
         
         ZipInputStream zis = new ZipInputStream(is);
         FileOutputStream fos = new FileOutputStream(tmpFile);
@@ -146,9 +153,9 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
         zis.close();
     }
 
-    private static AesZipFileZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
-        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
-        Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
+    private static AesZipFileZipEntrySource fileToSource(File tmpFile, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
+        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, CipherAlgorithm.aes128.jceId);
+        Cipher ciDec = CryptoFunctions.getCipher(skeySpec, CipherAlgorithm.aes128, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, PADDING);
         return new AesZipFileZipEntrySource(tmpFile, ciDec);
     }
     
index 7175217d8aa50aeeb94f25afe2bdecf7bbd7fcb9..8cfd93c0f5cbc6d923231f69dd8cfb33287e30cd 100644 (file)
@@ -48,6 +48,7 @@ public class EncryptedTempData {
     private static POILogger LOG = POILogFactory.getLogger(EncryptedTempData.class);
  
     private final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
+    private final static String PADDING = "PKCS5Padding";
     private final SecretKeySpec skeySpec;
     private final byte[] ivBytes;
     private final File tempFile;
@@ -63,12 +64,12 @@ public class EncryptedTempData {
     }
 
     public OutputStream getOutputStream() throws IOException {
-        Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, null);
+        Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, PADDING);
         return new CipherOutputStream(new FileOutputStream(tempFile), ciEnc);
     }
 
     public InputStream getInputStream() throws IOException {
-        Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, null);
+        Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, PADDING);
         return new CipherInputStream(new FileInputStream(tempFile), ciDec);
     }
     
index ffe9d2a737739a7eafa4c8aa9d15d2dc77963ffe..e5254157946c04677eb5b4d4f42d0adaf7d13429 100644 (file)
@@ -24,9 +24,9 @@ import java.io.FileOutputStream;
 import java.io.OutputStream;
 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.openxml4j.util.ZipSecureFile;
 import org.apache.poi.util.DocumentHelper;
 import org.apache.poi.util.IOUtils;
 import org.apache.xmlbeans.XmlException;
@@ -41,14 +41,13 @@ import org.w3c.dom.Document;
  */
 public final class XSSFDump {
 
+    private 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 = ZipHelper.openZipFile(args[i]);
-            try {
+        for (String arg : args) {
+            System.out.println("Dumping " + arg);
+            try (ZipSecureFile zip = ZipHelper.openZipFile(arg)) {
                 dump(zip);
-            } finally {
-                zip.close();
             }
         }
     }
@@ -72,7 +71,7 @@ public final class XSSFDump {
     }
     
 
-    public static void dump(ZipFile zip) throws Exception {
+    public static void dump(ZipSecureFile zip) throws Exception {
         String zipname = zip.getName();
         int sep = zipname.lastIndexOf('.');
         File root = new File(zipname.substring(0, sep));
@@ -90,8 +89,7 @@ public final class XSSFDump {
             }
 
             File f = new File(root, entry.getName());
-            OutputStream out = new FileOutputStream(f);
-            try {
+            try (final OutputStream out = new FileOutputStream(f)) {
                 if (entry.getName().endsWith(".xml") || entry.getName().endsWith(".vml") || entry.getName().endsWith(".rels")) {
                     try {
                         Document doc = DocumentHelper.readDocument(zip.getInputStream(entry));
@@ -106,8 +104,6 @@ public final class XSSFDump {
                 } else {
                     IOUtils.copy(zip.getInputStream(entry), out);
                 }
-            } finally {
-                out.close();
             }
         }
     }
index acf9d0f303b3a80f4ecdeda8a4408bb4f0713318..73141707c99838379659c3c737d13f3701d2bbf3 100644 (file)
@@ -36,6 +36,7 @@ import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
 import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream;
 import org.apache.poi.openxml4j.util.ZipEntrySource;
 import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
 import org.apache.poi.ss.SpreadsheetVersion;
@@ -50,7 +51,13 @@ import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.SheetVisibility;
 import org.apache.poi.ss.usermodel.Workbook;
-import org.apache.poi.util.*;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.NotImplemented;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.Removal;
+import org.apache.poi.util.TempFile;
 import org.apache.poi.xssf.model.SharedStringsTable;
 import org.apache.poi.xssf.usermodel.XSSFChartSheet;
 import org.apache.poi.xssf.usermodel.XSSFSheet;
@@ -369,13 +376,17 @@ public class SXSSFWorkbook implements Workbook {
     }
 
     protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException {
-        try {
-            try (ZipOutputStream zos = new ZipOutputStream(out)) {
-                Enumeration<? extends ZipEntry> en = zipEntrySource.getEntries();
-                while (en.hasMoreElements()) {
-                    ZipEntry ze = en.nextElement();
-                    zos.putNextEntry(new ZipEntry(ze.getName()));
-                    InputStream is = zipEntrySource.getInputStream(ze);
+        try (ZipOutputStream zos = new ZipOutputStream(out)) {
+            Enumeration<? extends ZipEntry> en = zipEntrySource.getEntries();
+            while (en.hasMoreElements()) {
+                ZipEntry ze = en.nextElement();
+                zos.putNextEntry(new ZipEntry(ze.getName()));
+                try (final InputStream is = zipEntrySource.getInputStream(ze)) {
+                    if (is instanceof ZipArchiveThresholdInputStream) {
+                        // #59743 - disable Threshold handling for SXSSF copy
+                        // as users tend to put too much repetitive data in when using SXSSF :)
+                        ((ZipArchiveThresholdInputStream)is).setGuardState(false);
+                    }
                     XSSFSheet xSheet = getSheetFromZipEntryName(ze.getName());
                     // See bug 56557, we should not inject data into the special ChartSheets
                     if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) {
@@ -386,7 +397,6 @@ public class SXSSFWorkbook implements Workbook {
                     } else {
                         IOUtils.copy(is, zos);
                     }
-                    is.close();
                 }
             }
         } finally {
index 8cdcd61ad908f555e21735bf7142abff1d32e5bc..b37de1816e66f7f12a43e73dbfcc3e8e3648c7fb 100644 (file)
@@ -17,6 +17,7 @@
 
 package org.apache.poi.openxml4j.opc;
 
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -34,13 +35,14 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PushbackInputStream;
-import java.lang.reflect.InvocationTargetException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.TreeMap;
+import java.util.function.BiConsumer;
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -53,7 +55,6 @@ import org.apache.poi.POITextExtractor;
 import org.apache.poi.POIXMLException;
 import org.apache.poi.UnsupportedFileFormatException;
 import org.apache.poi.extractor.ExtractorFactory;
-import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.openxml4j.OpenXML4JTestDataSamples;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
@@ -66,6 +67,8 @@ 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.sl.usermodel.SlideShow;
+import org.apache.poi.sl.usermodel.SlideShowFactory;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
 import org.apache.poi.util.DocumentHelper;
@@ -74,17 +77,26 @@ import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.TempFile;
 import org.apache.poi.xssf.XSSFTestDataSamples;
+import org.apache.poi.xwpf.usermodel.XWPFRelation;
 import org.apache.xmlbeans.XmlException;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 
 public final class TestPackage {
     private static final POILogger logger = POILogFactory.getLogger(TestPackage.class);
 
+       @Rule
+       public ExpectedException expectedEx = ExpectedException.none();
+
        /**
         * Test that just opening and closing the file doesn't alter the document.
         */
@@ -114,7 +126,7 @@ public final class TestPackage {
         */
     @Test
        public void createGetsContentTypes()
-    throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
+    throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException {
                File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestCreatePackageTMP.docx");
 
                // Zap the target file, in case of an earlier run
@@ -596,7 +608,7 @@ public final class TestPackage {
        }
 
     @Test
-    public void getPartsByName() throws IOException, InvalidFormatException {
+    public void getPartsByName() throws InvalidFormatException {
         String filepath =  OpenXML4JTestDataSamples.getSampleFileName("sample.docx");
 
         @SuppressWarnings("resource")
@@ -653,7 +665,7 @@ public final class TestPackage {
 
     @Test
     public void replaceContentType()
-    throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
+    throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException {
         InputStream is = OpenXML4JTestDataSamples.openSampleStream("sample.xlsx");
         @SuppressWarnings("resource")
         OPCPackage p = OPCPackage.open(is);
@@ -760,163 +772,175 @@ public final class TestPackage {
         }
     }
 
-    @Test(expected=IOException.class)
+       /**
+        * Zip bomb handling test
+        *
+        * see bug #50090 / #56865
+        */
+    @Test
     public void zipBombCreateAndHandle()
     throws IOException, EncryptedDocumentException, InvalidFormatException {
-        // #50090 / #56865
-        ZipFile zipFile = ZipHelper.openZipFile(OpenXML4JTestDataSamples.getSampleFile("sample.xlsx"));
-               assertNotNull(zipFile);
-
                ByteArrayOutputStream bos = new ByteArrayOutputStream(2500000);
-               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] = ' ';
-                    // 0x7FFF0000 is the maximum for 32-bit zips, but less still works
-                    while (size < 0x7FFF00) {
-                        append.write(spam);
-                        size += spam.length;
-                    }
-                    append.write("</Types>".getBytes("UTF-8"));
-                    size += 8;
-                    e.setSize(size);
-                } else {
-                    IOUtils.copy(is, append);
-                }
-                is.close();
-            }
-            append.closeEntry();
-        }
-        
-        append.close();
-        zipFile.close();
 
-        byte buf[] = bos.toByteArray();
-               //noinspection UnusedAssignment
-               bos = null;
-        
-        Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(buf));
-        wb.getSheetAt(0);
-        wb.close();
-        zipFile.close();
+        try (ZipFile zipFile = ZipHelper.openZipFile(OpenXML4JTestDataSamples.getSampleFile("sample.xlsx"));
+                        ZipOutputStream append = new ZipOutputStream(bos)) {
+                       assertNotNull(zipFile);
+
+                       // first, copy contents from existing war
+                       Enumeration<? extends ZipEntry> entries = zipFile.entries();
+                       while (entries.hasMoreElements()) {
+                               final ZipEntry eIn = entries.nextElement();
+                               final ZipEntry eOut = new ZipEntry(eIn.getName());
+                               eOut.setTime(eIn.getTime());
+                               eOut.setComment(eIn.getComment());
+                               eOut.setSize(eIn.getSize());
+
+                               append.putNextEntry(eOut);
+                               if (!eOut.isDirectory()) {
+                                       try (InputStream is = zipFile.getInputStream(eIn)) {
+                                               if (eOut.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];
+                                                       Arrays.fill(spam, (byte) ' ');
+                                                       // 0x7FFF0000 is the maximum for 32-bit zips, but less still works
+                                                       while (size < 0x7FFF00) {
+                                                               append.write(spam);
+                                                               size += spam.length;
+                                                       }
+                                                       append.write("</Types>".getBytes("UTF-8"));
+                                                       size += 8;
+                                                       eOut.setSize(size);
+                                               } else {
+                                                       IOUtils.copy(is, append);
+                                               }
+                                       }
+                               }
+                               append.closeEntry();
+                       }
+               }
+
+               expectedEx.expect(IOException.class);
+               expectedEx.expectMessage("Zip bomb detected!");
+
+               try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bos.toByteArray()))) {
+                       wb.getSheetAt(0);
+               }
     }
 
-    @Test
-       public void zipBombSampleFiles() throws IOException, OpenXML4JException, XmlException {
-       openZipBombFile("poc-shared-strings.xlsx");
-       openZipBombFile("poc-xmlbomb.xlsx");
-       openZipBombFile("poc-xmlbomb-empty.xlsx");
+       @Test
+       public void testZipEntityExpansionTerminates() throws IOException, OpenXML4JException, XmlException {
+               expectedEx.expect(IllegalStateException.class);
+               expectedEx.expectMessage("The text would exceed the max allowed overall size of extracted text.");
+               openXmlBombFile("poc-shared-strings.xlsx");
        }
 
-       private void openZipBombFile(String file) throws IOException, OpenXML4JException, XmlException {
-       try {
-                       Workbook wb = XSSFTestDataSamples.openSampleWorkbook(file);
-                       wb.close();
+       @Test
+       public void testZipEntityExpansionSharedStringTableEvents() throws IOException, OpenXML4JException, XmlException {
+               boolean before = ExtractorFactory.getThreadPrefersEventExtractors();
+               ExtractorFactory.setThreadPrefersEventExtractors(true);
+               try {
+                       expectedEx.expect(IllegalStateException.class);
+                       expectedEx.expectMessage("The text would exceed the max allowed overall size of extracted text.");
+                       openXmlBombFile("poc-shared-strings.xlsx");
+               } finally {
+                       ExtractorFactory.setThreadPrefersEventExtractors(before);
+               }
+       }
 
-                       try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx"))) {
-                               assertNotNull(extractor);
-                               extractor.getText();
-                       }
 
-                       fail("Should catch an exception because of a ZipBomb");
-               } catch (IllegalStateException e) {
-               if(!e.getMessage().contains("The text would exceed the max allowed overall size of extracted text.")) {
-                               throw e;
-                       }
-               } catch (POIXMLException e) {
-               checkForZipBombException(e);
+       @Test
+       public void testZipEntityExpansionExceedsMemory() throws IOException, OpenXML4JException, XmlException {
+               expectedEx.expect(POIXMLException.class);
+               expectedEx.expectMessage("Unable to parse xml bean");
+               expectedEx.expectCause(getCauseMatcher(SAXParseException.class, "The parser has encountered more than"));
+               openXmlBombFile("poc-xmlbomb.xlsx");
+       }
+
+       @Test
+       public void testZipEntityExpansionExceedsMemory2() throws IOException, OpenXML4JException, XmlException {
+               expectedEx.expect(POIXMLException.class);
+               expectedEx.expectMessage("Unable to parse xml bean");
+               expectedEx.expectCause(getCauseMatcher(SAXParseException.class, "The parser has encountered more than"));
+       openXmlBombFile("poc-xmlbomb-empty.xlsx");
+       }
+
+       private void openXmlBombFile(String file) throws IOException, OpenXML4JException, XmlException {
+               final double minInf = ZipSecureFile.getMinInflateRatio();
+               ZipSecureFile.setMinInflateRatio(0.002);
+               try (POITextExtractor extractor = ExtractorFactory.createExtractor(XSSFTestDataSamples.getSampleFile(file))) {
+                       assertNotNull(extractor);
+                       extractor.getText();
+               } finally {
+                       ZipSecureFile.setMinInflateRatio(minInf);
                }
        }
-    
+
     @Test
-    public void zipBombCheckSizes() throws IOException, EncryptedDocumentException, InvalidFormatException {
-        File file = OpenXML4JTestDataSamples.getSampleFile("sample.xlsx");
+    public void zipBombCheckSizesWithinLimits() throws IOException, EncryptedDocumentException, InvalidFormatException {
+               getZipStatsAndConsume((max_size, min_ratio) -> {
+                       // use values close to, but within the limits
+                       ZipSecureFile.setMinInflateRatio(min_ratio - 0.002);
+                       assertEquals(min_ratio - 0.002, ZipSecureFile.getMinInflateRatio(), 0.00001);
+                       ZipSecureFile.setMaxEntrySize(max_size + 1);
+                       assertEquals(max_size + 1, ZipSecureFile.getMaxEntrySize());
+               });
+       }
 
-        try {
-            double min_ratio = Double.MAX_VALUE;
-            long max_size = 0;
-            ZipFile zf = ZipHelper.openZipFile(file);
-                       assertNotNull(zf);
-            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);
-                       assertEquals(min_ratio-0.002, ZipSecureFile.getMinInflateRatio(), 0.00001);
-            ZipSecureFile.setMaxEntrySize(max_size+1);
-                       assertEquals(max_size+1, ZipSecureFile.getMaxEntrySize());
-                       
-            WorkbookFactory.create(file, null, true).close();
-    
-            // check ratio out of bounds
-            ZipSecureFile.setMinInflateRatio(min_ratio+0.002);
-            try {
-                WorkbookFactory.create(file, null, true).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 | POIXMLException e) {
-                checkForZipBombException(e);
-            }
+       @Test
+       public void zipBombCheckSizesRatioTooSmall() throws IOException, EncryptedDocumentException, InvalidFormatException {
+               expectedEx.expect(POIXMLException.class);
+               expectedEx.expectMessage("You can adjust this limit via ZipSecureFile.setMinInflateRatio()");
+               getZipStatsAndConsume((max_size, min_ratio) -> {
+                       // check ratio out of bounds
+                       ZipSecureFile.setMinInflateRatio(min_ratio+0.002);
+               });
+       }
 
+       @Test
+       public void zipBombCheckSizesSizeTooBig() throws IOException, EncryptedDocumentException, InvalidFormatException {
+               expectedEx.expect(POIXMLException.class);
+               expectedEx.expectMessage("You can adjust this limit via ZipSecureFile.setMaxEntrySize()");
+               getZipStatsAndConsume((max_size, min_ratio) -> {
                        // check max entry size ouf of bounds
-            ZipSecureFile.setMinInflateRatio(min_ratio-0.002);
-            ZipSecureFile.setMaxEntrySize(max_size-1);
-            try {
-                WorkbookFactory.create(file, null, true).close();
-            } catch (InvalidFormatException | POIXMLException e) {
-                checkForZipBombException(e);
-            }
-               } finally {
-            // reset otherwise a lot of ooxml tests will fail
-            ZipSecureFile.setMinInflateRatio(0.01d);
-            ZipSecureFile.setMaxEntrySize(0xFFFFFFFFL);
-        }
-    }
+                       ZipSecureFile.setMinInflateRatio(min_ratio-0.002);
+                       ZipSecureFile.setMaxEntrySize(max_size-100);
+               });
+       }
 
-    private void checkForZipBombException(Throwable e) {
-       // unwrap InvocationTargetException as they usually contain the nested exception in the "target" member
-        if(e instanceof InvocationTargetException) {
-                       e = ((InvocationTargetException)e).getTargetException();
-        }
-        
-        String msg = e.getMessage();
-        if(msg != null && (msg.startsWith("Zip bomb detected!") ||
-                               msg.contains("The parser has encountered more than \"4,096\" entity expansions in this document;") ||
-                               msg.contains("The parser has encountered more than \"4096\" entity expansions in this document;"))) {
-            return;
-        }
-        
-        // recursively check the causes for the message as it can be nested further down in the exception-tree
-        if(e.getCause() != null && e.getCause() != e) {
-            checkForZipBombException(e.getCause());
-            return;
-        }
+       private void getZipStatsAndConsume(BiConsumer<Long,Double> ratioCon) throws IOException, InvalidFormatException {
+       // use a test file with a xml file bigger than 100k (ZipArchiveThresholdInputStream.GRACE_ENTRY_SIZE)
+               final File file = XSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx");
 
-        throw new IllegalStateException("Expected to catch an Exception because of a detected Zip Bomb, but did not find the related error message in the exception", e);        
-    }
+               double min_ratio = Double.MAX_VALUE;
+               long max_size = 0;
+               try (ZipFile zf = ZipHelper.openZipFile(file)) {
+                       assertNotNull(zf);
+                       Enumeration<? extends ZipEntry> entries = zf.entries();
+                       while (entries.hasMoreElements()) {
+                               ZipEntry ze = entries.nextElement();
+                               if (ze.getSize() == 0) {
+                                       continue;
+                               }
+                               // add zip entry header ~ 30 bytes
+                               long size = ze.getSize()+30;
+                               double ratio = ze.getCompressedSize() / (double)size;
+                               min_ratio = Math.min(min_ratio, ratio);
+                               max_size = Math.max(max_size, size);
+                       }
+               }
+               ratioCon.accept(max_size, min_ratio);
+
+               //noinspection EmptyTryBlock,unused
+               try (Workbook wb = WorkbookFactory.create(file, null, true)) {
+               } finally {
+                       // reset otherwise a lot of ooxml tests will fail
+                       ZipSecureFile.setMinInflateRatio(0.01d);
+                       ZipSecureFile.setMaxEntrySize(0xFFFFFFFFL);
+               }
+       }
 
     @Test
     public void testConstructors() throws IOException {
@@ -926,10 +950,6 @@ public final class TestPackage {
         assertNotNull(zipFile.getName());
         zipFile.close();
 
-        zipFile = new ZipSecureFile(file, ZipFile.OPEN_READ);
-        assertNotNull(zipFile.getName());
-        zipFile.close();
-
         zipFile = new ZipSecureFile(file.getAbsolutePath());
         assertNotNull(zipFile.getName());
         zipFile.close();
@@ -948,7 +968,7 @@ public final class TestPackage {
     
     // bug 60128
     @Test(expected=NotOfficeXmlFileException.class)
-    public void testCorruptFile() throws IOException, InvalidFormatException {
+    public void testCorruptFile() throws InvalidFormatException {
         File file = OpenXML4JTestDataSamples.getSampleFile("invalid.xlsx");
         OPCPackage.open(file, PackageAccess.READ);
     }
@@ -976,4 +996,148 @@ public final class TestPackage {
             }
         }
     }
+
+       @Test
+       public void testBug56479() throws Exception {
+               InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
+               OPCPackage p = OPCPackage.open(is);
+
+               // Check we found the contents of it
+               boolean foundCoreProps = false, foundDocument = false, foundTheme1 = false;
+               for (final PackagePart part : p.getParts()) {
+                       final String partName = part.getPartName().toString();
+                       final String contentType = part.getContentType();
+                       if ("/docProps/core.xml".equals(partName)) {
+                               assertEquals(ContentTypes.CORE_PROPERTIES_PART, contentType);
+                               foundCoreProps = true;
+                       }
+                       if ("/word/document.xml".equals(partName)) {
+                               assertEquals(XWPFRelation.DOCUMENT.getContentType(), contentType);
+                               foundDocument = true;
+                       }
+                       if ("/word/theme/theme1.xml".equals(partName)) {
+                               assertEquals(XWPFRelation.THEME.getContentType(), contentType);
+                               foundTheme1 = true;
+                       }
+               }
+               assertTrue("Core not found in " + p.getParts(), foundCoreProps);
+               assertFalse("Document should not be found in " + p.getParts(), foundDocument);
+               assertFalse("Theme1 should not found in " + p.getParts(), foundTheme1);
+               p.close();
+               is.close();
+       }
+
+       @Test
+       public void unparseableCentralDirectory() throws IOException {
+               File f = OpenXML4JTestDataSamples.getSampleFile("at.pzp.www_uploads_media_PP_Scheinecker-jdk6error.pptx");
+               SlideShow<?,?> ppt = SlideShowFactory.create(f, null, true);
+               ppt.close();
+       }
+
+       @Test
+       public void testClosingStreamOnException() throws IOException {
+               InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
+               File tmp = File.createTempFile("poi-test-truncated-zip", "");
+               // create a corrupted zip file by truncating a valid zip file to the first 100 bytes
+               OutputStream os = new FileOutputStream(tmp);
+               for (int i = 0; i < 100; i++) {
+                       os.write(is.read());
+               }
+               os.flush();
+               os.close();
+               is.close();
+
+               // feed the corrupted zip file to OPCPackage
+               try {
+                       OPCPackage.open(tmp, PackageAccess.READ);
+               } catch (Exception e) {
+                       // expected: the zip file is invalid
+                       // this test does not care if open() throws an exception or not.
+               }
+               // If the stream is not closed on exception, it will keep a file descriptor to tmp,
+               // and requests to the OS to delete the file will fail.
+               assertTrue("Can't delete tmp file", tmp.delete());
+       }
+
+       /**
+        * If ZipPackage is passed an invalid file, a call to close
+        *  (eg from the OPCPackage open method) should tidy up the
+        *  stream / file the broken file is being read from.
+        * See bug #60128 for more
+        */
+       @Test(expected = NotOfficeXmlFileException.class)
+       public void testTidyStreamOnInvalidFile1() throws Exception {
+               openInvalidFile("SampleSS.ods", false);
+       }
+
+       @Test(expected = NotOfficeXmlFileException.class)
+       public void testTidyStreamOnInvalidFile2() throws Exception {
+               openInvalidFile("SampleSS.ods", true);
+       }
+
+       @Test(expected = NotOfficeXmlFileException.class)
+       public void testTidyStreamOnInvalidFile3() throws Exception {
+               openInvalidFile("SampleSS.txt", false);
+       }
+
+       @Test(expected = NotOfficeXmlFileException.class)
+       public void testTidyStreamOnInvalidFile4() throws Exception {
+               openInvalidFile("SampleSS.txt", true);
+       }
+
+       private static void openInvalidFile(final String name, final boolean useStream) throws IOException, InvalidFormatException {
+               // Spreadsheet has a good mix of alternate file types
+               final POIDataSamples files = POIDataSamples.getSpreadSheetInstance();
+               ZipPackage pkgTest = null;
+               try (final InputStream is = (useStream) ? files.openResourceAsStream(name) : null) {
+                       try (final ZipPackage pkg = (useStream) ? new ZipPackage(is, PackageAccess.READ) : new ZipPackage(files.getFile(name), PackageAccess.READ)) {
+                               pkgTest = pkg;
+                               assertNotNull(pkg.getZipArchive());
+//                             assertFalse(pkg.getZipArchive().isClosed());
+                               pkg.getParts();
+                               fail("Shouldn't work");
+                       }
+               } finally {
+                       if (pkgTest != null) {
+                               assertNotNull(pkgTest.getZipArchive());
+                               assertTrue(pkgTest.getZipArchive().isClosed());
+                       }
+               }
+       }
+
+       @SuppressWarnings("SameParameterValue")
+       private static <T extends Throwable> AnyCauseMatcher<T> getCauseMatcher(Class<T> cause, String message) {
+       // junit is only using hamcrest-core, so instead of adding hamcrest-beans, we provide the throwable
+               // search with the basics...
+               // see https://stackoverflow.com/a/47703937/2066598
+               return new AnyCauseMatcher<>(cause, message);
+       }
+
+       private static class AnyCauseMatcher<T extends Throwable> extends TypeSafeMatcher<T> {
+               private final Class<T> expectedType;
+               private final String expectedMessage;
+
+               AnyCauseMatcher(Class<T> expectedType, String expectedMessage) {
+                       this.expectedType = expectedType;
+                       this.expectedMessage = expectedMessage;
+               }
+
+               @Override
+               protected boolean matchesSafely(final Throwable root) {
+                       for (Throwable t = root; t != null; t = t.getCause()) {
+                               if (t.getClass().isAssignableFrom(expectedType) && t.getMessage().contains(expectedMessage)) {
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+
+               @Override
+               public void describeTo(Description description) {
+                       description.appendText("expects type ")
+                                       .appendValue(expectedType)
+                                       .appendText(" and a message ")
+                                       .appendValue(expectedMessage);
+               }
+       }
 }
diff --git a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestZipPackage.java b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestZipPackage.java
deleted file mode 100644 (file)
index 88c5bbb..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-/* ====================================================================
-   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.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.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-
-import org.apache.poi.POIDataSamples;
-import org.apache.poi.POITextExtractor;
-import org.apache.poi.POIXMLException;
-import org.apache.poi.extractor.ExtractorFactory;
-import org.apache.poi.hssf.HSSFTestDataSamples;
-import org.apache.poi.openxml4j.OpenXML4JTestDataSamples;
-import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
-import org.apache.poi.sl.usermodel.SlideShow;
-import org.apache.poi.sl.usermodel.SlideShowFactory;
-import org.apache.poi.ss.usermodel.Workbook;
-import org.apache.poi.ss.usermodel.WorkbookFactory;
-import org.apache.poi.xssf.XSSFTestDataSamples;
-import org.apache.poi.xwpf.usermodel.XWPFRelation;
-import org.apache.xmlbeans.XmlException;
-import org.junit.Test;
-
-public class TestZipPackage {
-    @Test
-    public void testBug56479() throws Exception {
-        InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
-        OPCPackage p = OPCPackage.open(is);
-        
-        // Check we found the contents of it
-        boolean foundCoreProps = false, foundDocument = false, foundTheme1 = false;
-        for (final PackagePart part : p.getParts()) {
-            final String partName = part.getPartName().toString();
-            final String contentType = part.getContentType();
-            if ("/docProps/core.xml".equals(partName)) {
-                assertEquals(ContentTypes.CORE_PROPERTIES_PART, contentType);
-                foundCoreProps = true;
-            }
-            if ("/word/document.xml".equals(partName)) {
-                assertEquals(XWPFRelation.DOCUMENT.getContentType(), contentType);
-                foundDocument = true;
-            }
-            if ("/word/theme/theme1.xml".equals(partName)) {
-                assertEquals(XWPFRelation.THEME.getContentType(), contentType);
-                foundTheme1 = true;
-            }
-        }
-        assertTrue("Core not found in " + p.getParts(), foundCoreProps);
-        assertFalse("Document should not be found in " + p.getParts(), foundDocument);
-        assertFalse("Theme1 should not found in " + p.getParts(), foundTheme1);
-        p.close();
-        is.close();
-    }
-
-    @Test
-    public void testZipEntityExpansionTerminates() throws IOException {
-        try {
-            Workbook wb = XSSFTestDataSamples.openSampleWorkbook("poc-xmlbomb.xlsx");
-            wb.close();
-            fail("Should catch exception due to entity expansion limitations");
-        } catch (POIXMLException e) {
-            assertEntityLimitReached(e);
-        }
-    }
-
-    private void assertEntityLimitReached(Exception e) throws UnsupportedEncodingException {
-        ByteArrayOutputStream str = new ByteArrayOutputStream();
-        try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(str, "UTF-8"))) {
-            e.printStackTrace(writer);
-        }
-        String string = new String(str.toByteArray(), "UTF-8");
-        assertTrue("Had: " + string, string.contains("The parser has encountered more than"));
-    }
-
-    @Test
-    public void testZipEntityExpansionExceedsMemory() throws Exception {
-        try {
-            Workbook wb = WorkbookFactory.create(XSSFTestDataSamples.openSamplePackage("poc-xmlbomb.xlsx"));
-            wb.close();
-            fail("Should catch exception due to entity expansion limitations");
-        } catch (POIXMLException e) {
-            assertEntityLimitReached(e);
-        }
-
-        try {
-            try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-xmlbomb.xlsx"))) {
-                assertNotNull(extractor);
-
-                try {
-                    extractor.getText();
-                } catch (IllegalStateException e) {
-                    // expected due to shared strings expansion
-                }
-            }
-        } catch (POIXMLException e) {
-            assertEntityLimitReached(e);
-        }
-    }
-
-    @Test
-    public void testZipEntityExpansionSharedStringTable() throws Exception {
-        Workbook wb = WorkbookFactory.create(XSSFTestDataSamples.openSamplePackage("poc-shared-strings.xlsx"));
-        wb.close();
-
-        try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx"))) {
-            assertNotNull(extractor);
-
-            try {
-                extractor.getText();
-            } catch (IllegalStateException e) {
-                // expected due to shared strings expansion
-            }
-        }
-    }
-
-    @Test
-    public void testZipEntityExpansionSharedStringTableEvents() throws Exception {
-        boolean before = ExtractorFactory.getThreadPrefersEventExtractors();
-        ExtractorFactory.setThreadPrefersEventExtractors(true);
-        try {
-            try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx"))) {
-                assertNotNull(extractor);
-
-                try {
-                    extractor.getText();
-                } catch (IllegalStateException e) {
-                    // expected due to shared strings expansion
-                }
-            }
-        } catch (XmlException e) {
-            assertEntityLimitReached(e);
-        } finally {
-            ExtractorFactory.setThreadPrefersEventExtractors(before);
-        }
-    }
-    
-    @Test
-    public void unparseableCentralDirectory() throws IOException {
-        File f = OpenXML4JTestDataSamples.getSampleFile("at.pzp.www_uploads_media_PP_Scheinecker-jdk6error.pptx");
-        SlideShow<?,?> ppt = SlideShowFactory.create(f, null, true);
-        ppt.close();
-    }
-
-    @Test
-    public void testClosingStreamOnException() throws IOException {
-        InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
-        File tmp = File.createTempFile("poi-test-truncated-zip", "");
-        // create a corrupted zip file by truncating a valid zip file to the first 100 bytes
-        OutputStream os = new FileOutputStream(tmp);
-        for (int i = 0; i < 100; i++) {
-            os.write(is.read());
-        }
-        os.flush();
-        os.close();
-        is.close();
-
-        // feed the corrupted zip file to OPCPackage
-        try {
-            OPCPackage.open(tmp, PackageAccess.READ);
-        } catch (Exception e) {
-            // expected: the zip file is invalid
-            // this test does not care if open() throws an exception or not.
-        }
-        // If the stream is not closed on exception, it will keep a file descriptor to tmp,
-        // and requests to the OS to delete the file will fail.
-        assertTrue("Can't delete tmp file", tmp.delete());
-    }
-    
-    /**
-     * If ZipPackage is passed an invalid file, a call to close
-     *  (eg from the OPCPackage open method) should tidy up the
-     *  stream / file the broken file is being read from.
-     * See bug #60128 for more
-     */
-    @Test
-    public void testTidyStreamOnInvalidFile() throws Exception {
-        // Spreadsheet has a good mix of alternate file types
-        POIDataSamples files = POIDataSamples.getSpreadSheetInstance();
-        
-        File[] notValidF = new File[] {
-                files.getFile("SampleSS.ods"), files.getFile("SampleSS.txt")
-        };
-        InputStream[] notValidS = new InputStream[] {
-                files.openResourceAsStream("SampleSS.ods"), files.openResourceAsStream("SampleSS.txt")
-        };
-
-        for (File notValid : notValidF) {
-            ZipPackage pkg = new ZipPackage(notValid, PackageAccess.READ);
-            assertNotNull(pkg.getZipArchive());
-            assertFalse(pkg.getZipArchive().isClosed());
-            try {
-                pkg.getParts();
-                fail("Shouldn't work");
-            } catch (NotOfficeXmlFileException e) {
-                // expected here
-            }
-            pkg.close();
-            
-            assertNotNull(pkg.getZipArchive());
-            assertTrue(pkg.getZipArchive().isClosed());
-        }
-        for (InputStream notValid : notValidS) {
-            ZipPackage pkg = new ZipPackage(notValid, PackageAccess.READ);
-            assertNotNull(pkg.getZipArchive());
-            assertFalse(pkg.getZipArchive().isClosed());
-            try {
-                pkg.getParts();
-                fail("Shouldn't work");
-            } catch (NotOfficeXmlFileException e) {
-                // expected here
-            }
-            pkg.close();
-            
-            assertNotNull(pkg.getZipArchive());
-            assertTrue(pkg.getZipArchive().isClosed());
-        }
-    }
-}
index 330720fd309f8eb13dedd2595482b7d7513781d0..3706085c1982efed564c77c0efc2204488160e94 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.poi.openxml4j.opc;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -31,20 +32,26 @@ import java.util.TreeMap;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
-import org.junit.Assert;
-
 import junit.framework.AssertionFailedError;
+import org.apache.poi.util.IOUtils;
+import org.junit.Assert;
+import org.xmlunit.builder.DiffBuilder;
+import org.xmlunit.builder.Input;
+import org.xmlunit.diff.Comparison;
+import org.xmlunit.diff.ComparisonResult;
+import org.xmlunit.diff.DefaultNodeMatcher;
+import org.xmlunit.diff.Diff;
+import org.xmlunit.diff.DifferenceEvaluator;
+import org.xmlunit.diff.ElementSelectors;
 
 /**
  * Compare the contents of 2 zip files.
  */
-public class ZipFileAssert {
+public final class ZipFileAssert {
        private ZipFileAssert() {
        }
 
-       static final int BUFFER_SIZE = 2048;
-
-       protected static void equals(
+       private static void equals(
                        TreeMap<String, ByteArrayOutputStream> file1,
                        TreeMap<String, ByteArrayOutputStream> file2) {
                Set<String> listFile1 = file1.keySet();
@@ -52,32 +59,37 @@ public class ZipFileAssert {
                
                for (String fileName : listFile1) {
                        // extract the contents for both
-                       ByteArrayOutputStream contain2 = file2.get(fileName);
                        ByteArrayOutputStream contain1 = file1.get(fileName);
+                       ByteArrayOutputStream contain2 = file2.get(fileName);
 
                        assertNotNull(fileName + " not found in 2nd zip", contain2);
                        // no need to check for contain1. The key come from it
 
-                       if ((fileName.endsWith(".xml")) || fileName.endsWith(".rels")) {
+                       if (fileName.matches(".*\\.(xml|rels)$")) {
                                // we have a xml file
-                // TODO
-                // YK: the original OpenXML4J version attempted to compare xml using xmlunit (http://xmlunit.sourceforge.net),
-                // but POI does not depend on this library
+                               final Diff diff = DiffBuilder.
+                                               compare(Input.fromByteArray(contain1.toByteArray())).
+                                               withTest(Input.fromByteArray(contain2.toByteArray())).
+                                               ignoreWhitespace().
+                                               checkForSimilar().
+                                               withDifferenceEvaluator(new IgnoreXMLDeclEvaluator()).
+                                               withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes, ElementSelectors.byNameAndText)).
+                                               build();
+                               assertFalse(fileName+": "+diff.toString(), diff.hasDifferences());
             } else {
                                // not xml, may be an image or other binary format
-                Assert.assertEquals(fileName + " does not have the same size in both zip:", contain2.size(), contain1.size());
+                Assert.assertEquals(fileName + " does not have the same size in both zip:", contain1.size(), contain2.size());
                                assertArrayEquals("contents differ", contain1.toByteArray(), contain2.toByteArray());
                        }
                }
        }
 
-       protected static TreeMap<String, ByteArrayOutputStream> decompress(
+       private static TreeMap<String, ByteArrayOutputStream> decompress(
                        File filename) throws IOException {
                // store the zip content in memory
                // let s assume it is not Go ;-)
                TreeMap<String, ByteArrayOutputStream> zipContent = new TreeMap<>();
 
-               byte data[] = new byte[BUFFER_SIZE];
                /* Open file to decompress */
                FileInputStream file_decompress = new FileInputStream(filename);
 
@@ -89,20 +101,12 @@ public class ZipFileAssert {
 
                /* Processing entries of the zip file */
                ZipEntry entree;
-               int count;
                while ((entree = zis.getNextEntry()) != null) {
 
                        /* Create a array for the current entry */
                        ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
+                       IOUtils.copy(zis, byteArray);
                        zipContent.put(entree.getName(), byteArray);
-
-                       /* copy in memory */
-                       while ((count = zis.read(data, 0, BUFFER_SIZE)) != -1) {
-                               byteArray.write(data, 0, count);
-                       }
-                       /* Flush the buffer */
-                       byteArray.flush();
-                       byteArray.close();
                }
 
                zis.close();
@@ -136,4 +140,29 @@ public class ZipFileAssert {
                        throw new AssertionFailedError(e.toString());
                }
        }
+
+       private static class IgnoreXMLDeclEvaluator implements DifferenceEvaluator {
+               public ComparisonResult evaluate(final Comparison comparison, final ComparisonResult outcome) {
+                       if (outcome != ComparisonResult.EQUAL) {
+                               // only evaluate differences
+                               switch (comparison.getType()) {
+                                       case CHILD_NODELIST_SEQUENCE:
+                                       case XML_STANDALONE:
+                                       case NAMESPACE_PREFIX:
+                                               return ComparisonResult.SIMILAR;
+                                       case TEXT_VALUE:
+                                               switch (comparison.getControlDetails().getTarget().getParentNode().getNodeName()) {
+                                               case "dcterms:created":
+                                               case "dc:creator":
+                                                       return ComparisonResult.SIMILAR;
+                                               }
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+
+                       return outcome;
+               }
+       }
 }
index 4ff321e370ec40bf7ac5e55e05381d39fa3b044d..f9abb2fe8b82dc00f6ccd712ff337a46b7bac793 100644 (file)
 
 package org.apache.poi.openxml4j.opc.internal.marshallers;
 
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
-import org.apache.poi.openxml4j.opc.PackagingURIHelper;
-import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
-import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
-import org.junit.Test;
+import static org.apache.poi.openxml4j.opc.PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_URI;
+import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -29,8 +26,11 @@ import java.io.OutputStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
-import static org.apache.poi.openxml4j.opc.PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_URI;
-import static org.junit.Assert.assertTrue;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
+import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
+import org.junit.Test;
 
 public class TestZipPackagePropertiesMarshaller {
     private PartMarshaller marshaller = new ZipPackagePropertiesMarshaller();
@@ -58,7 +58,7 @@ public class TestZipPackagePropertiesMarshaller {
         marshaller.marshall(new PackagePropertiesPart(null, PackagingURIHelper.createPartName(PACKAGE_RELATIONSHIPS_ROOT_URI)),
                 new ZipOutputStream(new ByteArrayOutputStream()) {
                     @Override
-                    public void putNextEntry(ZipEntry e) throws IOException {
+                    public void putNextEntry(final ZipEntry archiveEntry) throws IOException {
                         throw new IOException("TestException");
                     }
                 });
index c6c8f959e778ec79c7790598da1966d05b7df96f..027dd0e884bec9aa8fc4f6cce5c312c215114edc 100644 (file)
@@ -17,7 +17,9 @@
 package org.apache.poi.poifs.crypt;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -26,6 +28,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
+import java.util.stream.IntStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
@@ -146,21 +149,27 @@ public class TestDecryptor {
         // the test file contains a wrong ole entry size, produced by extenxls
         // the fix limits the available size and tries to read all entries 
         File f = POIDataSamples.getPOIFSInstance().getFile("extenxls_pwd123.xlsx");
-        NPOIFSFileSystem fs = new NPOIFSFileSystem(f, true);
-        EncryptionInfo info = new EncryptionInfo(fs);
-        Decryptor d = Decryptor.getInstance(info);
-        d.verifyPassword("pwd123");
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        ZipInputStream zis = new ZipInputStream(d.getDataStream(fs));
-        ZipEntry ze;
-        while ((ze = zis.getNextEntry()) != null) {
-            bos.reset();
-            IOUtils.copy(zis, bos);
-            assertEquals(ze.getSize(), bos.size());
+
+        try (NPOIFSFileSystem fs = new NPOIFSFileSystem(f, true)) {
+            EncryptionInfo info = new EncryptionInfo(fs);
+            Decryptor d = Decryptor.getInstance(info);
+            d.verifyPassword("pwd123");
+
+            final ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);
+            try (final ZipInputStream zis = new ZipInputStream(d.getDataStream(fs))) {
+                IntStream.of(3711, 1155, 445, 9376, 450, 588, 1337, 2593, 304, 7910).forEach(size -> {
+                    try {
+                        final ZipEntry ze = zis.getNextEntry();
+                        assertNotNull(ze);
+                        IOUtils.copy(zis, bos);
+                        assertEquals(size, bos.size());
+                        bos.reset();
+                    } catch (IOException e) {
+                        fail(e.getMessage());
+                    }
+                });
+            }
         }
-        
-        zis.close();
-        fs.close();
     }
 
     @Test
index 96bc0654e65fb08182cee5f7d30b460ae7abb053..a25d26b6cc9402ff953222aec510825eb161be67 100644 (file)
@@ -24,6 +24,7 @@ import static org.apache.poi.POITestCase.assertEndsWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -245,7 +246,7 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
         SXSSFWorkbook wb = new SXSSFWorkbook();
         SXSSFSheet sh = wb.createSheet();
         SheetDataWriter wr = sh.getSheetDataWriter();
-        assertTrue(wr.getClass() == SheetDataWriter.class);
+        assertSame(wr.getClass(), SheetDataWriter.class);
         File tmp = wr.getTempFile();
         assertStartsWith(tmp.getName(), "poi-sxssf-sheet");
         assertEndsWith(tmp.getName(), ".xml");
@@ -256,7 +257,7 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
         wb.setCompressTempFiles(true);
         sh = wb.createSheet();
         wr = sh.getSheetDataWriter();
-        assertTrue(wr.getClass() == GZIPSheetDataWriter.class);
+        assertSame(wr.getClass(), GZIPSheetDataWriter.class);
         tmp = wr.getTempFile();
         assertStartsWith(tmp.getName(), "poi-sxssf-sheet-xml");
         assertEndsWith(tmp.getName(), ".gz");
@@ -279,22 +280,10 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
     public void gzipSheetdataWriter() throws IOException {
         SXSSFWorkbook wb = new SXSSFWorkbook();
         wb.setCompressTempFiles(true);
-        int rowNum = 1000;
-        int sheetNum = 5;
-        for(int i = 0; i < sheetNum; i++){
-            Sheet sh = wb.createSheet("sheet" + i);
-            for(int j = 0; j < rowNum; j++){
-                Row row = sh.createRow(j);
-                Cell cell1 = row.createCell(0);
-                cell1.setCellValue(new CellReference(cell1).formatAsString());
-
-                Cell cell2 = row.createCell(1);
-                cell2.setCellValue(i);
 
-                Cell cell3 = row.createCell(2);
-                cell3.setCellValue(j);
-            }
-        }
+        final int rowNum = 1000;
+        final int sheetNum = 5;
+        populateData(wb, 1000, 5);
 
         XSSFWorkbook xwb = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
         for(int i = 0; i < sheetNum; i++){
@@ -319,10 +308,24 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
         wb.close();
     }
 
-    protected static void assertWorkbookDispose(SXSSFWorkbook wb)
+    private static void assertWorkbookDispose(SXSSFWorkbook wb)
     {
-        int rowNum = 1000;
-        int sheetNum = 5;
+        populateData(wb, 1000, 5);
+
+        for (Sheet sheet : wb) {
+            SXSSFSheet sxSheet = (SXSSFSheet) sheet;
+            assertTrue(sxSheet.getSheetDataWriter().getTempFile().exists());
+        }
+
+        assertTrue(wb.dispose());
+
+        for (Sheet sheet : wb) {
+            SXSSFSheet sxSheet = (SXSSFSheet) sheet;
+            assertFalse(sxSheet.getSheetDataWriter().getTempFile().exists());
+        }
+    }
+
+    private static void populateData(Workbook wb, final int rowNum, final int sheetNum) {
         for(int i = 0; i < sheetNum; i++){
             Sheet sh = wb.createSheet("sheet" + i);
             for(int j = 0; j < rowNum; j++){
@@ -337,18 +340,6 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
                 cell3.setCellValue(j);
             }
         }
-
-        for (Sheet sheet : wb) {
-            SXSSFSheet sxSheet = (SXSSFSheet) sheet;
-            assertTrue(sxSheet.getSheetDataWriter().getTempFile().exists());
-        }
-
-        assertTrue(wb.dispose());
-
-        for (Sheet sheet : wb) {
-            SXSSFSheet sxSheet = (SXSSFSheet) sheet;
-            assertFalse(sxSheet.getSheetDataWriter().getTempFile().exists());
-        }
     }
 
     @Test
@@ -440,43 +431,11 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
         }
     }
 
-    @Ignore("Just a local test for http://stackoverflow.com/questions/33627329/apache-poi-streaming-api-using-xssf-template")
     @Test
-    public void testTemplateFile() throws IOException {
-        XSSFWorkbook workBook = XSSFTestDataSamples.openSampleWorkbook("sample.xlsx");
-        SXSSFWorkbook streamingWorkBook = new SXSSFWorkbook(workBook,10);
-        Sheet sheet = streamingWorkBook.getSheet("Sheet1");
-        for(int rowNum = 10;rowNum < 1000000;rowNum++) {
-            Row row = sheet.createRow(rowNum);
-            for(int cellNum = 0;cellNum < 700;cellNum++) {
-                Cell cell = row.createCell(cellNum);
-                cell.setCellValue("somevalue");
-            }
-            
-            if(rowNum % 100 == 0) {
-                System.out.print(".");
-                if(rowNum % 10000 == 0) {
-                    System.out.println(rowNum);
-                }
-            }
-        }
-
-        FileOutputStream fos = new FileOutputStream("C:\\temp\\streaming.xlsx");
-        streamingWorkBook.write(fos);
-        fos.close();
-        
-        streamingWorkBook.close();
-        workBook.close();
-    }
-    
-
-    @Test
-    public void closeDoesNotModifyWorkbook() throws IOException, InvalidFormatException {
+    public void closeDoesNotModifyWorkbook() throws IOException {
         final String filename = "SampleSS.xlsx";
         final File file = POIDataSamples.getSpreadSheetInstance().getFile(filename);
-        SXSSFWorkbook wb = null;
-        XSSFWorkbook xwb = null;
-        
+
         // Some tests commented out because close() modifies the file
         // See bug 58779
         
@@ -489,19 +448,11 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
         //assertCloseDoesNotModifyFile(filename, wb);
         
         // InputStream
-        FileInputStream fis = new FileInputStream(file);
-        try {
-            xwb = new XSSFWorkbook(fis);
-            wb = new SXSSFWorkbook(xwb);
+
+        try (FileInputStream fis = new FileInputStream(file);
+             XSSFWorkbook xwb = new XSSFWorkbook(fis);
+             SXSSFWorkbook wb = new SXSSFWorkbook(xwb)) {
             assertCloseDoesNotModifyFile(filename, wb);
-        } finally {
-            if (xwb != null) {
-                xwb.close();
-            }
-            if (wb != null) {
-                wb.close();
-            }
-            fis.close();
         }
         
         // OPCPackage
@@ -531,7 +482,6 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
                 System.arraycopy(prefix, 0, useless, 0, prefix.length);
                 String ul = new String(useless);
                 r.createCell(col, CellType.STRING).setCellValue(ul);
-                ul = null;
             }
         }
         
@@ -573,12 +523,12 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
         xssf = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()));
         s = xssf.getSheet(sheetName);
         assertEquals(10, s.getLastRowNum());
-        assertEquals(true, s.getRow(0).getCell(0).getBooleanCellValue());
+        assertTrue(s.getRow(0).getCell(0).getBooleanCellValue());
         assertEquals("Test Row 9", s.getRow(9).getCell(2).getStringCellValue());
     }
 
     @Test
-    public void test56557() throws IOException, InvalidFormatException {
+    public void test56557() throws IOException {
         Workbook wb = XSSFTestDataSamples.openSampleWorkbook("56557.xlsx");
 
         // Using streaming XSSFWorkbook makes the output file invalid