commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bode...@apache.org
Subject svn commit: r925257 - in /commons/proper/compress/trunk/src: changes/changes.xml main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java test/java/org/apache/commons/compress/archivers/zip/Lister.java
Date Fri, 19 Mar 2010 15:08:23 GMT
Author: bodewig
Date: Fri Mar 19 15:08:23 2010
New Revision: 925257

URL: http://svn.apache.org/viewvc?rev=925257&view=rev
Log:
optionally read STORED entries with data descriptors.  COMPRESS-103

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/test/java/org/apache/commons/compress/archivers/zip/Lister.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=925257&r1=925256&r2=925257&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/changes/changes.xml (original)
+++ commons/proper/compress/trunk/src/changes/changes.xml Fri Mar 19 15:08:23 2010
@@ -23,6 +23,13 @@
   </properties>
   <body>
     <release version="1.1" date="as in SVN" description="Release 1.1">
+      <action type="add" date="2010-03-19" issue="COMPRESS-103">
+        ZipArchiveInputStream can optionally extract data that used
+        the STORED compression method and a data descriptor.
+        Doing so in a stream is not safe in general, so you have to
+        explicitly enable the feature.  By default the stream will
+        throw an exception if it encounters such an entry.
+      </action>
       <action type="fix" date="2010-03-12" issue="COMPRESS-100">
         ZipArchiveInputStream will throw an exception if it detects an
         entry that uses a data descriptor for a STORED entry since it

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=925257&r1=925256&r2=925257&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
Fri Mar 19 15:08:23 2010
@@ -18,6 +18,8 @@
  */
 package org.apache.commons.compress.archivers.zip;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -70,6 +72,9 @@ public class ZipArchiveInputStream exten
     private int bytesReadFromStream = 0;
     private int lengthOfLastRead = 0;
     private boolean hasDataDescriptor = false;
+    private ByteArrayInputStream lastStoredEntry = null;
+
+    private boolean allowStoredEntriesWithDataDescriptor = false;
 
     private static final int LFH_LEN = 30;
     /*
@@ -99,9 +104,27 @@ public class ZipArchiveInputStream exten
     public ZipArchiveInputStream(InputStream inputStream,
                                  String encoding,
                                  boolean useUnicodeExtraFields) {
+        this(inputStream, encoding, useUnicodeExtraFields, false);
+    }
+
+    /**
+     * @param encoding the encoding to use for file names, use null
+     * for the platform's default encoding
+     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
+     * Extra Fields (if present) to set the file names.
+     * @param allowStoredEntriesWithDataDescriptor whether the stream
+     * will try to read STORED entries that use a data descriptor
+     * @since Apache Commons Compress 1.1
+     */
+    public ZipArchiveInputStream(InputStream inputStream,
+                                 String encoding,
+                                 boolean useUnicodeExtraFields,
+                                 boolean allowStoredEntriesWithDataDescriptor) {
         zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
         this.useUnicodeExtraFields = useUnicodeExtraFields;
         in = new PushbackInputStream(inputStream, buf.length);
+        this.allowStoredEntriesWithDataDescriptor =
+            allowStoredEntriesWithDataDescriptor;
     }
 
     public ZipArchiveEntry getNextZipEntry() throws IOException {
@@ -225,6 +248,13 @@ public class ZipArchiveInputStream exten
             }
 
             if (current.getMethod() == ZipArchiveOutputStream.STORED) {
+                if (hasDataDescriptor) {
+                    if (lastStoredEntry == null) {
+                        readStoredEntry();
+                    }
+                    return lastStoredEntry.read(buffer, start, length);
+                }
+
                 int csize = (int) current.getSize();
                 if (readBytesOfEntry >= csize) {
                     return -1;
@@ -380,7 +410,7 @@ public class ZipArchiveInputStream exten
             }
         }
 
