United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4401122 : java.util.zip.ZipFile.getInputStream(name).available() returns incorrect value

Details
Type:
Bug
Submit Date:
2001-01-02
Status:
Closed
Updated Date:
2004-04-28
Project Name:
JDK
Resolved Date:
2003-05-16
Component:
core-libs
OS:
solaris_2.6,generic,windows_xp,windows_2000
Sub-Component:
java.util.jar
CPU:
x86,sparc,generic
Priority:
P4
Resolution:
Fixed
Affected Versions:
1.2.0,1.3.0,1.4.0,1.4.1,1.4.2
Fixed Versions:
1.4.2_05 (05)

Related Reports
Backport:
Duplicate:
Duplicate:
Relates:

Sub Tasks

Description
Depending upon how it's constructed, the available() method of a zip-file input
stream either always returns 1 until EOF is reached, or returns the total
(compressed!) size of the entry being read, regardless of how much of the entry
has been read so far.  The first behavior is, in fact, enshrined in the
specification of java.util.zip.ZipInputStream:

    /**
     * Returns 0 after EOF has reached for the current entry data,
     * otherwise always return 1.
     * <p>
     * Programs should not count on this method to return the actual number
     * of bytes that could be read without blocking.
     * ...

The specification of the InputStream.available() method only talks about
blocking, and does not explicitly say what value should be returned with
respect to EOF on a stream that (essentially) will never block, but these
behaviors are clearly not in the spirit of that specification.

-- mr@eng 2001/1/2


Name: nt126004			Date: 07/26/2002


FULL PRODUCT VERSION :
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
No service packs.

ADDITIONAL OPERATING SYSTEMS :
This bug shouldn't depend on the operating system.


A DESCRIPTION OF THE PROBLEM :
I'm reading 1 entry from a JAR file.

InputSteam.available() returns the total number of bytes in
the uncompressed entry independent of how many bytes have
been read yet.

Thus available() exceeds the true number of available bytes,
while according to the specification it must be less or
equal to that quantity.

By tracing I came to the private inner class method
java.util.zip.ZipFile.ZipFileInputStream.available() where
I found "return size;" in the source which is the obvious
cause of the error since "size" is not changed after
initialization.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
  Bug is best obvious from the source code of
java.util.zip.ZipFile.ZipFileInputStream, though it can be
easliy reproduced:
1. Create a new directory
2. Create a file "data.txt" with
contents "01234567890123456789" (20 bytes) there
3. Run "jar cvf data.jar *" to create JAR
5. Place compiled class PossibleBug in the same directory
4. Run "java PossibleBug" (source code below)

EXPECTED VERSUS ACTUAL BEHAVIOR :
Expected output:
20
19
0
or (possibly) any numbers in the intervals:
1-20
1-19
0
Actual output:
20
20
20


This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.net.*;

public class PossibleBug{
	public static void main(String[] args) throws Exception{

	   URL u =  new URL("jar:file:data.jar!/data.txt");

	   InputStream in = u.openStream();
	   System.out.println(in.available());

	   in.read();
	   System.out.println(in.available());

	   for( int j=0; j<19; j++ ) in.read();
	   System.out.println(in.available());

	}
}

---------- END SOURCE ----------
(Review ID: 159602)
======================================================================

Name: nt126004			Date: 04/10/2003




A DESCRIPTION OF THE PROBLEM :
The anonymous class defined in ZipFile.java as an InflaterInputStream subclass incorrectly implements the available() method. It calls down to the ZipFileInputStream. It should, however, record the original size, then reduce its available count by however many bytes are inflated, and never return more than that.

i.e. the fix would be a method that looked something like this:

                public int available() throws IOException {
                    int avail_in = super.available();
                    if (avail_in < inflated_remaining) {
                      return avail_in;
                    } else {
                      return inflated_remaining;
                    }
                }

with inflated_remaining being the original size minus the number of bytes already inflated.


REPRODUCIBILITY :
This bug can be reproduced always.
(Review ID: 183766)
======================================================================

                                    

Comments
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
1.4.2_05
generic
tiger

FIXED IN:
1.4.2_05
tiger

INTEGRATED IN:
1.4.2_05
tiger
tiger-b07

VERIFIED IN:
1.4.2_05


                                     
2004-06-14
SUGGESTED FIX

---- src/share/classes/java/util/zip/ZipFile.java
*** /tmp/23124aaa	Tue Jan  2 09:01:52 2001
--- ZipFile.java	Mon Jan  1 15:37:58 2001
***************
*** 192,203 ****
  	if (jzentry == 0) {
  	    return null;
  	}
! 	InputStream in = new ZipFileInputStream(jzfile, jzentry, this);
  	switch (getMethod(jzentry)) {
  	case STORED:
! 	    return in;
  	case DEFLATED:
! 	    return new InflaterInputStream(in, getInflater()) {
                  private boolean isClosed = false;
                  
  		public void close() throws IOException {
--- 192,204 ----
  	if (jzentry == 0) {
  	    return null;
  	}
! 	final ZipFileInputStream zfin
! 	    = new ZipFileInputStream(jzfile, jzentry, this);
  	switch (getMethod(jzentry)) {
  	case STORED:
! 	    return zfin;
  	case DEFLATED:
! 	    return new InflaterInputStream(zfin, getInflater()) {
                  private boolean isClosed = false;
                  
  		public void close() throws IOException {
***************
*** 227,233 ****
  
                  public int available() throws IOException {
                      if (super.available() != 0) {
!                         return this.in.available();
                      } else {
                          return 0;
                      }
--- 228,234 ----
  
                  public int available() throws IOException {
                      if (super.available() != 0) {
!                         return zfin.size() - inf.getTotalOut();
                      } else {
                          return 0;
                      }
***************
*** 367,374 ****
  	}
      }
  	
!     /*
!      * Inner class implementing the input stream used to read a zip file entry.
       */
     private static class ZipFileInputStream extends InputStream {
  	private long jzfile;	// address of jzfile data
--- 368,375 ----
  	}
      }
  	
