+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>
* java/lang/natString.cc (init): Handle case where DONT_COPY is
/* 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.
throw new IOException ("stream closed");
if (inf.finished())
return -1;
- if (inf.needsInput())
- fill ();
- int count;
- try
+
+ int count = 0;
+ while (count == 0)
{
- count = inf.inflate(buf, off, len);
- if (count == 0)
+ if (inf.needsInput())
+ fill ();
+ try
{
- if (this.len == -1)
- return -1; // Couldn't get any more data to feed to the Inflater
- if (inf.needsDictionary())
- throw new ZipException ("Inflater needs Dictionary");
- }
- }
- catch (DataFormatException dfe)
- {
- throw new ZipException (dfe.getMessage());
+ count = inf.inflate(buf, off, len);
+ if (count == 0)
+ {
+ if (this.len == -1)
+ {
+ // Couldn't get any more data to feed to the Inflater
+ return -1;
+ }
+ if (inf.needsDictionary())
+ throw new ZipException ("Inflater needs Dictionary");
+ }
+ }
+ catch (DataFormatException dfe)
+ {
+ throw new ZipException (dfe.getMessage());
+ }
}
return count;
}
-/* ZipConstants.java - Some constants used in the zip package
- Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+/* java.util.zip.ZipConstants
+ Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
-
+
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
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
{
- // Size in bytes of local file header, including signature.
- public static final int LOCAL_FILE_HEADER_SIZE = 30;
+ /* The local file header */
+ public final static int LOCHDR = 30;
+ public final static int LOCSIG = 'P'|('K'<<8)|(3<<16)|(4<<24);
+
+ public final static int LOCVER = 4;
+ 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;
- // Size in bytes of the "end of central directory" record, with signature.
- public static final int END_CENTRAL_DIR_SIZE = 22;
+ /* 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
- Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+/* java.util.zip.ZipEntry
+ Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath.
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
-
+
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
exception statement from your version. */
package java.util.zip;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.Date;
/**
- * @author Per Bothner
- * @date January 6, 1999.
- */
-
-/*
- * Written using on-line Java Platform 1.2 API Specification, as well
- * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
- * Status: Believed complete and correct.
- */
-
-/**
- * Represents entries in a zip file archive.
- * An Entry cn be created by giving a name or by giving an already existing
- * ZipEntries whose values should be copied. The name normally represents a
- * file path name or directory name.
+ * This class represents a member of a zip archive. ZipFile and
+ * 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
{
- // These values were determined using a simple test program.
- public static final int STORED = 0;
- 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;
- }
+ 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 */
+
+
+ /**
+ * Compression method. This method doesn't compress at all.
+ */
+ public final static int STORED = 0;
+ /**
+ * Compression method. This method uses the Deflater.
+ */
+ public final static int DEFLATED = 8;
/**
- * Creates a new ZipEntry using the fields of a given ZipEntry.
- * The comment, compressedSize, crc, extra, method, name, size, time and
- * 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
+ * Creates a zip entry with the given name.
+ * @param name the name. May include directory components separated
+ * by '/'.
*/
- public ZipEntry (ZipEntry ent)
+ public ZipEntry(String name)
{
- comment = ent.comment;
- compressedSize = ent.compressedSize;
- crc = ent.crc;
- extra = ent.extra;
- method = ent.method;
- name = ent.name;
- size = ent.size;
- time = ent.time;
- relativeOffset = ent.relativeOffset;
+ if (name == null)
+ throw new NullPointerException();
+ this.name = name;
}
-
+
/**
- * Creates a clone of this ZipEntry. Calls <code>new ZipEntry (this)</code>
- * and creates a clone of the contents of the extra byte array field.
- *
- * @since 1.2
+ * Creates a copy of the given zip entry.
+ * @param e the entry to copy.
*/
- public Object clone ()
+ public ZipEntry(ZipEntry e)
{
- // JCL defines this as being the same as the copy constructor above,
- // except that value of the "extra" field is also copied.
- ZipEntry clone = new ZipEntry (this);
- clone.extra = (byte[]) extra.clone ();
- return clone;
+ 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;
}
- public String getComment () { return comment; }
-
- public long getCompressedSize () { return compressedSize; }
+ 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)
+ {
+ /* Ignore illegal time stamp */
+ known &= ~KNOWN_TIME;
+ }
+ }
- public long getCrc () { return crc; }
+ int getDOSTime()
+ {
+ if ((known & KNOWN_TIME) == 0)
+ 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;
+ }
+ }
- public byte[] getExtra() { return extra; }
+ /**
+ * Creates a copy of this zip entry.
+ */
+ /**
+ * Clones the entry.
+ */
+ public Object clone()
+ {
+ try
+ {
+ // The JCL says that the `extra' field is also copied.
+ ZipEntry clone = (ZipEntry) super.clone();
+ if (extra != null)
+ clone.extra = (byte[]) extra.clone();
+ return clone;
+ }
+ catch (CloneNotSupportedException ex)
+ {
+ throw new InternalError();
+ }
+ }
- public int getMethod () { return method; }
+ /**
+ * Returns the entry name. The path components in the entry are
+ * always separated by slashes ('/').
+ */
+ public String getName()
+ {
+ return name;
+ }
- public String getName () { return name; }
+ /**
+ * 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 long getSize () { return size; }
+ /**
+ * 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()
+ {
+ return (known & KNOWN_TIME) != 0 ? time * 1000L : -1;
+ }
- public long getTime () { return time; }
+ /**
+ * Sets the size of the uncompressed data.
+ * @exception IllegalArgumentException if size is not in 0..0xffffffffL
+ */
+ public void setSize(long size)
+ {
+ if ((size & 0xffffffff00000000L) != 0)
+ throw new IllegalArgumentException();
+ this.size = (int) size;
+ this.known |= KNOWN_SIZE;
+ }
- public boolean isDirectory ()
+ /**
+ * Gets the size of the uncompressed data.
+ * @return the size or -1 if unknown.
+ */
+ public long getSize()
{
- if (name != null)
- {
- int nlen = name.length();
- if (nlen > 0 && name.charAt(nlen-1) == '/')
- return true;
- }
- return false;
+ 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)
- throw new IllegalArgumentException ();
- this.comment = comment;
+ if ((csize & 0xffffffff00000000L) != 0)
+ throw new IllegalArgumentException();
+ this.compressedSize = (int) csize;
+ this.known |= KNOWN_CSIZE;
}
-
+
/**
- * Sets the compressedSize of this ZipEntry.
- * The new size must be between 0 and 0xffffffffL.
- * @since 1.2
+ * Gets the size of the compressed data.
+ * @return the size or -1 if unknown.
*/
- public void setCompressedSize (long compressedSize)
+ public long getCompressedSize()
{
- if (compressedSize < 0 || compressedSize > 0xffffffffL)
- throw new IllegalArgumentException ();
- this.compressedSize = compressedSize;
+ return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L;
}
- 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)
- throw new IllegalArgumentException ();
- this.crc = crc;
+ if ((crc & 0xffffffff00000000L) != 0)
+ throw new IllegalArgumentException();
+ 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)
- throw new IllegalArgumentException ();
- this.extra = extra;
+ return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;
}
- 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)
- throw new IllegalArgumentException ();
- this.method = method;
+ if (method != ZipOutputStream.STORED
+ && method != ZipOutputStream.DEFLATED)
+ 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)
- throw new IllegalArgumentException ();
- this.size = size;
+ return method;
}
- public void setTime (long time)
+ /**
+ * Sets the extra data.
+ * @exception IllegalArgumentException if extra is longer than 0xffff bytes.
+ */
+ public void setExtra(byte[] extra)
{
- this.time = time;
+ if (extra == null)
+ {
+ this.extra = null;
+ return;
+ }
+
+ if (extra.length > 0xffff)
+ throw new IllegalArgumentException();
+ this.extra = extra;
+ try
+ {
+ 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;
+ }
}
- private final static short[] daysToMonthStart = {
- //Jan Feb Mar Apr May Jun Jul
- 0, 31, 31+28, 2*31+28, 2*31+28+30, 3*31+28+30, 3*31+28+2*30,
- // Aug Sep Oct Nov Dec
- 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};
+ /**
+ * Gets the extra data.
+ * @return the extra data or null if not set.
+ */
+ public byte[] getExtra()
+ {
+ return extra;
+ }
- /** Convert a DOS-style type value to milliseconds since 1970. */
- static long timeFromDOS (int date, int time)
+ /**
+ * Sets the entry comment.
+ * @exception IllegalArgumentException if comment is longer than 0xffff.
+ */
+ public void setComment(String comment)
{
- int sec = 2 * (time & 0x1f);
- 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. */
+ if (comment.length() > 0xffff)
+ throw new IllegalArgumentException();
+ this.comment = comment;
+ }
- // Guard against invalid or missing date causing IndexOutOfBoundsException.
- if (mon < 0 || mon > 11)
- return -1;
+ /**
+ * Gets the comment.
+ * @return the comment or null if not set.
+ */
+ public String getComment()
+ {
+ return comment;
+ }
- long mtime = (((hrs * 60) + min) * 60 + sec) * 1000;
+ /**
+ * Gets true, if the entry is a directory. This is solely
+ * determined by the name, a trailing slash '/' marks a directory.
+ */
+ public boolean isDirectory()
+ {
+ int nlen = name.length();
+ return nlen > 0 && name.charAt(nlen - 1) == '/';
+ }
- // Leap year calculations are rather trivial in this case ...
- int days = 365 * year + ((year+1)>>2);
- days += daysToMonthStart[mon];
- if ((year & 3) == 0 && mon > 1)
- days++;
- days += day;
- return (days * 24*60*60L + ((hrs * 60) + min) * 60 + sec) * 1000L;
+ /**
+ * Gets the string representation of this ZipEntry. This is just
+ * the name as returned by getName().
+ */
+ public String toString()
+ {
+ return name;
}
- 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
- Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+/* java.util.zip.ZipFile
+ Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
-
+
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
exception statement from your version. */
package java.util.zip;
-
-import java.io.*;
-
-/* Written using on-line Java Platform 1.2 API Specification
- * and JCL book.
- * Believed complete and correct.
+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
{
- public static final int OPEN_READ = 1;
- public static final int OPEN_DELETE = 4;
- public ZipFile (String fname) throws IOException
+ /** Mode flag to open a zip file for reading
+ *
+ */
+
+ public static final int OPEN_READ = 0x1;
+
+ /** Mode flag to delete a zip file after reading
+ *
+ */
+
+ public static final int OPEN_DELETE = 0x4;
+
+ 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)
{
- delete_on_close = f;
- f.deleteOnExit();
- }
- else
- {
- delete_on_close = null;
+ throw new IllegalArgumentException("OPEN_DELETE mode not supported yet in java.util.zip.ZipFile");
}
+ this.raf = new RandomAccessFile(file, "r");
+ this.name = file.getName();
+ readEntries();
+ }
- file = new RandomAccessFile(f, "r");
- name = f.getName();
- readDirectory ();
+ /**
+ * 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;
}
- void readDirectory () throws IOException
+ /**
+ * 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
{
- 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++)
+ /* 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++)
{
- 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)
+ 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[] bextra = new byte[extraLength];
- file.readFully(bextra);
- entry.extra = bextra;
+ byte[] extra = new byte[extraLen];
+ raf.readFully(extra);
+ entry.setExtra(extra);
}
- if (commentLength > 0)
+ if (commentLen > 0)
{
- byte[] bcomment = new byte[commentLength];
- file.readFully(bcomment);
- entry.comment = new String(bcomment, "8859_1");
+ raf.readFully(buffer, 0, commentLen);
+ entry.setComment(new String(buffer, 0, commentLen));
}
- 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;
+ entry.zipFileIndex = i;
+ entry.offset = offset;
+ entries[i] = entry;
}
}
- public java.util.Enumeration entries()
- {
- return new ZipEnumeration(this);
- }
-
+ /**
+ * 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
{
- file.close();
entries = null;
- numEntries = 0;
- if (delete_on_close != null)
- delete_on_close.delete();
- }
-
- public ZipEntry getEntry(String name)
- {
- for (ZipEntry entry = entries; entry != null; entry = entry.next)
+ synchronized (raf)
{
- if (name.equals(entry.getName()))
- return entry;
+ raf.close();
}
- return null;
}
- public InputStream getInputStream(ZipEntry ze) throws IOException
+ /**
+ * Returns an enumeration of all Zip entries in this Zip file.
+ */
+ public Enumeration entries()
{
- byte[] buffer = new byte[(int) ze.getCompressedSize()];
-
- /* Read the size of the extra field, and skip to the start of the
- data. */
- file.seek (ze.relativeOffset + ZipConstants.LOCAL_FILE_HEADER_SIZE - 2);
- int extraFieldLength = readu2();
- file.skipBytes (ze.getName().length() + extraFieldLength);
-
- file.readFully(buffer);
-
- InputStream is = new ByteArrayInputStream (buffer);
- if (ze.getMethod() == ZipEntry.DEFLATED)
- // Data in zipfile entries does not have a zlib header, so construct
- // an Inflater with the `nowrapper' option.
- is = new InflaterInputStream (is, new Inflater (true), 512);
- return is;
+ if (entries == null)
+ throw new IllegalStateException("ZipFile has closed");
+ return new ZipEntryEnumeration(entries);
}
- public String getName ()
+ private int getEntryIndex(String name)
{
- return name;
+ for (int i = 0; i < entries.length; i++)
+ if (name.equals(entries[i].getName()))
+ return i;
+ return -1;
}
/**
- * Returns the number of entries in this ZipFile.
- * @exception IllegalStateException if the ZipFile has been closed.
- *
- * @since 1.2
- */
- public int size ()
+ * 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 already closed");
- else
- return numEntries;
+ throw new IllegalStateException("ZipFile has closed");
+ int index = getEntryIndex(name);
+ return index >= 0 ? (ZipEntry) entries[index].clone() : null;
}
- protected void finalize () 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
{
- close();
+ 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();
+
+ if (entry.getMethod() != readLeShort())
+ throw new ZipException("Compression method mismatch");
+
+ /* Skip time, crc, size and csize */
+ if (raf.skipBytes(LOCNAM - LOCTIM) != LOCNAM - LOCTIM)
+ throw new EOFException();
+
+ if (entry.getName().length() != readLeShort())
+ throw new ZipException("file name length mismatch");
+
+ int extraLen = entry.getName().length() + readLeShort();
+ return entry.offset + LOCHDR + extraLen;
+ }
}
- private int readu2 () throws IOException
+ /**
+ * 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
{
- int byte0 = file.read();
- int byte1 = file.read();
- if (byte0 < 0 || byte1 < 0)
- throw new ZipException (".zip archive ended prematurely");
- return ((byte1 & 0xFF) << 8) | (byte0 & 0xFF);
- }
+ 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();
+ }
- private int read4 () throws IOException
+ 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;
+ case ZipOutputStream.DEFLATED:
+ return new InflaterInputStream(is, new Inflater(true));
+ default:
+ throw new ZipException("Unknown compression method " + method);
+ }
+ }
+
+ /**
+ * Returns the name of this zip file.
+ */
+ public String getName()
{
- int byte0 = file.read();
- int byte1 = file.read();
- int byte2 = file.read();
- int byte3 = file.read();
- if (byte3 < 0)
- throw new ZipException (".zip archive ended prematurely");
- return ((byte3 & 0xFF) << 24) + ((byte2 & 0xFF) << 16)
- + ((byte1 & 0xFF) << 8) + (byte0 & 0xFF);
+ return name;
}
- ZipEntry entries;
- int numEntries;
- RandomAccessFile file;
- String name;
- /** File to delete on close or null. */
- File delete_on_close;
-
-}
-
-final class ZipEnumeration implements java.util.Enumeration
-{
- ZipEntry entry;
-
- ZipEnumeration (ZipFile zfile)
+ /**
+ * Returns the number of entries in this zip file.
+ */
+ public int size()
{
- entry = zfile.entries;
+ try
+ {
+ return entries.length;
+ }
+ catch (NullPointerException ex)
+ {
+ throw new IllegalStateException("ZipFile has closed");
+ }
}
-
- public boolean hasMoreElements ()
+
+ private static class ZipEntryEnumeration implements Enumeration
{
- return entry != null;
+ ZipEntry[] array;
+ int ptr = 0;
+
+ public ZipEntryEnumeration(ZipEntry[] arr)
+ {
+ array = arr;
+ }
+
+ public boolean hasMoreElements()
+ {
+ return ptr < array.length;
+ }
+
+ public Object nextElement()
+ {
+ try
+ {
+ /* We return a clone, just to be safe that the user doesn't
+ * change the entry.
+ */
+ return array[ptr++].clone();
+ }
+ catch (ArrayIndexOutOfBoundsException ex)
+ {
+ throw new NoSuchElementException();
+ }
+ }
}
- public Object nextElement ()
+ private static class PartialInputStream extends InputStream
{
- ZipEntry cur = entry;
- if (cur == null)
- throw new java.util.NoSuchElementException();
- entry = cur.next;
- return cur;
+ RandomAccessFile raf;
+ long filepos, end;
+
+ public PartialInputStream(RandomAccessFile raf, long start, long len)
+ {
+ this.raf = raf;
+ filepos = start;
+ end = start + len;
+ }
+
+ public int available()
+ {
+ long amount = end - filepos;
+ if (amount > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ return (int) amount;
+ }
+
+ public int read() throws IOException
+ {
+ if (filepos == end)
+ return -1;
+ synchronized (raf)
+ {
+ raf.seek(filepos++);
+ return raf.read();
+ }
+ }
+
+ 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)
+ {
+ raf.seek(filepos);
+ int count = raf.read(b, off, len);
+ if (count > 0)
+ filepos += len;
+ return count;
+ }
+ }
+
+ public long skip(long amount)
+ {
+ if (amount < 0)
+ throw new IllegalArgumentException();
+ if (amount > end - filepos)
+ amount = end - filepos;
+ filepos += amount;
+ return amount;
+ }
}
}
-/* ZipInputStream.java - Input filter for reading zip file
- Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+/* java.util.zip.ZipInputStream
+ Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath.
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
-
+
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
exception statement from your version. */
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
- * @date May 1999.
+ * This is a FilterInputStream that reads the files in an zip archive
+ * 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.
+ *
+ * It includes support for STORED and DEFLATED entries.
+ *
+ * @author Jochen Hoenicke
*/
+public class ZipInputStream extends InflaterInputStream implements ZipConstants
+{
+ private CRC32 crc = new CRC32();
+ private ZipEntry entry = null;
-/*
- * Written using on-line Java Platform 1.2 API Specification, as well
- * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
- * Status: Quite incomplete, but can read uncompressed .zip archives.
- */
+ private int csize;
+ private int size;
+ private int method;
+ private int flags;
+ private int avail;
-// We do not calculate the CRC and compare it with the specified value;
-// we probably should. FIXME.
-
+ /**
+ * 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
-{
- public ZipInputStream (InputStream in)
+ private void fillBuf() throws IOException
{
- 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)
- 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')
+ if (avail <= 0)
{
- code = in.read();
- if (code != '\002')
- return null;
- 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();
+ fillBuf();
+ if (avail <= 0)
+ return -1;
}
- if (code == '\005')
+ if (length > avail)
+ length = avail;
+ System.arraycopy(buf, len - avail, out, offset, length);
+ avail -= length;
+ return length;
+ }
+
+ private void readFully(byte[] out) throws IOException
+ {
+ int off = 0;
+ int len = out.length;
+ while (len > 0)
{
- if (in.read() != '\006')
- return null;
- in.skip(16);
- int comment_size = readu2();
- in.skip(comment_size);
- if (in.read() != 'P' || in.read() != 'K')
- return null;
- code = in.read();
+ int count = readBuf(out, off, len);
+ if (count == -1)
+ throw new EOFException();
+ off += count;
+ len -= count;
}
- if (code != '\003'
- || in.read() != '\004')
- return null;
- int ex_version = readu2();
- current_flags = readu2();
- int method = readu2();
- 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)
+ }
+
+ private final int readLeByte() throws IOException
+ {
+ if (avail <= 0)
{
- byte[] bextra = new byte[extraLength];
- readFully(bextra);
- entry.extra = bextra;
+ fillBuf();
+ if (avail <= 0)
+ throw new ZipException("EOF in header");
}
- 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;
+ return buf[len - avail--] & 0xff;
}
- // We override fill to let us control how much data gets read from
- // the underlying input stream. This lets us avoid having to push
- // back data.
- protected void fill () throws IOException
+ /**
+ * Read an unsigned short in little endian byte order.
+ */
+ private final int readLeShort() throws IOException
{
- if (closed)
- 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;
- inf.setInput(buf, 0, len);
- }
+ return readLeByte() | (readLeByte() << 8);
}
/**
- * Creates a new ZipEntry with the given name.
- * 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).
+ * Read an int in little endian byte order.
*/
- protected ZipEntry createZipEntry (String name)
+ private final int readLeInt() throws IOException
{
- return new ZipEntry (name);
+ return readLeShort() | (readLeShort() << 16);
}
- public int read (byte[] b, int off, int len) throws IOException
+ /**
+ * Open the next entry from the zip archive, and return its description.
+ * If the previous entry wasn't closed, this method will close it.
+ */
+ public ZipEntry getNextEntry() throws IOException
{
- if (closed)
- throw new IOException ("stream closed");
- if (len > avail)
- len = avail;
- int count;
- if (current.method == Deflater.DEFLATED)
- count = super.read(b, off, len);
- else
- count = in.read(b, off, len);
- if (count == -1 || avail == 0)
+ if (crc == null)
+ throw new IllegalStateException("Closed.");
+ if (entry != null)
+ closeEntry();
+
+ int header = readLeInt();
+ if (header == CENSIG)
{
- inf.reset();
- count = -1;
+ /* Central Header reached. */
+ close();
+ return null;
}
- else
- avail -= count;
- return count;
+ if (header != LOCSIG)
+ throw new ZipException("Wrong Local header signature" + Integer.toHexString(header));
+ /* 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);
+ }
+
+ if (method == ZipOutputStream.DEFLATED && avail > 0)
+ {
+ System.arraycopy(buf, len - avail, buf, 0, avail);
+ len = avail;
+ avail = 0;
+ inf.setInput(buf, 0, len);
+ }
+ return entry;
}
- public long skip (long n) throws IOException
+ private void readDataDescr() throws IOException
{
- if (closed)
- throw new IOException ("stream closed");
- if (n > avail)
- n = avail;
- long count;
- if (current.method == Deflater.DEFLATED)
- count = super.skip(n);
- else
- count = in.skip(n);
- 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.
- *
- * @since 1.2
+ * Closes the current zip entry and moves to the next one.
*/
- public int available()
+ public void closeEntry() throws IOException
{
- return closed ? 0 : 1;
- }
+ if (crc == null)
+ throw new IllegalStateException("Closed.");
+ if (entry == null)
+ return;
- private void readFully (byte[] b) throws IOException
- {
- int off = 0;
- int len = b.length;
- while (len > 0)
+ if (method == ZipOutputStream.DEFLATED)
{
- int count = in.read(b, off, len);
- if (count <= 0)
- throw new EOFException(".zip archive ended prematurely");
- off += count;
- len -= count;
+ 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();
+ }
+
+ if (avail > csize && csize >= 0)
+ avail -= csize;
+ else
+ {
+ csize -= avail;
+ avail = 0;
+ while (csize != 0)
+ {
+ long skipped = in.skip(csize & 0xffffffffL);
+ if (skipped <= 0)
+ throw new ZipException("zip archive ends early.");
+ csize -= skipped;
+ }
}
+
+ 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();
- int byte1 = in.read();
- if (byte0 < 0 || byte1 < 0)
- throw new EOFException(".zip archive ended prematurely");
- return ((byte1 & 0xFF) << 8) | (byte0 & 0xFF);
+ return entry != null ? 1 : 0;
}
- 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();
- int byte1 = in.read();
- int byte2 = in.read();
- int byte3 = in.read();
- if (byte3 < 0)
- throw new EOFException(".zip archive ended prematurely");
- return ((byte3 & 0xFF) << 24) + ((byte2 & 0xFF) << 16)
- + ((byte1 & 0xFF) << 8) + (byte0 & 0xFF);
+ byte[] b = new byte[1];
+ if (read(b, 0, 1) <= 0)
+ return -1;
+ return b[0] & 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)
- skip (avail);
- if ((current_flags & 8) != 0)
+ case ZipOutputStream.DEFLATED:
+ len = super.read(b, off, len);
+ if (len < 0)
{
- int sig = read4();
- if (sig != 0x04034b50)
- throw new ZipException("bad/missing magic number at end of .zip entry");
- int crc = read4();
- int compressedSize = read4();
- int uncompressedSize = read4();
- if (current.compressedSize != compressedSize
- || current.size != uncompressedSize
- || current.crc != crc)
- throw new ZipException("bad data descriptor at end of .zip entry");
+ if (!inf.finished())
+ throw new ZipException("Inflater not finished!?");
+ avail = inf.getRemaining();
+ if ((flags & 8) != 0)
+ readDataDescr();
+
+ if (inf.getTotalIn() != csize
+ || inf.getTotalOut() != size)
+ throw new ZipException("size mismatch: "+csize+";"+size+" <-> "+inf.getTotalIn()+";"+inf.getTotalOut());
+ inf.reset();
+ finished = true;
}
- current = null;
- avail = 0;
+ break;
+
+ 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.
- *
- * @since 1.2
+ * Closes the zip file.
+ * @exception IOException if a i/o error occured.
*/
- public void close () throws IOException
+ public void close() throws IOException
{
- current = null;
- closed = true;
super.close();
+ crc = null;
+ entry = null;
}
- private ZipEntry current;
- private int current_flags;
- // Number of uncompressed bytes to be read.
- private int avail;
- // Number of bytes we can read from underlying stream.
- private int compressed_bytes;
- // Is this ZipInputStream closed? Set by the close() method.
- private boolean closed = false;
+ /**
+ * Creates a new zip entry for the given name. This is equivalent
+ * to new ZipEntry(name).
+ * @param name the name of the zip entry.
+ */
+ protected ZipEntry createZipEntry(String name)
+ {
+ return new ZipEntry(name);
+ }
}
-/* ZipOutputStream.java - Create a file in zip format
- Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+/* java.util.zip.ZipOutputStream
+ Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
-
+
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
exception statement from your version. */
package java.util.zip;
-
-import java.io.*;
-
-/* Written using on-line Java Platform 1.2 API Specification
- * and JCL book.
- * Believed complete and correct.
+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
+public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants
{
- public static final int STORED = 0;
- public static final int DEFLATED = 8;
-
- public void close () throws IOException
+ private Vector entries = new Vector();
+ private CRC32 crc = new CRC32();
+ private ZipEntry curEntry = null;
+
+ private int curMethod;
+ private int size;
+ private int offset = 0;
+
+ private byte[] zipComment = new byte[0];
+ private int defaultMethod = DEFLATED;
+
+ /**
+ * 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;
+
+ /**
+ * Compression method. This method doesn't compress at all.
+ */
+ public final static int STORED = 0;
+ /**
+ * Compression method. This method uses the Deflater.
+ */
+ public final static int DEFLATED = 8;
+
+ /**
+ * 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 ();
- out.close();
+ super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
}
- public void closeEntry () throws IOException
+ /**
+ * Set the zip file comment.
+ * @param comment the comment.
+ * @exception IllegalArgumentException if encoding of comment is
+ * longer than 0xffff bytes.
+ */
+ public void setComment(String comment)
{
- 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;
-
- 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");
-
- current.next = chain;
- chain = current;
- current = null;
+ byte[] commentBytes;
+ commentBytes = comment.getBytes();
+ if (commentBytes.length > 0xffff)
+ throw new IllegalArgumentException("Comment too long.");
+ zipComment = commentBytes;
}
-
- public void write (int bval) throws IOException
+
+ /**
+ * 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)
{
- if (current.method == STORED)
- {
- out.write(bval);
- }
- else
- super.write(bval);
- sum.update(bval);
- uncompressed_size += 1;
+ if (method != STORED && method != DEFLATED)
+ throw new IllegalArgumentException("Method not supported.");
+ defaultMethod = method;
}
- public void write (byte[] buf, int off, int len) throws IOException
+ /**
+ * 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)
{
- if (current.method == STORED)
- out.write(buf, off, len);
- else
- super.write(buf, off, len);
- sum.update(buf, off, len);
- uncompressed_size += len;
+ def.setLevel(level);
}
-
- public void finish () throws IOException
+
+ /**
+ * Write an unsigned short in little endian byte order.
+ */
+ private final void writeLeShort(int value) throws IOException
{
- if (current != null)
- closeEntry ();
-
- // Write the central directory.
- long offset = bytes_written;
- int count = 0;
- int bytes = 0;
- while (chain != null)
- {
- bytes += write_entry (chain, false);
- ++count;
- chain = chain.next;
- }
+ out.write(value & 0xff);
+ out.write((value >> 8) & 0xff);
+ }
- // Write the end of the central directory record.
- put4 (0x06054b50);
- // Disk number.
- put2 (0);
- // 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);
+ /**
+ * Write an int in little endian byte order.
+ */
+ private final void writeLeInt(int value) throws IOException
+ {
+ writeLeShort(value);
+ writeLeShort(value >> 16);
}
- // Helper for finish and putNextEntry.
- private int write_entry (ZipEntry entry, boolean is_local)
- throws IOException
+ /**
+ * Starts a new Zip entry. It automatically closes the previous
+ * entry if present. If the compression method is stored, the entry
+ * must have a valid size and crc, otherwise all elements (except
+ * name) are optional, but must be correct if present. If the time
+ * is not set in the entry, the current time is used.
+ * @param entry the entry.
+ * @exception IOException if an I/O error occured.
+ * @exception IllegalStateException if stream was finished
+ */
+ public void putNextEntry(ZipEntry entry) throws IOException
{
- int bytes = put4 (is_local ? 0x04034b50 : 0x02014b50);
- 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.
- // The actual values are given after the entry.
- bytes += put4 (0);
- bytes += put4 (0);
- bytes += put4 (0);
- }
- else
- {
- bytes += put4 ((int) (entry.getCrc()));
- bytes += put4 ((int) (entry.getCompressedSize()));
- bytes += put4 ((int) (entry.getSize()));
- }
+ if (entries == null)
+ throw new IllegalStateException("ZipOutputStream was finished");
- byte[] name = entry.name.getBytes("8859_1");
- bytes += put2 (name.length);
- bytes += put2 (entry.extra == null ? 0 : entry.extra.length);
+ int method = entry.getMethod();
+ int flags = 0;
+ if (method == -1)
+ method = defaultMethod;
- byte[] comment = null;
- if (! is_local)
+ if (method == STORED)
{
- if (entry.getComment() == null)
- bytes += put2 (0);
- else
+ if (entry.getCompressedSize() >= 0)
{
- comment = entry.getComment().getBytes("8859_1");
- bytes += put2 (comment.length);
+ if (entry.getSize() < 0)
+ entry.setSize(entry.getCompressedSize());
+ else if (entry.getSize() != entry.getCompressedSize())
+ throw new ZipException
+ ("Method STORED, but compressed size != size");
}
+ else
+ entry.setCompressedSize(entry.getSize());
- // Disk number start.
- bytes += put2 (0);
- // Internal file attributes.
- bytes += put2 (0);
- // External file attributes.
- bytes += put4 (0);
- // Relative offset of local header.
- bytes += put4 ((int) entry.relativeOffset);
+ 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)
+ {
+ if (entry.getCompressedSize() < 0
+ || entry.getSize() < 0 || entry.getCrc() < 0)
+ flags |= 8;
}
- out.write (name);
- bytes += name.length;
- if (entry.extra != null)
+ if (curEntry != null)
+ closeEntry();
+
+ if (entry.getTime() < 0)
+ entry.setTime(System.currentTimeMillis());
+
+ 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)
{
- out.write(entry.extra);
- bytes += entry.extra.length;
+ writeLeInt((int)entry.getCrc());
+ writeLeInt((int)entry.getCompressedSize());
+ writeLeInt((int)entry.getSize());
}
- if (comment != null)
+ else
{
- out.write(comment);
- bytes += comment.length;
+ writeLeInt(0);
+ writeLeInt(0);
+ writeLeInt(0);
}
-
- bytes_written += bytes;
- return bytes;
+ 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];
+ writeLeShort(name.length);
+ writeLeShort(extra.length);
+ 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;
}
- public void putNextEntry (ZipEntry entry) throws IOException
+ /**
+ * Closes the current entry.
+ * @exception IOException if an I/O error occured.
+ * @exception IllegalStateException if no entry is active.
+ */
+ public void closeEntry() throws IOException
{
- if (current != null)
- closeEntry ();
-
- if (entry.method < 0 )
- entry.method = method;
- if (entry.method == STORED)
+ if (curEntry == null)
+ throw new IllegalStateException("No open entry");
+
+ /* First finish the deflater, if appropriate */
+ if (curMethod == DEFLATED)
+ super.finish();
+
+ int csize = curMethod == DEFLATED ? def.getTotalOut() : size;
+
+ if (curEntry.getSize() < 0)
+ curEntry.setSize(size);
+ else if (curEntry.getSize() != size)
+ throw new ZipException("size was "+size
+ +", but I expected "+curEntry.getSize());
+
+ if (curEntry.getCompressedSize() < 0)
+ curEntry.setCompressedSize(csize);
+ else if (curEntry.getCompressedSize() != csize)
+ throw new ZipException("compressed size was "+csize
+ +", but I expected "+curEntry.getSize());
+
+ if (curEntry.getCrc() < 0)
+ curEntry.setCrc(crc.getValue());
+ else if (curEntry.getCrc() != crc.getValue())
+ throw new ZipException("crc was " + Long.toHexString(crc.getValue())
+ + ", but I expected "
+ + Long.toHexString(curEntry.getCrc()));
+
+ offset += csize;
+
+ /* Now write the data descriptor entry if needed. */
+ if (curMethod == DEFLATED && (curEntry.flags & 8) != 0)
{
- if (entry.getSize() == -1 || entry.getCrc() == -1)
- throw new ZipException ("required entry not set");
- // Just in case.
- entry.compressedSize = entry.getSize();
+ writeLeInt(EXTSIG);
+ writeLeInt((int)curEntry.getCrc());
+ writeLeInt((int)curEntry.getCompressedSize());
+ writeLeInt((int)curEntry.getSize());
+ offset += EXTHDR;
}
- 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 (level != Deflater.DEFAULT_COMPRESSION
- && (level < Deflater.NO_COMPRESSION
- || level > Deflater.BEST_COMPRESSION))
- throw new IllegalArgumentException ();
- this.level = level;
+ entries.addElement(curEntry);
+ curEntry = null;
}
- public void setMethod (int method)
+ /**
+ * 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
{
- if (method != DEFLATED && method != STORED)
- throw new IllegalArgumentException ();
- this.method = method;
- }
-
- public void setComment (String comment)
- {
- if (comment.length() > 65535)
- throw new IllegalArgumentException ();
- this.comment = comment;
- }
+ if (curEntry == null)
+ throw new IllegalStateException("No open entry.");
- public ZipOutputStream (OutputStream out)
- {
- super (out, new Deflater (Deflater.DEFAULT_COMPRESSION, true), 8192);
- sum = new CRC32 ();
- }
+ switch (curMethod)
+ {
+ case DEFLATED:
+ super.write(b, off, len);
+ break;
+
+ case STORED:
+ out.write(b, off, len);
+ break;
+ }
- private int put2 (int i) throws IOException
- {
- out.write (i);
- out.write (i >> 8);
- return 2;
+ crc.update(b, off, len);
+ size += len;
}
- private int put4 (int i) throws IOException
+ /**
+ * Finishes the stream. This will write the central directory at the
+ * end of the zip file and flush the stream.
+ * @exception IOException if an I/O error occured.
+ */
+ public void finish() throws IOException
{
- out.write (i);
- out.write (i >> 8);
- out.write (i >> 16);
- out.write (i >> 24);
- return 4;
- }
+ if (entries == null)
+ return;
+ if (curEntry != null)
+ closeEntry();
+
+ int numEntries = 0;
+ int sizeEntries = 0;
+
+ 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;
+ }
- private int put_version () throws IOException
- {
- // FIXME: for now we assume Unix, and we ignore the version
- // number.
- return put2 (3 << 8);
+ writeLeInt(ENDSIG);
+ 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;
}
-
- // The entry we are currently writing, or null if we've called
- // closeEntry.
- private ZipEntry current;
- // The chain of entries which have been written to this file.
- private ZipEntry chain;
-
- private int method = DEFLATED;
- private int level = Deflater.DEFAULT_COMPRESSION;
- private String comment = "";
- private long bytes_written;
-
- private int uncompressed_size;
-
- /** The checksum object. */
- private Checksum sum;
}
// 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.
// Just ignore errors.
deflateReset (s);
flush_flag = 0;
+ is_finished = false;
}
void
// 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.
z_streamp s = (z_streamp) zstream;
// Just ignore errors.
inflateReset (s);
+ is_finished = false;
+ dict_needed = false;
}
void