-        if (hasDataDescriptor) {
+        if (lastStoredEntry == null && hasDataDescriptor) {
             readDataDescriptor();
         }
 
@@ -389,6 +419,7 @@ public class ZipArchiveInputStream exten
             lengthOfLastRead = 0;
         crc.reset();
         current = null;
+        lastStoredEntry = null;
     }
 
     private void fill() throws IOException {
@@ -431,12 +462,96 @@ public class ZipArchiveInputStream exten
     /**
      * Whether this entry requires a data descriptor this library can work with.
      *
-     * @return true if the entry doesn't require any data descriptor
-     * or the method is DEFLATED).
+     * @return true if allowStoredEntriesWithDataDescriptor is true,
+     * the entry doesn't require any data descriptor or the method is
+     * DEFLATED.
      */
-    private static boolean supportsDataDescriptorFor(ZipArchiveEntry entry) {
-        return !entry.getGeneralPurposeBit().usesDataDescriptor()
+    private boolean supportsDataDescriptorFor(ZipArchiveEntry entry) {
+        return allowStoredEntriesWithDataDescriptor ||
+            !entry.getGeneralPurposeBit().usesDataDescriptor()
             || entry.getMethod() == ZipArchiveEntry.DEFLATED;
     }
 
+    /**
+     * Caches a stored entry that uses the data descriptor.
+     *
+     * <ul>
+     *   <li>Reads a stored entry until the signature of a local file
+     *     header, central directory header or data descriptor has been
+     *     found.</li>
+     *   <li>Stores all entry data in lastStoredEntry.</p>
+     *   <li>Rewinds the stream to position at the data
+     *     descriptor.</li>
+     *   <li>reads the data descriptor</li>
+     * </ul>
+     *
+     * <p>After calling this method the entry should know its size,
+     * the entry's data is cached and the stream is positioned at the
+     * next local file or central directory header.</p>
+     */
+    private void readStoredEntry() throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        byte[] LFH = ZipLong.LFH_SIG.getBytes();
+        byte[] CFH = ZipLong.CFH_SIG.getBytes();
+        byte[] DD = ZipLong.DD_SIG.getBytes();
+        int off = 0;
+        boolean done = false;
+
+        while (!done) {
+            int r = in.read(buf, off, ZipArchiveOutputStream.BUFFER_SIZE - off);
+            if (r <= 0) {
+                // read the whole archive without ever finding a
+                // central directory
+                throw new IOException("Truncated ZIP file");
+            }
+            if (r + off < 4) {
+                // buf is too small to check for a signature, loop
+                off += r;
+                continue;
+            }
+
+            int readTooMuch = 0;
+            for (int i = 0; !done && i < r - 4; i++) {
+                if (buf[i] == LFH[0] && buf[i + 1] == LFH[1]) {
+                    if ((buf[i + 2] == LFH[2] && buf[i + 3] == LFH[3])
+                        || (buf[i] == CFH[2] && buf[i + 3] == CFH[3])) {
+                        // found a LFH or CFH:
+                        readTooMuch = off + r - i - 12 /* dd without signature */;
+                        done = true;
+                    }
+                    else if (buf[i + 2] == DD[2] && buf[i + 3] == DD[3]) {
+                        // found DD:
+                        readTooMuch = off + r - i;
+                        done = true;
+                    }
+                    if (done) {
+                        // * push back bytes read in excess as well as the data
+                        //   descriptor
+                        // * copy the remaining bytes to cache
+                        // * read data descriptor
+                        ((PushbackInputStream) in).unread(buf, off + r - readTooMuch, readTooMuch);
+                        bos.write(buf, 0, i);
+                        readDataDescriptor();
+                    }
+                }
+            }
+            if (!done) {
+                // worst case we've read a data descriptor without a
+                // signature (12 bytes) plus the first three bytes of
+                // a LFH or CFH signature
+                // save the last 15 bytes in the buffer, cache
+                // anything in front of that, read on
+                if (off + r > 15) {
+                    bos.write(buf, 0, off + r - 15);
+                    System.arraycopy(buf, off + r - 15, buf, 0, 15);
+                    off = 15;
+                } else {
+                    off += r;
+                }
+            }
+        }
+
+        byte[] b = bos.toByteArray();
+        lastStoredEntry = new ByteArrayInputStream(b);
+    }
 }

Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Lister.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Lister.java?rev=925257&r1=925256&r2=925257&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Lister.java
(original)
+++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Lister.java
Fri Mar 19 15:08:23 2010
@@ -21,9 +21,12 @@ package org.apache.commons.compress.arch
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Enumeration;
 import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.utils.IOUtils;
 
 /**
  * Simple command line application that lists the contents of a ZIP archive.
@@ -38,6 +41,8 @@ public final class Lister {
         String archive;
         boolean useStream = false;
         String encoding;
+        boolean allowStoredEntriesWithDataDescriptor = false;
+        String dir;
     }
 
     public static void main(String[] args) throws IOException {
@@ -52,11 +57,16 @@ public final class Lister {
                 new BufferedInputStream(new FileInputStream(f));
             try {
                 ZipArchiveInputStream zs =
-                    new ZipArchiveInputStream(fs, cl.encoding, true);
+                    new ZipArchiveInputStream(fs, cl.encoding, true,
+                                              cl.allowStoredEntriesWithDataDescriptor);
                 for (ArchiveEntry entry = zs.getNextEntry();
                      entry != null;
                      entry = zs.getNextEntry()) {
-                    list((ZipArchiveEntry) entry);
+                    ZipArchiveEntry ze = (ZipArchiveEntry) entry;
+                    list(ze);
+                    if (cl.dir != null) {
+                        extract(cl.dir, ze, zs);
+                    }
                 }
             } finally {
                 fs.close();
@@ -66,7 +76,16 @@ public final class Lister {
             try {
                 for (Enumeration entries = zf.getEntries();
                      entries.hasMoreElements(); ) {
-                    list((ZipArchiveEntry) entries.nextElement());
+                    ZipArchiveEntry ze = (ZipArchiveEntry) entries.nextElement();
+                    list(ze);
+                    if (cl.dir != null) {
+                        InputStream is = zf.getInputStream(ze);
+                        try {
+                            extract(cl.dir, ze, is);
+                        } finally {
+                            is.close();
+                        }
+                    }
                 }
             } finally {
                 zf.close();
@@ -78,6 +97,23 @@ public final class Lister {
         System.out.println(entry.getName());
     }
 
+    private static void extract(String dir, ZipArchiveEntry entry,
+                                InputStream is) throws IOException {
+        File f = new File(dir, entry.getName());
+        if (!f.getParentFile().exists()) {
+            f.getParentFile().mkdirs();
+        }
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(f);
+            IOUtils.copy(is, fos);
+        } finally {
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
     private static CommandLine parse(String[] args) {
         CommandLine cl = new CommandLine();
         boolean error = false;
@@ -89,8 +125,17 @@ public final class Lister {
                     System.err.println("missing argument to -enc");
                     error = true;
                 }
+            } else if (args[i].equals("-extract")) {
+                if (args.length > i + 1) {
+                    cl.dir = args[++i];
+                } else {
+                    System.err.println("missing argument to -extract");
+                    error = true;
+                }
             } else if (args[i].equals("-stream")) {
                 cl.useStream = true;
+            } else if (args[i].equals("+storeddd")) {
+                cl.allowStoredEntriesWithDataDescriptor = true;
             } else if (args[i].equals("-file")) {
                 cl.useStream = false;
             } else if (cl.archive != null) {
@@ -107,7 +152,8 @@ public final class Lister {
     }
 
     private static void usage() {
-        System.err.println("lister [-enc encoding] [-stream] [-file] archive");
+        System.err.println("lister [-enc encoding] [-stream] [-file]"
+                           + " [+storeddd] [-extract dir] archive");
         System.exit(1);
     }
 }
\ No newline at end of file



Mime
View raw message