!     /* Inner class implementing the input stream used to read a
!      * (possibly compressed) zip file entry.
       */
     private static class ZipFileInputStream extends InputStream {
  	private long jzfile;	// address of jzfile data
***************
*** 383,389 ****
  	    this.jzentry = jzentry;
  	    pos = 0;
  	    rem = getCSize(jzentry);
!             size = getSize(jzentry);
  	    this.handle = zf;
  	}
  
--- 384,390 ----
  	    this.jzentry = jzentry;
  	    pos = 0;
  	    rem = getCSize(jzentry);
! 	    size = getSize(jzentry);
  	    this.handle = zf;
  	}
  
***************
*** 428,435 ****
  	}
  
  	public int available() {
! 	    return size;
  	}
  
  	private void cleanup() {
  	    rem = 0;
--- 429,440 ----
  	}
  
  	public int available() {
! 	    return rem;
  	}
+ 
+         public int size() {
+ 	    return size;
+         }
  
  	private void cleanup() {
  	    rem = 0;

---- src/share/classes/java/util/zip/ZipInputStream.java
*** /tmp/23133aaa	Tue Jan  2 09:01:52 2001
--- ZipInputStream.java	Mon Jan  1 15:49:34 2001
***************
*** 107,117 ****
       */
      public int available() throws IOException {
          ensureOpen();
!         if (entryEOF) {
!             return 0;
!         } else {
!             return 1;
!         }
      }
  
      /**
--- 107,116 ----
       */
      public int available() throws IOException {
          ensureOpen();
! 	if (entryEOF)
! 	    return 0;
! 	return (int)entry.getSize() - inf.getTotalOut();
! 	// ## Fix spec
      }
  
      /**

---- test/java/util/zip/Available.java
*** /tmp/23160aaa	Tue Jan  2 09:01:52 2001
--- Available.java	Mon Jan  1 15:38:27 2001
***************
*** 1,34 ****
  /* @test %I% %E%
!    @bug 4028605 4109069 4234207
!    @summary Make sure ZipInputStream/InflaterInputStream.available() will
!             return 0 after EOF has reached and 1 otherwise.
!    */
! 
  
  import java.io.*;
  import java.util.zip.*;
  
  public class Available {
!     static void test(InputStream in) throws Exception {
          byte[] buf = new byte[1024];
!         int n;
  
!         while ((n = in.read(buf)) != -1);
!         if (in.available() != 0) {
!             throw new Exception("available should return 0 after EOF");
!         }
      }
      
      public static void main(String[] args) throws Exception {
!         File f = new File(System.getProperty("test.src", "."), "input.jar");
  
          // test ZipInputStream
!         ZipInputStream z = new ZipInputStream(new FileInputStream(f));
!         z.getNextEntry();
!         test(z);
!         
          // test InflaterInputStream
!         ZipFile zfile = new ZipFile(f);
!         test(zfile.getInputStream(zfile.getEntry("Available.java")));
      }
  }
--- 1,68 ----
  /* @test %I% %E%
!    @bug 4028605 4109069 4234207 4400697
!    @summary Make sure ZipInputStream/InflaterInputStream.available()
!             returns zero just before, and after, EOF is reached
!  */
  
  import java.io.*;
  import java.util.zip.*;
  
+ 
  public class Available {
! 
!     private static interface InputStreamMaker {
! 	public InputStream make() throws IOException;
!     }
! 
!     static void test(InputStreamMaker ism) throws Exception {
          byte[] buf = new byte[1024];
!         int n, t = 0;
! 
! 	// Test behavior after EOF, and note length of entry
! 	InputStream in = ism.make();
! 	System.err.println(in + ": " + in.available());
!         while ((n = in.read(buf)) != -1)
! 	    t += n;
!         if ((n = in.available()) != 0)
!             throw new Exception("available() returned " + n + " after EOF");
  
! 	// Ensure that available() never exceeds the number of bytes remaining
! 	in = ism.make();
! 	int r = t;
! 	for (;;) {
! 	    if ((n = in.available()) > r)
! 		throw new Exception("available() returned " + n
! 				    + " with only " + r + " bytes remaining");
! 	    if ((n = in.read(buf, 0, 1)) == -1)
! 		break;
! 	    r -= n;
! 	}
! 	if (r != 0)
! 	    throw new Exception("Did not read entire entry");
      }
      
      public static void main(String[] args) throws Exception {
!         final File f;
! 	if (args.length > 0)
! 	    f = new File(args[0]);
! 	else
! 	    f = new File(System.getProperty("test.src", "."), "input.jar");
  
          // test ZipInputStream
! 	test(new InputStreamMaker() {
! 		public InputStream make() throws IOException {
! 		    ZipInputStream z
! 			= new ZipInputStream(new FileInputStream(f));
! 		    z.getNextEntry();
! 		    return z;
! 		}});
! 
          // test InflaterInputStream
! 	test(new InputStreamMaker() {
! 		public InputStream make() throws IOException {
! 		    ZipFile zf = new ZipFile(f);
! 		    return zf.getInputStream(zf.getEntry("Available.java"));
! 		}});
      }
+ 
  }
                                     
2004-06-11
EVALUATION

We will attempt to change the spec and make available return a reasonable value for the zip input streams in the next feature release.
###@###.### 2002-12-04

The spec change was rejected for compatibility reasons, but the bug where the available method returned the total size of the entry no matter how much of it had already been read has been fixed.
###@###.### 2003-04-21
                                     
2003-04-21



Hardware and Software, Engineered to Work Together