You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by bo...@apache.org on 2013/01/22 13:45:25 UTC

svn commit: r1436879 - in /commons/proper/compress/trunk/src: changes/ main/java/org/apache/commons/compress/archivers/zip/ test/java/org/apache/commons/compress/archivers/ test/java/org/apache/commons/compress/archivers/zip/ test/resources/

Author: bodewig
Date: Tue Jan 22 12:45:24 2013
New Revision: 1436879

URL: http://svn.apache.org/viewvc?rev=1436879&view=rev
Log:
consume remainder of ZIP when last LF-entry has been read

Added:
    commons/proper/compress/trunk/src/test/resources/archive_with_trailer.zip
      - copied, changed from r1435891, commons/proper/compress/trunk/src/test/resources/bla.zip
Modified:
    commons/proper/compress/trunk/src/changes/changes.xml
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java
    commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java
    commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java

Modified: commons/proper/compress/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/changes/changes.xml?rev=1436879&r1=1436878&r2=1436879&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/changes/changes.xml (original)
+++ commons/proper/compress/trunk/src/changes/changes.xml Tue Jan 22 12:45:24 2013
@@ -143,6 +143,10 @@ The <action> type attribute can be add,u
           original input stream when it reaches the end of the
           archive.
         </action>
+        <action type="fix" date="2013-01-22">
+          ZipArchiveInputStream now consumes the remainder of the
+          archive when getNextZipEntry returns null.
+        </action>
     </release>
     <release version="1.4.1" date="2012-05-23"
              description="Release 1.4.1">
@@ -150,7 +154,6 @@ The <action> type attribute can be add,u
         Ported libbzip2's fallback sort algorithm to
         BZip2CompressorOutputStream to speed up compression in certain
         edge cases.
-
         Using specially crafted inputs this can be used as a denial
         of service attack.  See the security reports page for details.
       </action>

Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java?rev=1436879&r1=1436878&r2=1436879&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java (original)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java Tue Jan 22 12:45:24 2013
@@ -126,14 +126,38 @@ public class ZipArchiveInputStream exten
       extra field length              2 bytes
     */
 
+    private static final int CFH_LEN = 46;
+    /*
+        central file header signature   4 bytes  (0x02014b50)
+        version made by                 2 bytes
+        version needed to extract       2 bytes
+        general purpose bit flag        2 bytes
+        compression method              2 bytes
+        last mod file time              2 bytes
+        last mod file date              2 bytes
+        crc-32                          4 bytes
+        compressed size                 4 bytes
+        uncompressed size               4 bytes
+        file name length                2 bytes
+        extra field length              2 bytes
+        file comment length             2 bytes
+        disk number start               2 bytes
+        internal file attributes        2 bytes
+        external file attributes        4 bytes
+        relative offset of local header 4 bytes
+    */
+
     private static final long TWO_EXP_32 = ZIP64_MAGIC + 1;
 
     // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
     private final byte[] LFH_BUF = new byte[LFH_LEN];
     private final byte[] SKIP_BUF = new byte[1024];
+    private final byte[] SHORT_BUF = new byte[SHORT];
     private final byte[] WORD_BUF = new byte[WORD];
     private final byte[] TWO_DWORD_BUF = new byte[2 * DWORD];
 
+    private int entriesRead = 0;
+
     public ZipArchiveInputStream(InputStream inputStream) {
         this(inputStream, ZipEncodingHelper.UTF8);
     }
@@ -204,9 +228,9 @@ public class ZipArchiveInputStream exten
         }
             
         ZipLong sig = new ZipLong(LFH_BUF);
-        if (sig.equals(ZipLong.CFH_SIG)) {
+        if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG)) {
             hitCentralDirectory = true;
-            return null;
+            skipRemainderOfArchive();
         }
         if (!sig.equals(ZipLong.LFH_SIG)) {
             return null;
@@ -271,6 +295,7 @@ public class ZipArchiveInputStream exten
         }
 
         processZip64Extra(size, cSize);
+        entriesRead++;
         return current.entry;
     }
 
@@ -834,6 +859,122 @@ public class ZipArchiveInputStream exten
         pushedBackBytes(length);
     }
 
