Commit 5f51b048 by Tom Tromey Committed by Tom Tromey

InflaterInputStream.java (read): Loop if data has been read but none output by inflater.

	* java/util/zip/InflaterInputStream.java (read): Loop if data has
	been read but none output by inflater.
	* java/util/zip/natDeflater.cc (reset): Set is_finished.
	* java/util/zip/natInflater.cc (reset): Set dist_needed and
	is_finished.
	* java/util/zip/ZipOutputStream.java: Replaced with Classpath
	version.
	* java/util/zip/ZipFile.java: Replaced with Classpath version.
	* java/util/zip/ZipEntry.java: Replaced with Classpath version.
	* java/util/zip/ZipInputStream.java: Replaced with Classpath
	version.
	* java/util/zip/ZipConstants.java: Replaced with Classpath version.

From-SVN: r54653
parent 21505616
2002-06-15 Tom Tromey <tromey@redhat.com>
* java/util/zip/InflaterInputStream.java (read): Loop if data has
been read but none output by inflater.
* java/util/zip/natDeflater.cc (reset): Set is_finished.
* java/util/zip/natInflater.cc (reset): Set dist_needed and
is_finished.
* java/util/zip/ZipOutputStream.java: Replaced with Classpath
version.
* java/util/zip/ZipFile.java: Replaced with Classpath version.
* java/util/zip/ZipEntry.java: Replaced with Classpath version.
* java/util/zip/ZipInputStream.java: Replaced with Classpath
version.
* java/util/zip/ZipConstants.java: Replaced with Classpath version.
2002-06-13 Tom Tromey <tromey@redhat.com> 2002-06-13 Tom Tromey <tromey@redhat.com>
* java/lang/natString.cc (init): Handle case where DONT_COPY is * java/lang/natString.cc (init): Handle case where DONT_COPY is
......
/* InflaterInputStream.java - Input stream filter for decompressing /* InflaterInputStream.java - Input stream filter for decompressing
Copyright (C) 1999, 2000 Free Software Foundation, Inc. Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath. This file is part of GNU Classpath.
...@@ -92,16 +92,22 @@ public class InflaterInputStream extends FilterInputStream ...@@ -92,16 +92,22 @@ public class InflaterInputStream extends FilterInputStream
throw new IOException ("stream closed"); throw new IOException ("stream closed");
if (inf.finished()) if (inf.finished())
return -1; return -1;
int count = 0;
while (count == 0)
{
if (inf.needsInput()) if (inf.needsInput())
fill (); fill ();
int count;
try try
{ {
count = inf.inflate(buf, off, len); count = inf.inflate(buf, off, len);
if (count == 0) if (count == 0)
{ {
if (this.len == -1) if (this.len == -1)
return -1; // Couldn't get any more data to feed to the Inflater {
// Couldn't get any more data to feed to the Inflater
return -1;
}
if (inf.needsDictionary()) if (inf.needsDictionary())
throw new ZipException ("Inflater needs Dictionary"); throw new ZipException ("Inflater needs Dictionary");
} }
...@@ -110,6 +116,7 @@ public class InflaterInputStream extends FilterInputStream ...@@ -110,6 +116,7 @@ public class InflaterInputStream extends FilterInputStream
{ {
throw new ZipException (dfe.getMessage()); throw new ZipException (dfe.getMessage());
} }
}
return count; return count;
} }
......
/* ZipConstants.java - Some constants used in the zip package /* java.util.zip.ZipConstants
Copyright (C) 1999, 2000 Free Software Foundation, Inc. Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath. This file is part of GNU Classpath.
...@@ -37,19 +37,61 @@ exception statement from your version. */ ...@@ -37,19 +37,61 @@ exception statement from your version. */
package java.util.zip; package java.util.zip;
/**
* Some constants used in the zip package.
* <p>
* Since this package local interface is completely undocumented no effort
* is made to make it compatible with other implementations.
* If someone is really interested you can probably come up with the right
* constants and documentation by studying the Info-ZIP zipfile.c constants.
*/
interface ZipConstants interface ZipConstants
{ {
// Size in bytes of local file header, including signature. /* The local file header */
public static final int LOCAL_FILE_HEADER_SIZE = 30; public final static int LOCHDR = 30;
public final static int LOCSIG = 'P'|('K'<<8)|(3<<16)|(4<<24);
// Size in bytes of the "end of central directory" record, with signature. public final static int LOCVER = 4;
public static final int END_CENTRAL_DIR_SIZE = 22; public final static int LOCFLG = 6;
public final static int LOCHOW = 8;
public final static int LOCTIM = 10;
public final static int LOCCRC = 14;
public final static int LOCSIZ = 18;
public final static int LOCLEN = 22;
public final static int LOCNAM = 26;
public final static int LOCEXT = 28;
/* The Data descriptor */
public final static int EXTSIG = 'P'|('K'<<8)|(7<<16)|(8<<24);
public final static int EXTHDR = 16;
public final static int EXTCRC = 4;
public final static int EXTSIZ = 8;
public final static int EXTLEN = 12;
/* The central directory file header */
public final static int CENSIG = 'P'|('K'<<8)|(1<<16)|(2<<24);
public final static int CENHDR = 46;
public final static int CENVEM = 4;
public final static int CENVER = 6;
public final static int CENFLG = 8;
public final static int CENHOW = 10;
public final static int CENTIM = 12;
public final static int CENCRC = 16;
public final static int CENSIZ = 20;
public final static int CENLEN = 24;
public final static int CENNAM = 28;
public final static int CENEXT = 30;
public final static int CENCOM = 32;
public final static int CENDSK = 34;
public final static int CENATT = 36;
public final static int CENATX = 38;
public final static int CENOFF = 42;
/* The entries in the end of central directory */
public final static int ENDSIG = 'P'|('K'<<8)|(5<<16)|(6<<24);
public final static int ENDHDR = 22;
/* The following two fields are missing in SUN JDK */
final static int ENDNRD = 4;
final static int ENDDCD = 6;
public final static int ENDSUB = 8;
public final static int ENDTOT = 10;
public final static int ENDSIZ = 12;
public final static int ENDOFF = 16;
public final static int ENDCOM = 20;
} }
/* ZipEntry.java - Represents entries in a zip file archive /* java.util.zip.ZipEntry
Copyright (C) 1999, 2000 Free Software Foundation, Inc. Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath. This file is part of GNU Classpath.
...@@ -36,201 +36,361 @@ obligated to do so. If you do not wish to do so, delete this ...@@ -36,201 +36,361 @@ obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */ exception statement from your version. */
package java.util.zip; package java.util.zip;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Date;
/** /**
* @author Per Bothner * This class represents a member of a zip archive. ZipFile and
* @date January 6, 1999. * ZipInputStream will give you instances of this class as information
* about the members in an archive. On the other hand ZipOutputStream
* needs an instance of this class to create a new member.
*
* @author Jochen Hoenicke
*/ */
public class ZipEntry implements ZipConstants, Cloneable
{
private static int KNOWN_SIZE = 1;
private static int KNOWN_CSIZE = 2;
private static int KNOWN_CRC = 4;
private static int KNOWN_TIME = 8;
private static Calendar cal = Calendar.getInstance();
private String name;
private int size;
private int compressedSize;
private int crc;
private int time;
private short known = 0;
private short method = -1;
private byte[] extra = null;
private String comment = null;
int zipFileIndex = -1; /* used by ZipFile */
int flags; /* used by ZipOutputStream */
int offset; /* used by ZipFile and ZipOutputStream */
/* /**
* Written using on-line Java Platform 1.2 API Specification, as well * Compression method. This method doesn't compress at all.
* as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998). */
* Status: Believed complete and correct. public final static int STORED = 0;
/**
* Compression method. This method uses the Deflater.
*/ */
public final static int DEFLATED = 8;
/** /**
* Represents entries in a zip file archive. * Creates a zip entry with the given name.
* An Entry cn be created by giving a name or by giving an already existing * @param name the name. May include directory components separated
* ZipEntries whose values should be copied. The name normally represents a * by '/'.
* file path name or directory name.
*/ */
public class ZipEntry implements ZipConstants, Cloneable public ZipEntry(String name)
{ {
// These values were determined using a simple test program. if (name == null)
public static final int STORED = 0; throw new NullPointerException();
public static final int DEFLATED = 8;
String comment;
long compressedSize = -1;
long crc = -1;
byte[] extra;
int method = -1;
String name;
long size = -1;
long time = -1;
long relativeOffset = -1;
ZipEntry next;
public ZipEntry (String name)
{
if (name.length() > 65535)
throw new IllegalArgumentException ();
this.name = name; this.name = name;
} }
/** /**
* Creates a new ZipEntry using the fields of a given ZipEntry. * Creates a copy of the given zip entry.
* The comment, compressedSize, crc, extra, method, name, size, time and * @param e the entry to copy.
* relativeOffset fields are copied from the given entry.
* Note that the contents of the extra byte array field is not cloned,
* only the reference is copied.
* The clone() method does clone the contents of the extra byte array if
* needed.
* @since 1.2
*/ */
public ZipEntry (ZipEntry ent) public ZipEntry(ZipEntry e)
{
name = e.name;
known = e.known;
size = e.size;
compressedSize = e.compressedSize;
crc = e.crc;
time = e.time;
method = e.method;
extra = e.extra;
comment = e.comment;
}
void setDOSTime(int dostime)
{
int sec = 2 * (dostime & 0x1f);
int min = (dostime >> 5) & 0x3f;
int hrs = (dostime >> 11) & 0x1f;
int day = (dostime >> 16) & 0x1f;
int mon = ((dostime >> 21) & 0xf) - 1;
int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */
// Guard against invalid or missing date causing
// IndexOutOfBoundsException.
try
{
synchronized (cal)
{
cal.set(year, mon, day, hrs, min, sec);
time = (int) (cal.getTime().getTime() / 1000L);
}
known |= KNOWN_TIME;
}
catch (RuntimeException ex)
{ {
comment = ent.comment; /* Ignore illegal time stamp */
compressedSize = ent.compressedSize; known &= ~KNOWN_TIME;
crc = ent.crc; }
extra = ent.extra; }
method = ent.method;
name = ent.name; int getDOSTime()
size = ent.size; {
time = ent.time; if ((known & KNOWN_TIME) == 0)
relativeOffset = ent.relativeOffset; return 0;
synchronized (cal)
{
cal.setTime(new Date(time*1000L));
return (cal.get(cal.YEAR) - 1980 & 0x7f) << 25
| (cal.get(cal.MONTH) + 1) << 21
| (cal.get(cal.DAY_OF_MONTH)) << 16
| (cal.get(cal.HOUR_OF_DAY)) << 11
| (cal.get(cal.MINUTE)) << 5
| (cal.get(cal.SECOND)) >> 1;
}
} }
/** /**
* Creates a clone of this ZipEntry. Calls <code>new ZipEntry (this)</code> * Creates a copy of this zip entry.
* and creates a clone of the contents of the extra byte array field. */
* /**
* @since 1.2 * Clones the entry.
*/ */
public Object clone () public Object clone()
{ {
// JCL defines this as being the same as the copy constructor above, try
// except that value of the "extra" field is also copied. {
ZipEntry clone = new ZipEntry (this); // The JCL says that the `extra' field is also copied.
clone.extra = (byte[]) extra.clone (); ZipEntry clone = (ZipEntry) super.clone();
if (extra != null)
clone.extra = (byte[]) extra.clone();
return clone; return clone;
} }
catch (CloneNotSupportedException ex)
{
throw new InternalError();
}
}
public String getComment () { return comment; } /**
* Returns the entry name. The path components in the entry are
public long getCompressedSize () { return compressedSize; } * always separated by slashes ('/').
*/
public long getCrc () { return crc; } public String getName()
{
public byte[] getExtra() { return extra; } return name;
}
public int getMethod () { return method; }
public String getName () { return name; }
public long getSize () { return size; }
public long getTime () { return time; } /**
* Sets the time of last modification of the entry.
* @time the time of last modification of the entry.
*/
public void setTime(long time)
{
this.time = (int) (time / 1000L);
this.known |= KNOWN_TIME;
}
public boolean isDirectory () /**
* Gets the time of last modification of the entry.
* @return the time of last modification of the entry, or -1 if unknown.
*/
public long getTime()
{ {
if (name != null) return (known & KNOWN_TIME) != 0 ? time * 1000L : -1;
}
/**
* Sets the size of the uncompressed data.
* @exception IllegalArgumentException if size is not in 0..0xffffffffL
*/
public void setSize(long size)
{ {
int nlen = name.length(); if ((size & 0xffffffff00000000L) != 0)
if (nlen > 0 && name.charAt(nlen-1) == '/') throw new IllegalArgumentException();
return true; this.size = (int) size;
this.known |= KNOWN_SIZE;
} }
return false;
/**
* Gets the size of the uncompressed data.
* @return the size or -1 if unknown.
*/
public long getSize()
{
return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L;
} }
public void setComment (String comment) /**
* Sets the size of the compressed data.
* @exception IllegalArgumentException if size is not in 0..0xffffffffL
*/
public void setCompressedSize(long csize)
{ {
if (comment != null && comment.length() > 65535) if ((csize & 0xffffffff00000000L) != 0)
throw new IllegalArgumentException (); throw new IllegalArgumentException();
this.comment = comment; this.compressedSize = (int) csize;
this.known |= KNOWN_CSIZE;
} }
/** /**
* Sets the compressedSize of this ZipEntry. * Gets the size of the compressed data.
* The new size must be between 0 and 0xffffffffL. * @return the size or -1 if unknown.
* @since 1.2
*/ */
public void setCompressedSize (long compressedSize) public long getCompressedSize()
{ {
if (compressedSize < 0 || compressedSize > 0xffffffffL) return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L;
throw new IllegalArgumentException ();
this.compressedSize = compressedSize;
} }
public void setCrc (long crc) /**
* Sets the crc of the uncompressed data.
* @exception IllegalArgumentException if crc is not in 0..0xffffffffL
*/
public void setCrc(long crc)
{ {
if (crc < 0 || crc > 0xffffffffL) if ((crc & 0xffffffff00000000L) != 0)
throw new IllegalArgumentException (); throw new IllegalArgumentException();
this.crc = crc; this.crc = (int) crc;
this.known |= KNOWN_CRC;
} }
public void setExtra (byte[] extra) /**
* Gets the crc of the uncompressed data.
* @return the crc or -1 if unknown.
*/
public long getCrc()
{ {
if (extra != null && extra.length > 65535) return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;
throw new IllegalArgumentException ();
this.extra = extra;
} }
public void setMethod (int method) /**
* Sets the compression method. Only DEFLATED and STORED are
* supported.
* @exception IllegalArgumentException if method is not supported.
* @see ZipOutputStream#DEFLATED
* @see ZipOutputStream#STORED
*/
public void setMethod(int method)
{ {
if (method != DEFLATED && method != STORED) if (method != ZipOutputStream.STORED
throw new IllegalArgumentException (); && method != ZipOutputStream.DEFLATED)
this.method = method; throw new IllegalArgumentException();
this.method = (short) method;
} }
public void setSize (long size) /**
* Gets the compression method.
* @return the compression method or -1 if unknown.
*/
public int getMethod()
{ {
if (size < 0 || size > 0xffffffffL) return method;
throw new IllegalArgumentException ();
this.size = size;
} }
public void setTime (long time) /**
* Sets the extra data.
* @exception IllegalArgumentException if extra is longer than 0xffff bytes.
*/
public void setExtra(byte[] extra)
{
if (extra == null)
{ {
this.time = time; this.extra = null;
return;
} }
private final static short[] daysToMonthStart = { if (extra.length > 0xffff)
//Jan Feb Mar Apr May Jun Jul throw new IllegalArgumentException();
0, 31, 31+28, 2*31+28, 2*31+28+30, 3*31+28+30, 3*31+28+2*30, this.extra = extra;
// Aug Sep Oct Nov Dec try
4*31+28+2*30, 5*31+28+2*30, 5*31+28+3*30, 6*31+28+3*30, 6*31+28+4*30}; {
int pos = 0;
while (pos < extra.length)
{
int sig = (extra[pos++] & 0xff)
| (extra[pos++] & 0xff) << 8;
int len = (extra[pos++] & 0xff)
| (extra[pos++] & 0xff) << 8;
if (sig == 0x5455)
{
/* extended time stamp */
int flags = extra[pos];
if ((flags & 1) != 0)
{
time = ((extra[pos+1] & 0xff)
| (extra[pos+2] & 0xff) << 8
| (extra[pos+3] & 0xff) << 16
| (extra[pos+4] & 0xff) << 24);
known |= KNOWN_TIME;
}
}
pos += len;
}
}
catch (ArrayIndexOutOfBoundsException ex)
{
/* be lenient */
return;
}
}
/** Convert a DOS-style type value to milliseconds since 1970. */ /**
static long timeFromDOS (int date, int time) * Gets the extra data.
* @return the extra data or null if not set.
*/
public byte[] getExtra()
{ {
int sec = 2 * (time & 0x1f); return extra;
int min = (time >> 5) & 0x3f; }
int hrs = (time >> 11) & 0x1f;
int day = date & 0x1f;
int mon = ((date >> 5) & 0xf) - 1;
int year = ((date >> 9) & 0x7f) + 10; /* Since 1970. */
// Guard against invalid or missing date causing IndexOutOfBoundsException. /**
if (mon < 0 || mon > 11) * Sets the entry comment.
return -1; * @exception IllegalArgumentException if comment is longer than 0xffff.
*/
public void setComment(String comment)
{
if (comment.length() > 0xffff)
throw new IllegalArgumentException();
this.comment = comment;
}
long mtime = (((hrs * 60) + min) * 60 + sec) * 1000; /**
* Gets the comment.
* @return the comment or null if not set.
*/
public String getComment()
{
return comment;
}
// Leap year calculations are rather trivial in this case ... /**
int days = 365 * year + ((year+1)>>2); * Gets true, if the entry is a directory. This is solely
days += daysToMonthStart[mon]; * determined by the name, a trailing slash '/' marks a directory.
if ((year & 3) == 0 && mon > 1) */
days++; public boolean isDirectory()
days += day; {
return (days * 24*60*60L + ((hrs * 60) + min) * 60 + sec) * 1000L; int nlen = name.length();
return nlen > 0 && name.charAt(nlen - 1) == '/';
} }
public String toString () { return name; } /**
* Gets the string representation of this ZipEntry. This is just
* the name as returned by getName().
*/
public String toString()
{
return name;
}
/** /**
* Returns the hashcode of the name of this ZipEntry. * Gets the hashCode of this ZipEntry. This is just the hashCode
* of the name. Note that the equals method isn't changed, though.
*/ */
public int hashCode () { return name.hashCode (); } public int hashCode()
{
return name.hashCode();
}
} }
/* ZipFile.java - Read contents of a ZIP file /* java.util.zip.ZipFile
Copyright (C) 1999, 2000 Free Software Foundation, Inc. Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath. This file is part of GNU Classpath.
...@@ -36,233 +36,422 @@ obligated to do so. If you do not wish to do so, delete this ...@@ -36,233 +36,422 @@ obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */ exception statement from your version. */
package java.util.zip; package java.util.zip;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.EOFException;
import java.io.RandomAccessFile;
import java.util.Enumeration;
import java.util.NoSuchElementException;
/**
* This class represents a Zip archive. You can ask for the contained
* entries, or get an input stream for a file entry. The entry is
* automatically decompressed.
*
* This class is thread safe: You can open input streams for arbitrary
* entries in different threads.
*
* @author Jochen Hoenicke
*/
public class ZipFile implements ZipConstants
{
/** Mode flag to open a zip file for reading
*
*/
import java.io.*; public static final int OPEN_READ = 0x1;
/* Written using on-line Java Platform 1.2 API Specification /** Mode flag to delete a zip file after reading
* and JCL book. *
* Believed complete and correct.
*/ */
public class ZipFile implements ZipConstants public static final int OPEN_DELETE = 0x4;
{
public static final int OPEN_READ = 1;
public static final int OPEN_DELETE = 4;
public ZipFile (String fname) throws IOException private String name;
RandomAccessFile raf;
ZipEntry[] entries;
/**
* Opens a Zip file with the given name for reading.
* @exception IOException if a i/o error occured.
* @exception ZipException if the file doesn't contain a valid zip
* archive.
*/
public ZipFile(String name) throws ZipException, IOException
{ {
this(new File(fname)); this.raf = new RandomAccessFile(name, "r");
this.name = name;
readEntries();
} }
public ZipFile (File f) throws IOException /**
* Opens a Zip file reading the given File.
* @exception IOException if a i/o error occured.
* @exception ZipException if the file doesn't contain a valid zip
* archive.
*/
public ZipFile(File file) throws ZipException, IOException
{ {
this(f, OPEN_READ); this.raf = new RandomAccessFile(file, "r");
this.name = file.getName();
readEntries();
} }
public ZipFile (File f, int mode) throws IOException /**
* Opens a Zip file reading the given File in the given mode.
*
* If the OPEN_DELETE mode is specified, the zip file will be deleted at some time moment
* after it is opened. It will be deleted before the zip file is closed or the Virtual Machine
* exits.
*
* The contents of the zip file will be accessible until it is closed.
*
* The OPEN_DELETE mode is currently unimplemented in this library
*
* @since JDK1.3
* @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
*
* @exception IOException if a i/o error occured.
* @exception ZipException if the file doesn't contain a valid zip
* archive.
*/
public ZipFile(File file, int mode) throws ZipException, IOException
{ {
if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
throw new IllegalArgumentException
("mode can only be OPEN_READ or OPEN_READ | OPEN_DELETE");
if ((mode & OPEN_DELETE) != 0) if ((mode & OPEN_DELETE) != 0)
{ {
delete_on_close = f; throw new IllegalArgumentException("OPEN_DELETE mode not supported yet in java.util.zip.ZipFile");
f.deleteOnExit(); }
} this.raf = new RandomAccessFile(file, "r");
else this.name = file.getName();
{ readEntries();
delete_on_close = null;
}
file = new RandomAccessFile(f, "r");
name = f.getName();
readDirectory ();
}
void readDirectory () throws IOException
{
long size = file.length ();
if (size < ZipConstants.END_CENTRAL_DIR_SIZE)
throw new ZipException ("zipfile too short");
// We do not handle a "zipfile comment", which the appnote says can
// be at the end of a .zip file. We could handle this by seeking
// to the beginning and reading forwards.
file.seek(size - ZipConstants.END_CENTRAL_DIR_SIZE);
if (file.read() != 'P'
|| file.read() != 'K'
|| file.read() != '\005'
|| file.read() != '\006')
throw new ZipException("not a valid zipfile");
file.skipBytes(6);
numEntries = readu2();
int dir_size = read4 (); // Read "size of the central directory".
file.seek(size - (dir_size + ZipConstants.END_CENTRAL_DIR_SIZE));
ZipEntry last = null;
for (int i = 0; i < numEntries; i++)
{
file.skipBytes(10);
int method = readu2();
int modtime = readu2();
int moddate = readu2();
int crc = read4();
int compressedSize = read4();
int uncompressedSize = read4();
int filenameLength = readu2();
int extraLength = readu2();
int commentLength = readu2();
int diskNumberStart = readu2();
int intAttributes = readu2();
int extAttributes = read4();
int relativeOffset = read4();
byte[] bname = new byte[filenameLength];
file.readFully(bname);
ZipEntry entry = new ZipEntry(new String(bname, "8859_1"));
if (extraLength > 0)
{
byte[] bextra = new byte[extraLength];
file.readFully(bextra);
entry.extra = bextra;
}
if (commentLength > 0)
{
byte[] bcomment = new byte[commentLength];
file.readFully(bcomment);
entry.comment = new String(bcomment, "8859_1");
}
entry.compressedSize = compressedSize;
entry.size = uncompressedSize;
entry.crc = (long) crc & 0xffffffffL;
entry.method = method;
entry.relativeOffset = relativeOffset;
entry.time = ZipEntry.timeFromDOS(moddate, modtime);
if (last == null)
entries = entry;
else
last.next = entry;
last = entry;
}
}
public java.util.Enumeration entries()
{
return new ZipEnumeration(this);
} }
/**
* Read an unsigned short in little endian byte order.
* @exception IOException if a i/o error occured.
* @exception EOFException if the file ends prematurely
*/
private final int readLeShort() throws IOException {
return raf.readUnsignedByte() | raf.readUnsignedByte() << 8;
}
/**
* Read an int in little endian byte order.
* @exception IOException if a i/o error occured.
* @exception EOFException if the file ends prematurely
*/
private final int readLeInt() throws IOException {
return readLeShort() | readLeShort() << 16;
}
/**
* Read the central directory of a zip file and fill the entries
* array. This is called exactly once by the constructors.
* @exception IOException if a i/o error occured.
* @exception ZipException if the central directory is malformed
*/
private void readEntries() throws ZipException, IOException
{
/* Search for the End Of Central Directory. When a zip comment is
* present the directory may start earlier.
* FIXME: This searches the whole file in a very slow manner if the
* file isn't a zip file.
*/
long pos = raf.length() - ENDHDR;
do
{
if (pos < 0)
throw new ZipException
("central directory not found, probably not a zip file");
raf.seek(pos--);
}
while (readLeInt() != ENDSIG);
if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
throw new EOFException();
int count = readLeShort();
if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
throw new EOFException();
int centralOffset = readLeInt();
entries = new ZipEntry[count];
raf.seek(centralOffset);
for (int i = 0; i < count; i++)
{
if (readLeInt() != CENSIG)
throw new ZipException("Wrong Central Directory signature");
if (raf.skipBytes(CENHOW - CENVEM) != CENHOW - CENVEM)
throw new EOFException();
int method = readLeShort();
int dostime = readLeInt();
int crc = readLeInt();
int csize = readLeInt();
int size = readLeInt();
int nameLen = readLeShort();
int extraLen = readLeShort();
int commentLen = readLeShort();
if (raf.skipBytes(CENOFF - CENDSK) != CENOFF - CENDSK)
throw new EOFException();
int offset = readLeInt();
byte[] buffer = new byte[Math.max(nameLen, commentLen)];
raf.readFully(buffer, 0, nameLen);
String name = new String(buffer, 0, nameLen);
ZipEntry entry = new ZipEntry(name);
entry.setMethod(method);
entry.setCrc(crc & 0xffffffffL);
entry.setSize(size & 0xffffffffL);
entry.setCompressedSize(csize & 0xffffffffL);
entry.setDOSTime(dostime);
if (extraLen > 0)
{
byte[] extra = new byte[extraLen];
raf.readFully(extra);
entry.setExtra(extra);
}
if (commentLen > 0)
{
raf.readFully(buffer, 0, commentLen);
entry.setComment(new String(buffer, 0, commentLen));
}
entry.zipFileIndex = i;
entry.offset = offset;
entries[i] = entry;
}
}
/**
* Closes the ZipFile. This also closes all input streams given by
* this class. After this is called, no further method should be
* called.
* @exception IOException if a i/o error occured.
*/
public void close() throws IOException public void close() throws IOException
{ {
file.close();
entries = null; entries = null;
numEntries = 0; synchronized (raf)
if (delete_on_close != null) {
delete_on_close.delete(); raf.close();
}
} }
public ZipEntry getEntry(String name) /**
* Returns an enumeration of all Zip entries in this Zip file.
*/
public Enumeration entries()
{ {
for (ZipEntry entry = entries; entry != null; entry = entry.next) if (entries == null)
throw new IllegalStateException("ZipFile has closed");
return new ZipEntryEnumeration(entries);
}
private int getEntryIndex(String name)
{ {
if (name.equals(entry.getName())) for (int i = 0; i < entries.length; i++)
return entry; if (name.equals(entries[i].getName()))
return i;
return -1;
} }
return null;
/**
* Searches for a zip entry in this archive with the given name.
* @param the name. May contain directory components separated by
* slashes ('/').
* @return the zip entry, or null if no entry with that name exists.
* @see #entries */
public ZipEntry getEntry(String name)
{
if (entries == null)
throw new IllegalStateException("ZipFile has closed");
int index = getEntryIndex(name);
return index >= 0 ? (ZipEntry) entries[index].clone() : null;
} }
public InputStream getInputStream(ZipEntry ze) throws IOException /**
* Checks, if the local header of the entry at index i matches the
* central directory, and returns the offset to the data.
* @return the start offset of the (compressed) data.
* @exception IOException if a i/o error occured.
* @exception ZipException if the local header doesn't match the
* central directory header
*/
private long checkLocalHeader(ZipEntry entry) throws IOException
{ {
byte[] buffer = new byte[(int) ze.getCompressedSize()]; synchronized (raf)
{
raf.seek(entry.offset);
if (readLeInt() != LOCSIG)
throw new ZipException("Wrong Local header signature");
/* skip version and flags */
if (raf.skipBytes(LOCHOW - LOCVER) != LOCHOW - LOCVER)
throw new EOFException();
/* Read the size of the extra field, and skip to the start of the if (entry.getMethod() != readLeShort())
data. */ throw new ZipException("Compression method mismatch");
file.seek (ze.relativeOffset + ZipConstants.LOCAL_FILE_HEADER_SIZE - 2);
int extraFieldLength = readu2();
file.skipBytes (ze.getName().length() + extraFieldLength);
file.readFully(buffer); /* Skip time, crc, size and csize */
if (raf.skipBytes(LOCNAM - LOCTIM) != LOCNAM - LOCTIM)
throw new EOFException();
InputStream is = new ByteArrayInputStream (buffer); if (entry.getName().length() != readLeShort())
if (ze.getMethod() == ZipEntry.DEFLATED) throw new ZipException("file name length mismatch");
// Data in zipfile entries does not have a zlib header, so construct
// an Inflater with the `nowrapper' option. int extraLen = entry.getName().length() + readLeShort();
is = new InflaterInputStream (is, new Inflater (true), 512); return entry.offset + LOCHDR + extraLen;
}
}
/**
* Creates an input stream reading the given zip entry as
* uncompressed data. Normally zip entry should be an entry
* returned by getEntry() or entries().
* @return the input stream.
* @exception IOException if a i/o error occured.
* @exception ZipException if the Zip archive is malformed.
*/
public InputStream getInputStream(ZipEntry entry) throws IOException
{
if (entries == null)
throw new IllegalStateException("ZipFile has closed");
int index = entry.zipFileIndex;
if (index < 0 || index >= entries.length
|| entries[index].getName() != entry.getName())
{
index = getEntryIndex(entry.getName());
if (index < 0)
throw new NoSuchElementException();
}
long start = checkLocalHeader(entries[index]);
int method = entries[index].getMethod();
InputStream is = new PartialInputStream
(raf, start, entries[index].getCompressedSize());
switch (method)
{
case ZipOutputStream.STORED:
return is; return is;
case ZipOutputStream.DEFLATED:
return new InflaterInputStream(is, new Inflater(true));
default:
throw new ZipException("Unknown compression method " + method);
}
} }
public String getName () /**
* Returns the name of this zip file.
*/
public String getName()
{ {
return name; return name;
} }
/** /**
* Returns the number of entries in this ZipFile. * Returns the number of entries in this zip file.
* @exception IllegalStateException if the ZipFile has been closed.
*
* @since 1.2
*/ */
public int size () public int size()
{ {
if (entries == null) try
throw new IllegalStateException("ZipFile already closed"); {
else return entries.length;
return numEntries; }
catch (NullPointerException ex)
{
throw new IllegalStateException("ZipFile has closed");
} }
}
private static class ZipEntryEnumeration implements Enumeration
{
ZipEntry[] array;
int ptr = 0;
protected void finalize () throws IOException public ZipEntryEnumeration(ZipEntry[] arr)
{ {
close(); array = arr;
} }
private int readu2 () throws IOException public boolean hasMoreElements()
{ {
int byte0 = file.read(); return ptr < array.length;
int byte1 = file.read();
if (byte0 < 0 || byte1 < 0)
throw new ZipException (".zip archive ended prematurely");
return ((byte1 & 0xFF) << 8) | (byte0 & 0xFF);
} }
private int read4 () throws IOException public Object nextElement()
{ {
int byte0 = file.read(); try
int byte1 = file.read(); {
int byte2 = file.read(); /* We return a clone, just to be safe that the user doesn't
int byte3 = file.read(); * change the entry.
if (byte3 < 0) */
throw new ZipException (".zip archive ended prematurely"); return array[ptr++].clone();
return ((byte3 & 0xFF) << 24) + ((byte2 & 0xFF) << 16) }
+ ((byte1 & 0xFF) << 8) + (byte0 & 0xFF); catch (ArrayIndexOutOfBoundsException ex)
{
throw new NoSuchElementException();
}
}
} }
ZipEntry entries; private static class PartialInputStream extends InputStream
int numEntries; {
RandomAccessFile file; RandomAccessFile raf;
String name; long filepos, end;
/** File to delete on close or null. */
File delete_on_close;
} public PartialInputStream(RandomAccessFile raf, long start, long len)
{
this.raf = raf;
filepos = start;
end = start + len;
}
final class ZipEnumeration implements java.util.Enumeration public int available()
{ {
ZipEntry entry; long amount = end - filepos;
if (amount > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
return (int) amount;
}
ZipEnumeration (ZipFile zfile) public int read() throws IOException
{
if (filepos == end)
return -1;
synchronized (raf)
{ {
entry = zfile.entries; raf.seek(filepos++);
return raf.read();
}
} }
public boolean hasMoreElements () public int read(byte[] b, int off, int len) throws IOException
{
if (len > end - filepos)
{
len = (int) (end - filepos);
if (len == 0)
return -1;
}
synchronized (raf)
{ {
return entry != null; raf.seek(filepos);
int count = raf.read(b, off, len);
if (count > 0)
filepos += len;
return count;
}
} }
public Object nextElement () public long skip(long amount)
{ {
ZipEntry cur = entry; if (amount < 0)
if (cur == null) throw new IllegalArgumentException();
throw new java.util.NoSuchElementException(); if (amount > end - filepos)
entry = cur.next; amount = end - filepos;
return cur; filepos += amount;
return amount;
}
} }
} }
/* ZipInputStream.java - Input filter for reading zip file /* java.util.zip.ZipInputStream
Copyright (C) 1999, 2000 Free Software Foundation, Inc. Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath. This file is part of GNU Classpath.
...@@ -36,259 +36,327 @@ obligated to do so. If you do not wish to do so, delete this ...@@ -36,259 +36,327 @@ obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */ exception statement from your version. */
package java.util.zip; package java.util.zip;
import java.io.*; import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;
import java.util.Enumeration;
/** /**
* @author Per Bothner * This is a FilterInputStream that reads the files in an zip archive
* @date May 1999. * one after another. It has a special method to get the zip entry of
*/ * the next file. The zip entry contains information about the file name
* size, compressed size, CRC, etc.
/* *
* Written using on-line Java Platform 1.2 API Specification, as well * It includes support for STORED and DEFLATED entries.
* as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998). *
* Status: Quite incomplete, but can read uncompressed .zip archives. * @author Jochen Hoenicke
*/ */
public class ZipInputStream extends InflaterInputStream implements ZipConstants
{
private CRC32 crc = new CRC32();
private ZipEntry entry = null;
// We do not calculate the CRC and compare it with the specified value; private int csize;
// we probably should. FIXME. private int size;
private int method;
private int flags;
private int avail;
/**
* Creates a new Zip input stream, reading a zip archive.
*/
public ZipInputStream(InputStream in)
{
super(in, new Inflater(true));
}
public class ZipInputStream extends InflaterInputStream implements ZipConstants private void fillBuf() throws IOException
{
public ZipInputStream (InputStream in)
{ {
super (in, new Inflater (true)); avail = len = in.read(buf, 0, buf.length);
} }
public ZipEntry getNextEntry () throws IOException private int readBuf(byte[] out, int offset, int length) throws IOException
{ {
if (closed) if (avail <= 0)
throw new IOException ("stream closed");
if (current != null)
closeEntry();
if (in.read() != 'P'
|| in.read() != 'K')
return null;
int code = in.read();
while (code == '\001')
{ {
code = in.read(); fillBuf();
if (code != '\002') if (avail <= 0)
return null; return -1;
in.skip(16);
int size = read4();
in.skip(4);
int fname_length = readu2();
int extra_length = readu2();
int fcomment_length = readu2();
// `12' is the number of bytes between the comment length
// field and the end of the fixed part of the header:
// 2 bytes for `disk number start'
// 2 bytes for `internal file attributes'
// 4 bytes for `external file attributes'
// 4 bytes for `relative offset of local header'
in.skip(12 + fname_length + extra_length + fcomment_length);
if (in.read() != 'P' || in.read() != 'K')
return null;
code = in.read();
} }
if (code == '\005') if (length > avail)
{ length = avail;
if (in.read() != '\006') System.arraycopy(buf, len - avail, out, offset, length);
return null; avail -= length;
in.skip(16); return length;
int comment_size = readu2();
in.skip(comment_size);
if (in.read() != 'P' || in.read() != 'K')
return null;
code = in.read();
} }
if (code != '\003'
|| in.read() != '\004') private void readFully(byte[] out) throws IOException
return null; {
int ex_version = readu2(); int off = 0;
current_flags = readu2(); int len = out.length;
int method = readu2(); while (len > 0)
int modtime = readu2();
int moddate = readu2();
int crc = read4();
int compressedSize = read4();
int uncompressedSize = read4();
int filenameLength = readu2();
int extraLength = readu2();
byte[] bname = new byte[filenameLength];
readFully(bname);
ZipEntry entry = createZipEntry(new String(bname, "8859_1"));
if (extraLength > 0)
{ {
byte[] bextra = new byte[extraLength]; int count = readBuf(out, off, len);
readFully(bextra); if (count == -1)
entry.extra = bextra; throw new EOFException();
off += count;
len -= count;
} }
entry.compressedSize = compressedSize;
entry.size = uncompressedSize;
entry.crc = (long) crc & 0xffffffffL;
entry.method = method;
entry.time = ZipEntry.timeFromDOS(moddate, modtime);
current = entry;
avail = uncompressedSize;
compressed_bytes = compressedSize;
return entry;
} }
// We override fill to let us control how much data gets read from private final int readLeByte() throws IOException
// the underlying input stream. This lets us avoid having to push
// back data.
protected void fill () throws IOException
{ {
if (closed) if (avail <= 0)
throw new IOException ("stream closed");
int count = buf.length;
if (count > compressed_bytes)
count = compressed_bytes;
len = in.read(buf, 0, count);
if (len != -1)
{ {
compressed_bytes -= len; fillBuf();
inf.setInput(buf, 0, len); if (avail <= 0)
throw new ZipException("EOF in header");
} }
return buf[len - avail--] & 0xff;
} }
/** /**
* Creates a new ZipEntry with the given name. * Read an unsigned short in little endian byte order.
* Used by ZipInputStream when normally <code>new ZipEntry (name)</code>
* would be called. This gives subclasses such as JarInputStream a change
* to override this method and add aditional information to the ZipEntry
* (subclass).
*/ */
protected ZipEntry createZipEntry (String name) private final int readLeShort() throws IOException
{ {
return new ZipEntry (name); return readLeByte() | (readLeByte() << 8);
} }
public int read (byte[] b, int off, int len) throws IOException /**
* Read an int in little endian byte order.
*/
private final int readLeInt() throws IOException
{ {
if (closed) return readLeShort() | (readLeShort() << 16);
throw new IOException ("stream closed"); }
if (len > avail)
len = avail; /**
int count; * Open the next entry from the zip archive, and return its description.
if (current.method == Deflater.DEFLATED) * If the previous entry wasn't closed, this method will close it.
count = super.read(b, off, len); */
else public ZipEntry getNextEntry() throws IOException
count = in.read(b, off, len);
if (count == -1 || avail == 0)
{ {
inf.reset(); if (crc == null)
count = -1; throw new IllegalStateException("Closed.");
if (entry != null)
closeEntry();
int header = readLeInt();
if (header == CENSIG)
{
/* Central Header reached. */
close();
return null;
} }
else if (header != LOCSIG)
avail -= count; throw new ZipException("Wrong Local header signature" + Integer.toHexString(header));
return count; /* skip version */
readLeShort();
flags = readLeShort();
method = readLeShort();
int dostime = readLeInt();
int crc = readLeInt();
csize = readLeInt();
size = readLeInt();
int nameLen = readLeShort();
int extraLen = readLeShort();
if (method == ZipOutputStream.STORED && csize != size)
throw new ZipException("Stored, but compressed != uncompressed");
byte[] buffer = new byte[nameLen];
readFully(buffer);
String name = new String(buffer);
entry = createZipEntry(name);
entry.setMethod(method);
if ((flags & 8) == 0)
{
entry.setCrc(crc & 0xffffffffL);
entry.setSize(size & 0xffffffffL);
entry.setCompressedSize(csize & 0xffffffffL);
}
entry.setDOSTime(dostime);
if (extraLen > 0)
{
byte[] extra = new byte[extraLen];
readFully(extra);
entry.setExtra(extra);
} }
public long skip (long n) throws IOException if (method == ZipOutputStream.DEFLATED && avail > 0)
{ {
if (closed) System.arraycopy(buf, len - avail, buf, 0, avail);
throw new IOException ("stream closed"); len = avail;
if (n > avail) avail = 0;
n = avail; inf.setInput(buf, 0, len);
long count; }
if (current.method == Deflater.DEFLATED) return entry;
count = super.skip(n); }
else
count = in.skip(n); private void readDataDescr() throws IOException
avail = avail - (int) count; {
return count; if (readLeInt() != EXTSIG)
throw new ZipException("Data descriptor signature not found");
entry.setCrc(readLeInt() & 0xffffffffL);
csize = readLeInt();
size = readLeInt();
entry.setSize(size & 0xffffffffL);
entry.setCompressedSize(csize & 0xffffffffL);
} }
/** /**
* Returns 0 if the ZipInputStream is closed and 1 otherwise. * Closes the current zip entry and moves to the next one.
*
* @since 1.2
*/ */
public int available() public void closeEntry() throws IOException
{ {
return closed ? 0 : 1; if (crc == null)
throw new IllegalStateException("Closed.");
if (entry == null)
return;
if (method == ZipOutputStream.DEFLATED)
{
if ((flags & 8) != 0)
{
/* We don't know how much we must skip, read until end. */
byte[] tmp = new byte[2048];
while (read(tmp) > 0)
;
/* read will close this entry */
return;
}
csize -= inf.getTotalIn();
avail = inf.getRemaining();
} }
private void readFully (byte[] b) throws IOException if (avail > csize && csize >= 0)
avail -= csize;
else
{ {
int off = 0; csize -= avail;
int len = b.length; avail = 0;
while (len > 0) while (csize != 0)
{ {
int count = in.read(b, off, len); long skipped = in.skip(csize & 0xffffffffL);
if (count <= 0) if (skipped <= 0)
throw new EOFException(".zip archive ended prematurely"); throw new ZipException("zip archive ends early.");
off += count; csize -= skipped;
len -= count; }
} }
size = 0;
crc.reset();
if (method == ZipOutputStream.DEFLATED)
inf.reset();
entry = null;
} }
private int readu2 () throws IOException public int available() throws IOException
{ {
int byte0 = in.read(); return entry != null ? 1 : 0;
int byte1 = in.read();
if (byte0 < 0 || byte1 < 0)
throw new EOFException(".zip archive ended prematurely");
return ((byte1 & 0xFF) << 8) | (byte0 & 0xFF);
} }
private int read4 () throws IOException /**
* Reads a byte from the current zip entry.
* @return the byte or -1 on EOF.
* @exception IOException if a i/o error occured.
* @exception ZipException if the deflated stream is corrupted.
*/
public int read() throws IOException
{ {
int byte0 = in.read(); byte[] b = new byte[1];
int byte1 = in.read(); if (read(b, 0, 1) <= 0)
int byte2 = in.read(); return -1;
int byte3 = in.read(); return b[0] & 0xff;
if (byte3 < 0)
throw new EOFException(".zip archive ended prematurely");
return ((byte3 & 0xFF) << 24) + ((byte2 & 0xFF) << 16)
+ ((byte1 & 0xFF) << 8) + (byte0 & 0xFF);
} }
public void closeEntry () throws IOException /**
* Reads a block of bytes from the current zip entry.
* @return the number of bytes read (may be smaller, even before
* EOF), or -1 on EOF.
* @exception IOException if a i/o error occured.
* @exception ZipException if the deflated stream is corrupted.
*/
public int read(byte[] b, int off, int len) throws IOException
{ {
if (current != null) if (crc == null)
throw new IllegalStateException("Closed.");
if (entry == null)
return -1;
boolean finished = false;
switch (method)
{ {
if (avail > 0) case ZipOutputStream.DEFLATED:
skip (avail); len = super.read(b, off, len);
if ((current_flags & 8) != 0) if (len < 0)
{ {
int sig = read4(); if (!inf.finished())
if (sig != 0x04034b50) throw new ZipException("Inflater not finished!?");
throw new ZipException("bad/missing magic number at end of .zip entry"); avail = inf.getRemaining();
int crc = read4(); if ((flags & 8) != 0)
int compressedSize = read4(); readDataDescr();
int uncompressedSize = read4();
if (current.compressedSize != compressedSize if (inf.getTotalIn() != csize
|| current.size != uncompressedSize || inf.getTotalOut() != size)
|| current.crc != crc) throw new ZipException("size mismatch: "+csize+";"+size+" <-> "+inf.getTotalIn()+";"+inf.getTotalOut());
throw new ZipException("bad data descriptor at end of .zip entry"); inf.reset();
finished = true;
} }
current = null; break;
avail = 0;
case ZipOutputStream.STORED:
if (len > csize && csize >= 0)
len = csize;
len = readBuf(b, off, len);
if (len > 0)
{
csize -= len;
size -= len;
} }
if (csize == 0)
finished = true;
else if (len < 0)
throw new ZipException("EOF in stored block");
break;
}
if (len > 0)
crc.update(b, off, len);
if (finished)
{
if ((crc.getValue() & 0xffffffffL) != entry.getCrc())
throw new ZipException("CRC mismatch");
crc.reset();
entry = null;
}
return len;
} }
/** /**
* Closes this InflaterInputStream. * Closes the zip file.
* * @exception IOException if a i/o error occured.
* @since 1.2
*/ */
public void close () throws IOException public void close() throws IOException
{ {
current = null;
closed = true;
super.close(); super.close();
crc = null;
entry = null;
} }
private ZipEntry current; /**
private int current_flags; * Creates a new zip entry for the given name. This is equivalent
// Number of uncompressed bytes to be read. * to new ZipEntry(name).
private int avail; * @param name the name of the zip entry.
// Number of bytes we can read from underlying stream. */
private int compressed_bytes; protected ZipEntry createZipEntry(String name)
// Is this ZipInputStream closed? Set by the close() method. {
private boolean closed = false; return new ZipEntry(name);
}
} }
/* ZipOutputStream.java - Create a file in zip format /* java.util.zip.ZipOutputStream
Copyright (C) 1999, 2000 Free Software Foundation, Inc. Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath. This file is part of GNU Classpath.
...@@ -36,286 +36,362 @@ obligated to do so. If you do not wish to do so, delete this ...@@ -36,286 +36,362 @@ obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */ exception statement from your version. */
package java.util.zip; package java.util.zip;
import java.io.OutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
import java.util.Enumeration;
/**
* This is a FilterOutputStream that writes the files into a zip
* archive one after another. It has a special method to start a new
* zip entry. The zip entries contains information about the file name
* size, compressed size, CRC, etc.
*
* It includes support for STORED and DEFLATED entries.
*
* This class is not thread safe.
*
* @author Jochen Hoenicke
*/
public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants
{
private Vector entries = new Vector();
private CRC32 crc = new CRC32();
private ZipEntry curEntry = null;
import java.io.*; private int curMethod;
private int size;
private int offset = 0;
/* Written using on-line Java Platform 1.2 API Specification private byte[] zipComment = new byte[0];
* and JCL book. private int defaultMethod = DEFLATED;
* Believed complete and correct.
/**
* Our Zip version is hard coded to 1.0 resp. 2.0
*/ */
private final static int ZIP_STORED_VERSION = 10;
private final static int ZIP_DEFLATED_VERSION = 20;
public class ZipOutputStream extends DeflaterOutputStream /**
implements ZipConstants * Compression method. This method doesn't compress at all.
{ */
public static final int STORED = 0; public final static int STORED = 0;
public static final int DEFLATED = 8; /**
* Compression method. This method uses the Deflater.
*/
public final static int DEFLATED = 8;
public void close () throws IOException /**
* Creates a new Zip output stream, writing a zip archive.
* @param out the output stream to which the zip archive is written.
*/
public ZipOutputStream(OutputStream out)
{ {
finish (); super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
out.close();
} }
public void closeEntry () throws IOException /**
{ * Set the zip file comment.
int compressed_size; * @param comment the comment.
if (current.method == STORED) * @exception IllegalArgumentException if encoding of comment is
* longer than 0xffff bytes.
*/
public void setComment(String comment)
{ {
compressed_size = uncompressed_size; byte[] commentBytes;
commentBytes = comment.getBytes();
if (commentBytes.length > 0xffff)
throw new IllegalArgumentException("Comment too long.");
zipComment = commentBytes;
} }
else
/**
* Sets default compression method. If the Zip entry specifies
* another method its method takes precedence.
* @param method the method.
* @exception IllegalArgumentException if method is not supported.
* @see #STORED
* @see #DEFLATED
*/
public void setMethod(int method)
{ {
super.finish(); if (method != STORED && method != DEFLATED)
compressed_size = def.getTotalOut(); throw new IllegalArgumentException("Method not supported.");
defaultMethod = method;
} }
long crc = sum.getValue();
bytes_written += compressed_size;
if (current.getCrc() == -1 || current.getCompressedSize() == -1 /**
|| current.getSize() == -1) * Sets default compression level. The new level will be activated
* immediately.
* @exception IllegalArgumentException if level is not supported.
* @see Deflater
*/
public void setLevel(int level)
{ {
current.setCrc(crc); def.setLevel(level);
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");
current.next = chain;
chain = current;
current = null;
} }
public void write (int bval) throws IOException /**
{ * Write an unsigned short in little endian byte order.
if (current.method == STORED) */
private final void writeLeShort(int value) throws IOException
{ {
out.write(bval); out.write(value & 0xff);
} out.write((value >> 8) & 0xff);
else
super.write(bval);
sum.update(bval);
uncompressed_size += 1;
} }
public void write (byte[] buf, int off, int len) throws IOException /**
* Write an int in little endian byte order.
*/
private final void writeLeInt(int value) throws IOException
{ {
if (current.method == STORED) writeLeShort(value);
out.write(buf, off, len); writeLeShort(value >> 16);
else
super.write(buf, off, len);
sum.update(buf, off, len);
uncompressed_size += len;
} }
public void finish () throws IOException /**
{ * Starts a new Zip entry. It automatically closes the previous
if (current != null) * entry if present. If the compression method is stored, the entry
closeEntry (); * must have a valid size and crc, otherwise all elements (except
* name) are optional, but must be correct if present. If the time
// Write the central directory. * is not set in the entry, the current time is used.
long offset = bytes_written; * @param entry the entry.
int count = 0; * @exception IOException if an I/O error occured.
int bytes = 0; * @exception IllegalStateException if stream was finished
while (chain != null) */
public void putNextEntry(ZipEntry entry) throws IOException
{ {
bytes += write_entry (chain, false); if (entries == null)
++count; throw new IllegalStateException("ZipOutputStream was finished");
chain = chain.next;
}
// Write the end of the central directory record. int method = entry.getMethod();
put4 (0x06054b50); int flags = 0;
// Disk number. if (method == -1)
put2 (0); method = defaultMethod;
// 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);
}
// Helper for finish and putNextEntry. if (method == STORED)
private int write_entry (ZipEntry entry, boolean is_local)
throws IOException
{ {
int bytes = put4 (is_local ? 0x04034b50 : 0x02014b50); if (entry.getCompressedSize() >= 0)
if (! is_local)
bytes += put_version ();
bytes += put_version ();
boolean crc_after = false;
if (is_local
&& (entry.getCrc() == -1 || entry.getCompressedSize() == -1
|| entry.getSize() == -1))
crc_after = true;
// For the bits field we always indicate `normal' compression,
// even if that isn't true.
bytes += put2 (crc_after ? (1 << 3) : 0);
bytes += put2 (entry.method);
bytes += put2(0); // time - FIXME
bytes += put2(0); // date - FIXME
if (crc_after)
{ {
// CRC, compressedSize, and Size are always 0 in this header. if (entry.getSize() < 0)
// The actual values are given after the entry. entry.setSize(entry.getCompressedSize());
bytes += put4 (0); else if (entry.getSize() != entry.getCompressedSize())
bytes += put4 (0); throw new ZipException
bytes += put4 (0); ("Method STORED, but compressed size != size");
} }
else else
entry.setCompressedSize(entry.getSize());
if (entry.getSize() < 0)
throw new ZipException("Method STORED, but size not set");
if (entry.getCrc() < 0)
throw new ZipException("Method STORED, but crc not set");
}
else if (method == DEFLATED)
{ {
bytes += put4 ((int) (entry.getCrc())); if (entry.getCompressedSize() < 0
bytes += put4 ((int) (entry.getCompressedSize())); || entry.getSize() < 0 || entry.getCrc() < 0)
bytes += put4 ((int) (entry.getSize())); flags |= 8;
} }
byte[] name = entry.name.getBytes("8859_1"); if (curEntry != null)
bytes += put2 (name.length); closeEntry();
bytes += put2 (entry.extra == null ? 0 : entry.extra.length);
if (entry.getTime() < 0)
byte[] comment = null; entry.setTime(System.currentTimeMillis());
if (! is_local)
entry.flags = flags;
entry.offset = offset;
entry.setMethod(method);
curMethod = method;
/* Write the local file header */
writeLeInt(LOCSIG);
writeLeShort(method == STORED
? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
writeLeShort(flags);
writeLeShort(method);
writeLeInt(entry.getDOSTime());
if ((flags & 8) == 0)
{ {
if (entry.getComment() == null) writeLeInt((int)entry.getCrc());
bytes += put2 (0); writeLeInt((int)entry.getCompressedSize());
writeLeInt((int)entry.getSize());
}
else else
{ {
comment = entry.getComment().getBytes("8859_1"); writeLeInt(0);
bytes += put2 (comment.length); writeLeInt(0);
writeLeInt(0);
} }
byte[] name = entry.getName().getBytes();
// Disk number start. if (name.length > 0xffff)
bytes += put2 (0); throw new ZipException("Name too long.");
// Internal file attributes. byte[] extra = entry.getExtra();
bytes += put2 (0); if (extra == null)
// External file attributes. extra = new byte[0];
bytes += put4 (0); writeLeShort(name.length);
// Relative offset of local header. writeLeShort(extra.length);
bytes += put4 ((int) entry.relativeOffset); out.write(name);
out.write(extra);
offset += LOCHDR + name.length + extra.length;
/* Activate the entry. */
curEntry = entry;
crc.reset();
if (method == DEFLATED)
def.reset();
size = 0;
} }
out.write (name); /**
bytes += name.length; * Closes the current entry.
if (entry.extra != null) * @exception IOException if an I/O error occured.
{ * @exception IllegalStateException if no entry is active.
out.write(entry.extra); */
bytes += entry.extra.length; public void closeEntry() throws IOException
}
if (comment != null)
{ {
out.write(comment); if (curEntry == null)
bytes += comment.length; throw new IllegalStateException("No open entry");
}
bytes_written += bytes; /* First finish the deflater, if appropriate */
return bytes; if (curMethod == DEFLATED)
} super.finish();
public void putNextEntry (ZipEntry entry) throws IOException int csize = curMethod == DEFLATED ? def.getTotalOut() : size;
{
if (current != null)
closeEntry ();
if (entry.method < 0 ) if (curEntry.getSize() < 0)
entry.method = method; curEntry.setSize(size);
if (entry.method == STORED) else if (curEntry.getSize() != size)
{ throw new ZipException("size was "+size
if (entry.getSize() == -1 || entry.getCrc() == -1) +", but I expected "+curEntry.getSize());
throw new ZipException ("required entry not set");
// 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);
sum.reset();
uncompressed_size = 0;
}
public void setLevel (int level) if (curEntry.getCompressedSize() < 0)
{ curEntry.setCompressedSize(csize);
if (level != Deflater.DEFAULT_COMPRESSION else if (curEntry.getCompressedSize() != csize)
&& (level < Deflater.NO_COMPRESSION throw new ZipException("compressed size was "+csize
|| level > Deflater.BEST_COMPRESSION)) +", but I expected "+curEntry.getSize());
throw new IllegalArgumentException ();
this.level = level;
}
public void setMethod (int method) if (curEntry.getCrc() < 0)
{ curEntry.setCrc(crc.getValue());
if (method != DEFLATED && method != STORED) else if (curEntry.getCrc() != crc.getValue())
throw new IllegalArgumentException (); throw new ZipException("crc was " + Long.toHexString(crc.getValue())
this.method = method; + ", but I expected "
} + Long.toHexString(curEntry.getCrc()));
offset += csize;
public void setComment (String comment) /* Now write the data descriptor entry if needed. */
if (curMethod == DEFLATED && (curEntry.flags & 8) != 0)
{ {
if (comment.length() > 65535) writeLeInt(EXTSIG);
throw new IllegalArgumentException (); writeLeInt((int)curEntry.getCrc());
this.comment = comment; writeLeInt((int)curEntry.getCompressedSize());
writeLeInt((int)curEntry.getSize());
offset += EXTHDR;
} }
public ZipOutputStream (OutputStream out) entries.addElement(curEntry);
{ curEntry = null;
super (out, new Deflater (Deflater.DEFAULT_COMPRESSION, true), 8192);
sum = new CRC32 ();
} }
private int put2 (int i) throws IOException /**
* Writes the given buffer to the current entry.
* @exception IOException if an I/O error occured.
* @exception IllegalStateException if no entry is active.
*/
public void write(byte[] b, int off, int len) throws IOException
{ {
out.write (i); if (curEntry == null)
out.write (i >> 8); throw new IllegalStateException("No open entry.");
return 2;
}
private int put4 (int i) throws IOException switch (curMethod)
{ {
out.write (i); case DEFLATED:
out.write (i >> 8); super.write(b, off, len);
out.write (i >> 16); break;
out.write (i >> 24);
return 4; case STORED:
out.write(b, off, len);
break;
} }
private int put_version () throws IOException crc.update(b, off, len);
{ size += len;
// FIXME: for now we assume Unix, and we ignore the version
// number.
return put2 (3 << 8);
} }
// The entry we are currently writing, or null if we've called /**
// closeEntry. * Finishes the stream. This will write the central directory at the
private ZipEntry current; * end of the zip file and flush the stream.
// The chain of entries which have been written to this file. * @exception IOException if an I/O error occured.
private ZipEntry chain; */
public void finish() throws IOException
{
if (entries == null)
return;
if (curEntry != null)
closeEntry();
private int method = DEFLATED; int numEntries = 0;
private int level = Deflater.DEFAULT_COMPRESSION; int sizeEntries = 0;
private String comment = "";
private long bytes_written;
private int uncompressed_size; Enumeration enum = entries.elements();
while (enum.hasMoreElements())
{
ZipEntry entry = (ZipEntry) enum.nextElement();
int method = entry.getMethod();
writeLeInt(CENSIG);
writeLeShort(method == STORED
? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
writeLeShort(method == STORED
? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
writeLeShort(entry.flags);
writeLeShort(method);
writeLeInt(entry.getDOSTime());
writeLeInt((int)entry.getCrc());
writeLeInt((int)entry.getCompressedSize());
writeLeInt((int)entry.getSize());
byte[] name = entry.getName().getBytes();
if (name.length > 0xffff)
throw new ZipException("Name too long.");
byte[] extra = entry.getExtra();
if (extra == null)
extra = new byte[0];
String strComment = entry.getComment();
byte[] comment = strComment != null
? strComment.getBytes() : new byte[0];
if (comment.length > 0xffff)
throw new ZipException("Comment too long.");
writeLeShort(name.length);
writeLeShort(extra.length);
writeLeShort(comment.length);
writeLeShort(0); /* disk number */
writeLeShort(0); /* internal file attr */
writeLeInt(0); /* external file attr */
writeLeInt(entry.offset);
out.write(name);
out.write(extra);
out.write(comment);
numEntries++;
sizeEntries += CENHDR + name.length + extra.length + comment.length;
}
/** The checksum object. */ writeLeInt(ENDSIG);
private Checksum sum; writeLeShort(0); /* disk number */
writeLeShort(0); /* disk with start of central dir */
writeLeShort(numEntries);
writeLeShort(numEntries);
writeLeInt(sizeEntries);
writeLeInt(offset);
writeLeShort(zipComment.length);
out.write(zipComment);
out.flush();
entries = null;
}
} }
// natDeflater.cc - Implementation of Deflater native methods. // natDeflater.cc - Implementation of Deflater native methods.
/* Copyright (C) 1999 Free Software Foundation /* Copyright (C) 1999, 2002 Free Software Foundation
This file is part of libgcj. This file is part of libgcj.
...@@ -125,6 +125,7 @@ java::util::zip::Deflater::reset () ...@@ -125,6 +125,7 @@ java::util::zip::Deflater::reset ()
// Just ignore errors. // Just ignore errors.
deflateReset (s); deflateReset (s);
flush_flag = 0; flush_flag = 0;
is_finished = false;
} }
void void
......
// natInflater.cc - Implementation of Inflater native methods. // natInflater.cc - Implementation of Inflater native methods.
/* Copyright (C) 1999 Free Software Foundation /* Copyright (C) 1999, 2002 Free Software Foundation
This file is part of libgcj. This file is part of libgcj.
...@@ -149,6 +149,8 @@ java::util::zip::Inflater::reset () ...@@ -149,6 +149,8 @@ java::util::zip::Inflater::reset ()
z_streamp s = (z_streamp) zstream; z_streamp s = (z_streamp) zstream;
// Just ignore errors. // Just ignore errors.
inflateReset (s); inflateReset (s);
is_finished = false;
dict_needed = false;
} }
void void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment