patches for DeflaterOutputStream.java and ZipOutputStream.java

Per Bothner per@bothner.com
Fri Mar 30 10:35:00 GMT 2001


It is clear that there has been zero testing of ZipOutputStream.java,
since it contains numerous errors in the zip file format emitted.
The patches fixes the problems I found.  It seems to work for at
least the case I've tested: a single uncompressed ("STORED") member.

When it comes to deflating with level 0 (no compression), I finally
got it so that the entire input file was written out, but pre-pended
with 5 extra bytes.  My guess this is some zlib header that is
emitted even when doing no compression, but the logic of zlib is
convoluted enough I'm not sure.  I finally concluded that when using
the STORED method we should by-pass zlib entirely - that is more
efficient anyway.

With the patch (plus the String patch I will check in soon), Kawa
builds *and* the entire Kawa testsuite runs correctly!

I'll give people a chance to comment before I check this in.

2001-03-30  Per Bothner  <per@bothner.com>

	* DeflaterOutputStream.java (deflate):  Loop while def.needsInput.
	(finish):  def.deflate needs to be called in a loop.
	(inbuf, inbufLength):  New private fields.
	(write(int)): Use inbuf.
	(write(byte[],int,int):  Check if pending output in inbuf.
	* ZipOutputStream.java:  Don't use Deflater if stored.
	Use a Checksum object directly, not via a CheckedOutputStream.
	(uncompressed_size):  New field,
	(closeEntry):  Only write data_directory if needed.
	(write):  If STORED, write directly.
	Always update crc, and uncompressed_size.
	(write_entry):  Fix lots of protocol erors.

Index: DeflaterOutputStream.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/util/zip/DeflaterOutputStream.java,v
retrieving revision 1.5
diff -u -r1.5 DeflaterOutputStream.java
--- DeflaterOutputStream.java	2000/11/17 21:42:28	1.5
+++ DeflaterOutputStream.java	2001/03/30 18:12:30
@@ -50,13 +50,13 @@
 
   protected void deflate () throws IOException
   {
-    while (true)
+    do
       {
 	int len = def.deflate(buf, 0, buf.length);
-	if (len == 0 || len == -1)
-	  break;
-	out.write(buf, 0, len);
-      }
+	if (len > 0)
+	  out.write(buf, 0, len);
+       }
+    while (! def.needsInput());
   }
 
   public DeflaterOutputStream (OutputStream out)
@@ -78,22 +78,52 @@
 
   public void finish () throws IOException
   {
+    if (inbufLength > 0)
+      {
+	def.setInput (inbuf, 0, inbufLength);
+	deflate ();
+	inbufLength = 0;
+      }
     def.finish();
-    deflate ();
+    while (! def.finished ())
+      {
+	int len = def.deflate(buf, 0, buf.length);
+	if (len > 0)
+	  out.write(buf, 0, len);
+      }
   }
 
   public void write (int bval) throws IOException
   {
-    byte[] b = new byte[1];
-    b[0] = (byte) bval;
-    write (b, 0, 1);
+    if (inbuf == null)
+      {
+	inbuf = new byte[128];
+      }
+    else if (inbufLength == inbuf.length)
+      {
+	def.setInput (inbuf, 0, inbufLength);
+	deflate ();
+	inbufLength = 0;
+      }
+    inbuf[inbufLength++] = (byte) bval;
   }
 
   public void write (byte[] buf, int off, int len) throws IOException
   {
+    if (inbufLength > 0)
+      {
+	def.setInput (inbuf, 0, inbufLength);
+	deflate ();
+	inbufLength = 0;
+      }
     def.setInput (buf, off, len);
     deflate ();
   }
+
+  // Used, if needed, for write(int).
+  private byte[] inbuf;
+  // Used length of inbuf.
+  private int inbufLength;
 
   // The retrieval buffer.
   protected byte[] buf;