+    // End of Central Directory Record
+    //   end of central dir signature    4 bytes  (0x06054b50)
+    //   number of this disk             2 bytes
+    //   number of the disk with the
+    //   start of the central directory  2 bytes
+    //   total number of entries in the
+    //   central directory on this disk  2 bytes
+    //   total number of entries in
+    //   the central directory           2 bytes
+    //   size of the central directory   4 bytes
+    //   offset of start of central
+    //   directory with respect to
+    //   the starting disk number        4 bytes
+    //   .ZIP file comment length        2 bytes
+    //   .ZIP file comment       (variable size)
+    //
+
+    /**
+     * Reads the stream until it find the "End of central directory
+     * record" and consumes it as well.
+     */
+    private void skipRemainderOfArchive() throws IOException {
+        // skip over central directory. One LFH has been read too much
+        // already.  The calculation discounts file names and extra
+        // data so it will be too short.
+        realSkip(entriesRead * CFH_LEN - LFH_LEN);
+        findEocdRecord();
+        realSkip(ZipFile.MIN_EOCD_SIZE
+                 - WORD /* signature */ - SHORT /* comment len */);
+        readFully(SHORT_BUF);
+        // file comment
+        realSkip(ZipShort.getValue(SHORT_BUF));
+    }
+
+    /**
+     * Reads forward until the signature of the &quot;End of central
+     * directory&quot; recod is found.
+     */
+    private void findEocdRecord() throws IOException {
+        int currentByte = -1;
+        boolean skipReadCall = false;
+        while (skipReadCall || (currentByte = readOneByte()) > -1) {
+            skipReadCall = false;
+            if (!isFirstByteOfEocdSig(currentByte)) {
+                continue;
+            }
+            currentByte = readOneByte();
+            if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) {
+                if (currentByte == -1) {
+                    break;
+                }
+                skipReadCall = isFirstByteOfEocdSig(currentByte);
+                continue;
+            }
+            currentByte = readOneByte();
+            if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) {
+                if (currentByte == -1) {
+                    break;
+                }
+                skipReadCall = isFirstByteOfEocdSig(currentByte);
+                continue;
+            }
+            currentByte = readOneByte();
+            if (currentByte == -1
+                || currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) {
+                break;
+            }
+            skipReadCall = isFirstByteOfEocdSig(currentByte);
+        }
+    }
+
+    /**
+     * Skips bytes by reading from the underlying stream rather than
+     * the (potentially inflating) archive stream - which {@link
+     * #skip} would do.
+     *
+     * Also updates bytes-read counter.
+     */
+    private void realSkip(long value) throws IOException {
+        if (value >= 0) {
+            long skipped = 0;
+            while (skipped < value) {
+                long rem = value - skipped;
+                int x = in.read(SKIP_BUF, 0,
+                                (int) (SKIP_BUF.length > rem ? rem
+                                       : SKIP_BUF.length));
+                if (x == -1) {
+                    return;
+                }
+                count(x);
+                skipped += x;
+            }
+            return;
+        }
+        throw new IllegalArgumentException();
+    }
+
+    /**
+     * Reads bytes by reading from the underlying stream rather than
+     * the (potentially inflating) archive stream - which {@link
+     * #read} would do.
+     *
+     * Also updates bytes-read counter.
+     */
+    private int readOneByte() throws IOException {
+        int b = in.read();
+        if (b != -1) {
+            count(1);
+        }
+        return b;
+    }
+
+    private boolean isFirstByteOfEocdSig(int b) {
+        return b == ZipArchiveOutputStream.EOCD_SIG[0];
+    }
+
     /**
      * Structure collecting information for the entry that is
      * currently being read.

Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java?rev=1436879&r1=1436878&r2=1436879&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java (original)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java Tue Jan 22 12:45:24 2013
@@ -571,7 +571,7 @@ public class ZipFile {
      * supposed to be the last structure of the archive - without file
      * comment.
      */
-    private static final int MIN_EOCD_SIZE =
+    static final int MIN_EOCD_SIZE =
         /* end of central dir signature    */ WORD
         /* number of this disk             */ + SHORT
         /* number of the disk with the     */
@@ -685,6 +685,9 @@ public class ZipFile {
      * finds the &quot;Zip64 end of central directory record&quot; using the
      * parsed information, parses that and positions the stream at the
      * first central directory record.
+     *
+     * Expects stream to be positioned right behind the &quot;Zip64
+     * end of central directory locator&quot;'s signature.
      */
     private void positionAtCentralDirectory64()
         throws IOException {
@@ -704,9 +707,11 @@ public class ZipFile {
     }
 
     /**
-     * Searches for the &quot;End of central dir record&quot;, parses
-     * it and positions the stream at the first central directory
-     * record.
+     * Parses the &quot;End of central dir record&quot; and positions
+     * the stream at the first central directory record.
+     *
+     * Expects stream to be positioned at the beginning of the
+     * &quot;End of central dir record&quot;.
      */
     private void positionAtCentralDirectory32()
         throws IOException {

Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java?rev=1436879&r1=1436878&r2=1436879&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java (original)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java Tue Jan 22 12:45:24 2013
@@ -81,6 +81,12 @@ public final class ZipLong implements Cl
         new ZipLong(0X30304B50L);
 
     /**
+     * Archive extra data record signature.</p>
+     * @since 1.5
+     */
+    public static final ZipLong AED_SIG = new ZipLong(0X08064B50L);
+
+    /**
      * Create instance from a number.
      * @param value the long to store as a ZipLong
      */

Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java?rev=1436879&r1=1436878&r2=1436879&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java (original)
+++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java Tue Jan 22 12:45:24 2013
@@ -167,6 +167,7 @@ public final class ZipTestCase extends A
             try {
                 assertNull(zip.getNextZipEntry());
             } catch (IOException e) {
+                e.printStackTrace();
                 fail("COMPRESS-93: Unable to skip an unsupported zip entry");
             }
         } finally {

Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java?rev=1436879&r1=1436878&r2=1436879&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java (original)
+++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStreamTest.java Tue Jan 22 12:45:24 2013
@@ -18,12 +18,14 @@
 
 package org.apache.commons.compress.archivers.zip;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 
@@ -79,4 +81,20 @@ public class ZipArchiveInputStreamTest {
         }
     }
 
+    @Test
+    public void shouldConsumeArchiveCompletely() throws Exception {
+        InputStream is = ZipArchiveInputStreamTest.class
+            .getResourceAsStream("/archive_with_trailer.zip");
+        ZipArchiveInputStream zip = new ZipArchiveInputStream(is);
+        while (zip.getNextZipEntry() != null) {
+            // just consume the archive
+            ;
+        }
+        byte[] expected = new byte[] {
+            'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n'
+        };
+        byte[] actual = new byte[expected.length];
+        is.read(actual);
+        assertArrayEquals(expected, actual);
+    }
 }

Copied: commons/proper/compress/trunk/src/test/resources/archive_with_trailer.zip (from r1435891, commons/proper/compress/trunk/src/test/resources/bla.zip)
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/resources/archive_with_trailer.zip?p2=commons/proper/compress/trunk/src/test/resources/archive_with_trailer.zip&p1=commons/proper/compress/trunk/src/test/resources/bla.zip&r1=1435891&r2=1436879&rev=1436879&view=diff
==============================================================================
Binary files - no diff available.