Zip files created with the zip task store the CRC and size of deflated entries
in the data descriptor _following_ the actual data.  This means that if the
zip file is processed later using a ZipInputStream, it is not possible to
determine the CRC and size of each entry until the data has been read (see bug
ID 19195). The command line zip tool does not have this limitation.  

I have attached patches below to ZipOutputStream.java and Zip.java which use a
RandomAccessFile where possible to create the file.  The CRC and size can then
be written into the local file header before the data.  In addition, it is no
longer necessary to set the CRC of a non-deflated entry before it is added to
the file.

The patches are against a recent 1.6 build.

Richard Evans

--- org/apache/tools/zip/ZipOutputStream.java.orig      Tue Jul 15 17:45:26 2003
+++ org/apache/tools/zip/ZipOutputStream.java   Thu Jul 17 11:04:19 2003
@@ -54,7 +54,11 @@
 
 package org.apache.tools.zip;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
 import java.io.OutputStream;
+import java.io.RandomAccessFile;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.util.Date;
@@ -82,7 +86,7 @@
  * @author Stefan Bodewig
  * @version $Revision: 1.13 $
  */
-public class ZipOutputStream extends DeflaterOutputStream {
+public class ZipOutputStream extends FilterOutputStream {
 
     /**
      * Current entry.
@@ -149,6 +153,13 @@
     private long dataStart = 0;
 
     /**
+     * Data for local header data
+     *
+     * @since 1.6
+     */
+    private long localDataStart = 0;
+
+    /**
      * Start of central directory.
      *
      * @since 1.1
@@ -209,12 +220,52 @@
     public static final int STORED = ZipEntry.STORED;
 
     /**
+     * Deflater object for output 
+     *
+     * @since 1.6
+     */
+    private Deflater def;
+
+    /**
+     * Deflater buffer
+     *
+     * @since 1.6
+     */
+    private byte [] buf = new byte [512];
+
+    /**
+     * Optional random access output
+     *
+     * @since 1.6
+     */
+    private RandomAccessFile random;
+
+    /**
      * Creates a new ZIP OutputStream filtering the underlying stream.
      *
      * @since 1.1
      */
     public ZipOutputStream(OutputStream out) {
-        super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
+        super(out);
+       def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
+    }
+
+    /**
+     * Creates a new ZIP OutputStream writing to a File.  Will use random 
access if possible.
+     *
+     * @since 1.6
+     */
+    public ZipOutputStream(File file) throws IOException {
+        super(null);
+       def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
+
+       try {  
+         random = new RandomAccessFile(file, "rw");
+         random.setLength(0);
+       }
+       catch (IOException e) {
+         out = new FileOutputStream(file);
+       }
     }
 
     /**
@@ -292,23 +343,42 @@
 
             written += entry.getCompressedSize();
         } else {
-            if (entry.getCrc() != realCrc) {
-                throw new ZipException("bad CRC checksum for entry "
-                                       + entry.getName() + ": " 
-                                       + Long.toHexString(entry.getCrc())
-                                       + " instead of " 
-                                       + Long.toHexString(realCrc));
-            }
-
-            if (entry.getSize() != written - dataStart) {
-                throw new ZipException("bad size for entry "
-                                       + entry.getName() + ": " 
-                                       + entry.getSize()
-                                       + " instead of " 
-                                       + (written - dataStart));
-            }
-
-        }
+           if (random == null) {
+              if (entry.getCrc() != realCrc) {
+                  throw new ZipException("bad CRC checksum for entry "
+                                         + entry.getName() + ": " 
+                                         + Long.toHexString(entry.getCrc())
+                                         + " instead of " 
+                                         + Long.toHexString(realCrc));
+              }
+
+              if (entry.getSize() != written - dataStart) {
+                  throw new ZipException("bad size for entry "
+                                         + entry.getName() + ": " 
+                                         + entry.getSize()
+                                         + " instead of " 
+                                         + (written - dataStart));
+              }
+           } else {
+             long size = written - dataStart;
+
+             entry.setSize(size);
+             entry.setComprSize(size);
+             entry.setCrc(realCrc);
+           }
+        }
+
+       // If random access output, write the local file header containing
+       // the correct CRC and compressed/uncompressed sizes
+       if (random != null) {
+         long save = random.getFilePointer();
+
+         random.seek(localDataStart);
+         writeOut((new ZipLong(entry.getCrc())).getBytes());
+         writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
+         writeOut((new ZipLong(entry.getSize())).getBytes());
+         random.seek(save);
+       }
 
         writeDataDescriptor(entry);
         entry = null;
@@ -334,13 +404,16 @@
         }
 
         if (entry.getMethod() == STORED) {
-            if (entry.getSize() == -1) {
-                throw new ZipException("uncompressed size is required for 
STORED method");
-            }
-            if (entry.getCrc() == -1) {
-                throw new ZipException("crc checksum is required for STORED 
method");
-            }
-            entry.setComprSize(entry.getSize());
+         // Size/CRC not required if 'random' output is available
+         if (random == null) {
+              if (entry.getSize() == -1) {
+                  throw new ZipException("uncompressed size is required for 
STORED method");
+              }
+              if (entry.getCrc() == -1) {
+                  throw new ZipException("crc checksum is required for STORED 
method");
+              }
+              entry.setComprSize(entry.getSize());
+         }
         } else if (hasCompressionLevelChanged) {
             def.setLevel(level);
             hasCompressionLevelChanged = false;
@@ -388,14 +461,63 @@
      */
     public void write(byte[] b, int offset, int length) throws IOException {
         if (entry.getMethod() == DEFLATED) {
-            super.write(b, offset, length);
+           if (length > 0) {
+              if (!def.finished()) {
+                def.setInput(b, offset, length);
+                while (!def.needsInput()) {
+                  deflate();
+                }
+              }
+           }
         } else {
-            out.write(b, offset, length);
+            writeOut(b, offset, length);
             written += length;
         }
         crc.update(b, offset, length);
     }
 
+    public void write(int b) throws IOException {
+        byte[] buf = new byte[1];
+       buf[0] = (byte)(b & 0xff);
+       write(buf, 0, 1);
+    }
+
+    protected void deflate() throws IOException {
+       int len = def.deflate(buf, 0, buf.length);
+       if (len > 0) {
+           if (out == null)
+             random.write(buf, 0, len);
+            else
+             out.write(buf, 0, len);
+       }
+    }
+
+    /**
+     * Closes this output stream and releases any system resources 
+     * associated with the stream. 
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void close() throws IOException {
+       finish();
+
+       if (random != null)
+         random.close();
+        else
+         out.close();
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes 
+     * to be written out to the stream. 
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void flush() throws IOException {
+       if (random == null)
+         out.flush();
+    }
+
     /*
      * Various ZIP constants
      */
@@ -432,62 +554,66 @@
     protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
         offsets.put(ze, new ZipLong(written));
 
-        out.write(LFH_SIG.getBytes());
+        writeOut(LFH_SIG.getBytes());
         written += 4;
 
         // version needed to extract
         // general purpose bit flag
-        if (ze.getMethod() == DEFLATED) {
+        if (ze.getMethod() == DEFLATED && random == null) {
             // requires version 2 as we are going to store length info
             // in the data descriptor
-            out.write((new ZipShort(20)).getBytes());
+            writeOut((new ZipShort(20)).getBytes());
 
             // bit3 set to signal, we use a data descriptor
-            out.write((new ZipShort(8)).getBytes());
+            writeOut((new ZipShort(8)).getBytes());
         } else {
-            out.write((new ZipShort(10)).getBytes());
-            out.write(ZERO);
+            writeOut((new ZipShort(10)).getBytes());
+            writeOut(ZERO);
         }
         written += 4;
 
         // compression method
-        out.write((new ZipShort(ze.getMethod())).getBytes());
+        writeOut((new ZipShort(ze.getMethod())).getBytes());
         written += 2;
 
         // last mod. time and date
-        out.write(toDosTime(new Date(ze.getTime())).getBytes());
+        writeOut(toDosTime(new Date(ze.getTime())).getBytes());
         written += 4;
 
         // CRC
         // compressed length
         // uncompressed length
-        if (ze.getMethod() == DEFLATED) {
-            out.write(LZERO);
-            out.write(LZERO);
-            out.write(LZERO);
+       // defer until later if random access output is available
+
+       localDataStart = written;
+
+        if (ze.getMethod() == DEFLATED || random != null) {
+            writeOut(LZERO);
+            writeOut(LZERO);
+            writeOut(LZERO);
         } else {
-            out.write((new ZipLong(ze.getCrc())).getBytes());
-            out.write((new ZipLong(ze.getSize())).getBytes());
-            out.write((new ZipLong(ze.getSize())).getBytes());
+            writeOut((new ZipLong(ze.getCrc())).getBytes());
+            writeOut((new ZipLong(ze.getSize())).getBytes());
+            writeOut((new ZipLong(ze.getSize())).getBytes());
         }
         written += 12;
         
         // file name length
         byte[] name = getBytes(ze.getName());
-        out.write((new ZipShort(name.length)).getBytes());
+        writeOut((new ZipShort(name.length)).getBytes());
         written += 2;
         
         // extra field length
         byte[] extra = ze.getLocalFileDataExtra();
-        out.write((new ZipShort(extra.length)).getBytes());
+        writeOut((new ZipShort(extra.length)).getBytes());
         written += 2;
 
         // file name
-        out.write(name);
+        writeOut(name);
         written += name.length;
 
         // extra field
-        out.write(extra);
+        writeOut(extra);
         written += extra.length;
 
         dataStart = written;
@@ -499,13 +625,13 @@
      * @since 1.1
      */
     protected void writeDataDescriptor(ZipEntry ze) throws IOException {
-        if (ze.getMethod() != DEFLATED) {
+        if (ze.getMethod() != DEFLATED || random != null) {
             return;
         }
-        out.write(DD_SIG.getBytes());
-        out.write((new ZipLong(entry.getCrc())).getBytes());
-        out.write((new ZipLong(entry.getCompressedSize())).getBytes());
-        out.write((new ZipLong(entry.getSize())).getBytes());
+        writeOut(DD_SIG.getBytes());
+        writeOut((new ZipLong(entry.getCrc())).getBytes());
+        writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
+        writeOut((new ZipLong(entry.getSize())).getBytes());
         written += 16;
     }
 
@@ -515,52 +641,52 @@
      * @since 1.1
      */
     protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
-        out.write(CFH_SIG.getBytes());
+        writeOut(CFH_SIG.getBytes());
         written += 4;
 
         // version made by
-        out.write((new ZipShort((ze.getPlatform() << 8) | 20)).getBytes());
+        writeOut((new ZipShort((ze.getPlatform() << 8) | 20)).getBytes());
         written += 2;
 
         // version needed to extract
         // general purpose bit flag
-        if (ze.getMethod() == DEFLATED) {
+        if (ze.getMethod() == DEFLATED && random == null) {
             // requires version 2 as we are going to store length info
             // in the data descriptor
-            out.write((new ZipShort(20)).getBytes());
+            writeOut((new ZipShort(20)).getBytes());
 
             // bit3 set to signal, we use a data descriptor
-            out.write((new ZipShort(8)).getBytes());
+            writeOut((new ZipShort(8)).getBytes());
         } else {
-            out.write((new ZipShort(10)).getBytes());
-            out.write(ZERO);
+            writeOut((new ZipShort(10)).getBytes());
+            writeOut(ZERO);
         }
         written += 4;
 
         // compression method
-        out.write((new ZipShort(ze.getMethod())).getBytes());
+        writeOut((new ZipShort(ze.getMethod())).getBytes());
         written += 2;
 
         // last mod. time and date
-        out.write(toDosTime(new Date(ze.getTime())).getBytes());
+        writeOut(toDosTime(new Date(ze.getTime())).getBytes());
         written += 4;
 
         // CRC
         // compressed length
         // uncompressed length
-        out.write((new ZipLong(ze.getCrc())).getBytes());
-        out.write((new ZipLong(ze.getCompressedSize())).getBytes());
-        out.write((new ZipLong(ze.getSize())).getBytes());
+        writeOut((new ZipLong(ze.getCrc())).getBytes());
+        writeOut((new ZipLong(ze.getCompressedSize())).getBytes());
+        writeOut((new ZipLong(ze.getSize())).getBytes());
         written += 12;
         
         // file name length
         byte[] name = getBytes(ze.getName());
-        out.write((new ZipShort(name.length)).getBytes());
+        writeOut((new ZipShort(name.length)).getBytes());
         written += 2;
         
         // extra field length
         byte[] extra = ze.getCentralDirectoryExtra();
-        out.write((new ZipShort(extra.length)).getBytes());
+        writeOut((new ZipShort(extra.length)).getBytes());
         written += 2;
 
         // file comment length
@@ -569,35 +695,35 @@
             comm = "";
         }
         byte[] comment = getBytes(comm);
-        out.write((new ZipShort(comment.length)).getBytes());
+        writeOut((new ZipShort(comment.length)).getBytes());
         written += 2;
         
         // disk number start
-        out.write(ZERO);
+        writeOut(ZERO);
         written += 2;
 
         // internal file attributes
-        out.write((new ZipShort(ze.getInternalAttributes())).getBytes());
+        writeOut((new ZipShort(ze.getInternalAttributes())).getBytes());
         written += 2;
 
         // external file attributes
-        out.write((new ZipLong(ze.getExternalAttributes())).getBytes());
+        writeOut((new ZipLong(ze.getExternalAttributes())).getBytes());
         written += 4;
 
         // relative offset of LFH
-        out.write(((ZipLong) offsets.get(ze)).getBytes());
+        writeOut(((ZipLong) offsets.get(ze)).getBytes());
         written += 4;
 
         // file name
-        out.write(name);
+        writeOut(name);
         written += name.length;
 
         // extra field
-        out.write(extra);
+        writeOut(extra);
         written += extra.length;
 
         // file comment
-        out.write(comment);
+        writeOut(comment);
         written += comment.length;
     }
 
@@ -607,25 +733,25 @@
      * @since 1.1
      */
     protected void writeCentralDirectoryEnd() throws IOException {
-        out.write(EOCD_SIG.getBytes());
+        writeOut(EOCD_SIG.getBytes());
         
         // disk numbers
-        out.write(ZERO);
-        out.write(ZERO);
+        writeOut(ZERO);
+        writeOut(ZERO);
 
         // number of entries
         byte[] num = (new ZipShort(entries.size())).getBytes();
-        out.write(num);
-        out.write(num);
+        writeOut(num);
+        writeOut(num);
 
         // length and location of CD
-        out.write(cdLength.getBytes());
-        out.write(cdOffset.getBytes());
+        writeOut(cdLength.getBytes());
+        writeOut(cdOffset.getBytes());
 
         // ZIP file comment
         byte[] data = getBytes(comment);
-        out.write((new ZipShort(data.length)).getBytes());
-        out.write(data);
+        writeOut((new ZipShort(data.length)).getBytes());
+        writeOut(data);
     }
 
     /**
@@ -681,4 +807,19 @@
         }
     }
 
+    /**
+     * Write bytes to output or random access file
+     *
+     * @since 1.6
+     */
+     private void writeOut(byte [] data) throws IOException {
+         writeOut(data, 0, data.length);
+     }
+
+     private void writeOut(byte [] data, int offset, int length) throws 
IOException {
+         if (random == null)
+          out.write(data, offset, length);
+         else
+          random.write(data, offset, length);
+     }
 }
--- org/apache/tools/ant/taskdefs/Zip.java.orig Wed Jul 16 17:29:35 2003
+++ org/apache/tools/ant/taskdefs/Zip.java      Wed Jul 16 17:29:46 2003
@@ -419,7 +419,7 @@
             try {
 
                 if (! skipWriting) {
-                    zOut = new ZipOutputStream(new FileOutputStream(zipFile));
+                    zOut = new ZipOutputStream(zipFile);
 
                     zOut.setEncoding(encoding);
                     if (doCompress) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to