Index: ZipOutputStream.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/util/zip/ZipOutputStream.java,v
retrieving revision 1.8
diff -u -r1.8 ZipOutputStream.java
--- ZipOutputStream.java	2000/11/17 21:42:28	1.8
+++ ZipOutputStream.java	2001/03/30 18:12:31
@@ -47,37 +47,64 @@
 
   public void closeEntry ()  throws IOException
   {
-    int uncompressed_size = def.getTotalIn();
-    int compressed_size = def.getTotalOut();
-    long crc = filter.getChecksum().getValue();
+    int compressed_size;
+    if (current.method == STORED)
+      {
+	compressed_size = uncompressed_size;
+      }
+    else
+      {
+	super.finish();
+	compressed_size = def.getTotalOut();
+      }
+    long crc = sum.getValue();
 
     bytes_written += compressed_size;
 
-    bytes_written += put4 (0x08074b50);
     if (current.getCrc() == -1 || current.getCompressedSize() == -1
 	|| current.getSize() == -1)
       {
 	current.setCrc(crc);
 	current.compressedSize = compressed_size;
 	current.setSize(uncompressed_size);
+	put4 (0x08074b50);
+	put4 ((int) (current.getCrc()));
+	put4 ((int) (current.getCompressedSize()));
+	put4 ((int) (current.getSize()));
+	bytes_written += 16;
       }
-    else
-      {
-	if (current.getCrc() != crc
-	    || current.getCompressedSize() != compressed_size
-	    || current.getSize() != uncompressed_size)
-	  throw new ZipException ("zip entry field incorrect");
-      }
-    bytes_written += put4 ((int) (current.getCrc()));
-    bytes_written += put4 ((int) (current.getCompressedSize()));
-    bytes_written += put4 ((int) (current.getSize()));
+    else if (current.getCrc() != crc
+	     || current.getCompressedSize() != compressed_size
+	     || current.getSize() != uncompressed_size)
+      throw new ZipException ("zip entry field incorrect");
 
     current.next = chain;
     chain = current;
     current = null;
-    filter = null;
   }
 
+  public void write (int bval) throws IOException
+  {
+    if (current.method == STORED)
+      {
+	out.write(bval);
+      }
+    else
+      super.write(bval);
+    sum.update(bval);
+    uncompressed_size += 1;
+  }
+
+  public void write (byte[] buf, int off, int len) throws IOException
+  {
+    if (current.method == STORED)
+      out.write(buf, off, len);
+    else
+      super.write(buf, off, len);
+    sum.update(buf, off, len);
+    uncompressed_size += len;
+  }
+
   public void finish () throws IOException
   {
     if (current != null)
@@ -101,21 +128,19 @@
     // Another disk number.
     put2 (0);
     put2 (count);
+    put2 (count);
     put4 (bytes);
     put4 ((int) offset);
 
     byte[] c = comment.getBytes("8859_1");
     put2 (c.length);
     out.write(c);
-    out.write((byte) 0);
   }
 
   // Helper for finish and putNextEntry.
   private int write_entry (ZipEntry entry, boolean is_local)
     throws IOException
   {
-    long offset = bytes_written;
-
     int bytes = put4 (is_local ? 0x04034b50 : 0x02014b50);
     if (! is_local)
       bytes += put_version ();
@@ -169,25 +194,22 @@
 	// Internal file attributes.
 	bytes += put2 (0);
 	// External file attributes.
-	bytes += put2 (0);
+	bytes += put4 (0);
 	// Relative offset of local header.
-	bytes += put2 ((int) offset);
+	bytes += put4 ((int) entry.relativeOffset);
       }
 
     out.write (name);
-    out.write ((byte) 0);
-    bytes += name.length + 1;
+    bytes += name.length;
     if (entry.extra != null)
       {
 	out.write(entry.extra);
-	out.write((byte) 0);
-	bytes += entry.extra.length + 1;
+	bytes += entry.extra.length;
       }
     if (comment != null)
       {
 	out.write(comment);
-	out.write((byte) 0);
-	bytes += comment.length + 1;
+	bytes += comment.length;
       }
 
     bytes_written += bytes;
@@ -208,13 +230,13 @@
 	// Just in case.
 	entry.compressedSize = entry.getSize();
       }
+    entry.relativeOffset = bytes_written;
     write_entry (entry, true);
     current = entry;
     int compr = (method == STORED) ? Deflater.NO_COMPRESSION : level;
     def.reset();
     def.setLevel(compr);
-    filter = new CheckedOutputStream (new DeflaterOutputStream (out, def),
-				      new CRC32 ());
+    sum.reset();
   }
 
   public void setLevel (int level)
@@ -240,18 +262,10 @@
     this.comment = comment;
   }
 
-  public synchronized void write (byte[] buf, int off, int len)
-    throws IOException
-  {
-    if (filter == null)
-      throw new ZipException ("no open zip entry");
-    filter.write(buf, off, len);
-  }
-
   public ZipOutputStream (OutputStream out)
   {
-    super (out);
-    def = new Deflater (level, true);
+    super (out, new Deflater (Deflater.DEFAULT_COMPRESSION, true), 8192);
+    sum = new CRC32 ();
   }
 
   private int put2 (int i) throws IOException
@@ -282,14 +296,14 @@
   private ZipEntry current;
   // The chain of entries which have been written to this file.
   private ZipEntry chain;
-  // The output stream to which data should be sent.
-  private CheckedOutputStream filter;
 
   private int method = DEFLATED;
   private int level = Deflater.DEFAULT_COMPRESSION;
   private String comment = "";
   private long bytes_written;
+
+  private int uncompressed_size;
 
-  // The Deflater we use.
-  private Deflater def;
+  /** The checksum object. */
+  private Checksum sum;
 }

-- 
	--Per Bothner
per@bothner.com   http://www.bothner.com/~per/



More information about the Java-patches mailing list