diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 1b97fe772d7..32955b51243 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,18 @@ +2002-06-15 Tom Tromey + + * 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 * java/lang/natString.cc (init): Handle case where DONT_COPY is diff --git a/libjava/java/util/zip/InflaterInputStream.java b/libjava/java/util/zip/InflaterInputStream.java index dc20e745956..5aac73d7e5a 100644 --- a/libjava/java/util/zip/InflaterInputStream.java +++ b/libjava/java/util/zip/InflaterInputStream.java @@ -1,5 +1,5 @@ /* 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. @@ -92,23 +92,30 @@ public class InflaterInputStream extends FilterInputStream 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; } diff --git a/libjava/java/util/zip/ZipConstants.java b/libjava/java/util/zip/ZipConstants.java index 0583cb31360..3d6b7447c92 100644 --- a/libjava/java/util/zip/ZipConstants.java +++ b/libjava/java/util/zip/ZipConstants.java @@ -1,5 +1,5 @@ -/* 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. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify 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 @@ -37,19 +37,61 @@ exception statement from your version. */ package java.util.zip; -/** - * Some constants used in the zip package. - *

- * 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); - // Size in bytes of the "end of central directory" record, with signature. - public static final int END_CENTRAL_DIR_SIZE = 22; + 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; + + /* 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; } + diff --git a/libjava/java/util/zip/ZipEntry.java b/libjava/java/util/zip/ZipEntry.java index cbf62af0091..5a43b1f5a2e 100644 --- a/libjava/java/util/zip/ZipEntry.java +++ b/libjava/java/util/zip/ZipEntry.java @@ -1,5 +1,5 @@ -/* 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. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify 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 @@ -36,201 +36,361 @@ obligated to do so. If you do not wish to do so, delete this 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; + private static int KNOWN_SIZE = 1; + private static int KNOWN_CSIZE = 2; + private static int KNOWN_CRC = 4; + private static int KNOWN_TIME = 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; + private static Calendar cal = Calendar.getInstance(); - ZipEntry next; + 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; - public ZipEntry (String name) + 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 zip entry with the given name. + * @param name the name. May include directory components separated + * by '/'. + */ + public ZipEntry(String name) { - if (name.length() > 65535) - throw new IllegalArgumentException (); + if (name == null) + throw new NullPointerException(); this.name = name; } /** - * 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 copy of the given zip entry. + * @param e the entry to copy. */ - public ZipEntry (ZipEntry ent) + public ZipEntry(ZipEntry e) { - 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; - } - - /** - * Creates a clone of this ZipEntry. Calls new ZipEntry (this) - * and creates a clone of the contents of the extra byte array field. - * - * @since 1.2 - */ - public Object clone () - { - // 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; } - - public long getCrc () { return crc; } - - public byte[] getExtra() { return extra; } - - public int getMethod () { return method; } - - public String getName () { return name; } - - public long getSize () { return size; } - - public long getTime () { return time; } - - public boolean isDirectory () + void setDOSTime(int dostime) { - if (name != null) + 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 { - int nlen = name.length(); - if (nlen > 0 && name.charAt(nlen-1) == '/') - return true; + 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; } - return false; } - public void setComment (String comment) + int getDOSTime() { - if (comment != null && comment.length() > 65535) - throw new IllegalArgumentException (); + 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; + } + } + + /** + * 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(); + } + } + + /** + * Returns the entry name. The path components in the entry are + * always separated by slashes ('/'). + */ + 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; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * Sets the size of the compressed data. + * @exception IllegalArgumentException if size is not in 0..0xffffffffL + */ + public void setCompressedSize(long csize) + { + if ((csize & 0xffffffff00000000L) != 0) + throw new IllegalArgumentException(); + this.compressedSize = (int) csize; + this.known |= KNOWN_CSIZE; + } + + /** + * Gets the size of the compressed data. + * @return the size or -1 if unknown. + */ + public long getCompressedSize() + { + return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L; + } + + /** + * Sets the crc of the uncompressed data. + * @exception IllegalArgumentException if crc is not in 0..0xffffffffL + */ + public void setCrc(long crc) + { + if ((crc & 0xffffffff00000000L) != 0) + throw new IllegalArgumentException(); + this.crc = (int) crc; + this.known |= KNOWN_CRC; + } + + /** + * Gets the crc of the uncompressed data. + * @return the crc or -1 if unknown. + */ + public long getCrc() + { + return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L; + } + + /** + * 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 != ZipOutputStream.STORED + && method != ZipOutputStream.DEFLATED) + throw new IllegalArgumentException(); + this.method = (short) method; + } + + /** + * Gets the compression method. + * @return the compression method or -1 if unknown. + */ + public int getMethod() + { + return method; + } + + /** + * Sets the extra data. + * @exception IllegalArgumentException if extra is longer than 0xffff bytes. + */ + public void setExtra(byte[] extra) + { + 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; + } + } + + /** + * Gets the extra data. + * @return the extra data or null if not set. + */ + public byte[] getExtra() + { + return extra; + } + + /** + * Sets the entry comment. + * @exception IllegalArgumentException if comment is longer than 0xffff. + */ + public void setComment(String comment) + { + if (comment.length() > 0xffff) + throw new IllegalArgumentException(); this.comment = comment; } - + /** - * Sets the compressedSize of this ZipEntry. - * The new size must be between 0 and 0xffffffffL. - * @since 1.2 + * Gets the comment. + * @return the comment or null if not set. */ - public void setCompressedSize (long compressedSize) + public String getComment() { - if (compressedSize < 0 || compressedSize > 0xffffffffL) - throw new IllegalArgumentException (); - this.compressedSize = compressedSize; + return comment; } - public void setCrc (long crc) - { - if (crc < 0 || crc > 0xffffffffL) - throw new IllegalArgumentException (); - this.crc = crc; - } - - public void setExtra (byte[] extra) - { - if (extra != null && extra.length > 65535) - throw new IllegalArgumentException (); - this.extra = extra; - } - - public void setMethod (int method) - { - if (method != DEFLATED && method != STORED) - throw new IllegalArgumentException (); - this.method = method; - } - - public void setSize (long size) - { - if (size < 0 || size > 0xffffffffL) - throw new IllegalArgumentException (); - this.size = size; - } - - public void setTime (long time) - { - this.time = time; - } - - 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}; - - /** Convert a DOS-style type value to milliseconds since 1970. */ - static long timeFromDOS (int date, int time) - { - 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. */ - - // Guard against invalid or missing date causing IndexOutOfBoundsException. - if (mon < 0 || mon > 11) - return -1; - - long mtime = (((hrs * 60) + min) * 60 + sec) * 1000; - - // 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; - } - - public String toString () { return name; } - /** - * Returns the hashcode of the name of this ZipEntry. + * Gets true, if the entry is a directory. This is solely + * determined by the name, a trailing slash '/' marks a directory. */ - public int hashCode () { return name.hashCode (); } + public boolean isDirectory() + { + int nlen = name.length(); + return nlen > 0 && name.charAt(nlen - 1) == '/'; + } + + /** + * Gets the string representation of this ZipEntry. This is just + * the name as returned by getName(). + */ + public String toString() + { + return name; + } + + /** + * 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(); + } } diff --git a/libjava/java/util/zip/ZipFile.java b/libjava/java/util/zip/ZipFile.java index 941f3b1202e..6dbed7cdaee 100644 --- a/libjava/java/util/zip/ZipFile.java +++ b/libjava/java/util/zip/ZipFile.java @@ -1,5 +1,5 @@ -/* 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. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify 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 @@ -36,233 +36,422 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ 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; -import java.io.*; - -/* Written using on-line Java Platform 1.2 API Specification - * and JCL book. - * Believed complete and correct. +/** + * 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(); + throw new IllegalArgumentException("OPEN_DELETE mode not supported yet in java.util.zip.ZipFile"); } - else - { - delete_on_close = null; - } - - file = new RandomAccessFile(f, "r"); - name = f.getName(); - readDirectory (); + this.raf = new RandomAccessFile(file, "r"); + this.name = file.getName(); + readEntries(); } - void readDirectory () throws IOException + /** + * 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 { - 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 { - 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 (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[] 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(); + synchronized (raf) + { + raf.close(); + } } + /** + * Returns an enumeration of all Zip entries in this Zip file. + */ + public Enumeration entries() + { + if (entries == null) + throw new IllegalStateException("ZipFile has closed"); + return new ZipEntryEnumeration(entries); + } + + private int getEntryIndex(String name) + { + for (int i = 0; i < entries.length; i++) + if (name.equals(entries[i].getName())) + return i; + return -1; + } + + /** + * 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) { - for (ZipEntry entry = entries; entry != null; entry = entry.next) - { - if (name.equals(entry.getName())) - return entry; - } - return null; + 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"); - /* 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); + /* skip version and flags */ + if (raf.skipBytes(LOCHOW - LOCVER) != LOCHOW - LOCVER) + throw new EOFException(); - file.readFully(buffer); + if (entry.getMethod() != readLeShort()) + throw new ZipException("Compression method mismatch"); - 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; + /* 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; + } } - public String getName () + /** + * 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; + 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() { return name; } /** - * Returns the number of entries in this ZipFile. - * @exception IllegalStateException if the ZipFile has been closed. - * - * @since 1.2 + * Returns the number of entries in this zip file. */ - public int size () + public int size() { - if (entries == null) - throw new IllegalStateException("ZipFile already closed"); - else - return numEntries; + try + { + return entries.length; + } + catch (NullPointerException ex) + { + throw new IllegalStateException("ZipFile has closed"); + } + } + + private static class ZipEntryEnumeration implements Enumeration + { + 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(); + } + } } - protected void finalize () throws IOException + private static class PartialInputStream extends InputStream { - close(); - } + RandomAccessFile raf; + long filepos, end; - private int readu2 () 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); - } - - private int read4 () throws IOException - { - 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); - } - - ZipEntry entries; - int numEntries; - RandomAccessFile file; - String name; - /** 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; + } -} + 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(); + } + } -final class ZipEnumeration implements java.util.Enumeration -{ - ZipEntry entry; + 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; + } + } - ZipEnumeration (ZipFile zfile) - { - entry = zfile.entries; - } - - public boolean hasMoreElements () - { - return entry != null; - } - - public Object nextElement () - { - ZipEntry cur = entry; - if (cur == null) - throw new java.util.NoSuchElementException(); - entry = cur.next; - return cur; + public long skip(long amount) + { + if (amount < 0) + throw new IllegalArgumentException(); + if (amount > end - filepos) + amount = end - filepos; + filepos += amount; + return amount; + } } } diff --git a/libjava/java/util/zip/ZipInputStream.java b/libjava/java/util/zip/ZipInputStream.java index 8fb63ef2cac..63153b649c9 100644 --- a/libjava/java/util/zip/ZipInputStream.java +++ b/libjava/java/util/zip/ZipInputStream.java @@ -1,5 +1,5 @@ -/* 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. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify 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 @@ -36,259 +36,327 @@ obligated to do so. If you do not wish to do so, delete this 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 */ - -/* - * 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. - */ - -// We do not calculate the CRC and compare it with the specified value; -// we probably should. FIXME. - - public class ZipInputStream extends InflaterInputStream implements ZipConstants { - public ZipInputStream (InputStream in) - { - super (in, new Inflater (true)); - } + private CRC32 crc = new CRC32(); + private ZipEntry entry = null; - public ZipEntry getNextEntry () 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') - { - 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(); - } - if (code == '\005') - { - 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(); - } - 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) - { - byte[] bextra = new byte[extraLength]; - readFully(bextra); - entry.extra = bextra; - } - 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 - // the underlying input stream. This lets us avoid having to push - // back data. - protected void fill () 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); - } - } + private int csize; + private int size; + private int method; + private int flags; + private int avail; /** - * Creates a new ZipEntry with the given name. - * Used by ZipInputStream when normally new ZipEntry (name) - * would be called. This gives subclasses such as JarInputStream a change - * to override this method and add aditional information to the ZipEntry - * (subclass). + * Creates a new Zip input stream, reading a zip archive. */ - protected ZipEntry createZipEntry (String name) + public ZipInputStream(InputStream in) { - return new ZipEntry (name); + super(in, new Inflater(true)); } - public int read (byte[] b, int off, int len) throws IOException + private void fillBuf() 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) + avail = len = in.read(buf, 0, buf.length); + } + + private int readBuf(byte[] out, int offset, int length) throws IOException + { + if (avail <= 0) { - inf.reset(); - count = -1; + fillBuf(); + if (avail <= 0) + return -1; } - else - avail -= count; - return count; + if (length > avail) + length = avail; + System.arraycopy(buf, len - avail, out, offset, length); + avail -= length; + return length; } - - public long skip (long n) 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; - } - - /** - * Returns 0 if the ZipInputStream is closed and 1 otherwise. - * - * @since 1.2 - */ - public int available() - { - return closed ? 0 : 1; - } - - private void readFully (byte[] b) throws IOException + + private void readFully(byte[] out) throws IOException { int off = 0; - int len = b.length; + int len = out.length; while (len > 0) { - int count = in.read(b, off, len); - if (count <= 0) - throw new EOFException(".zip archive ended prematurely"); + int count = readBuf(out, off, len); + if (count == -1) + throw new EOFException(); off += count; len -= count; } } - - private int readu2 () throws IOException + + private final int readLeByte() 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); - } - - private int read4 () 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); - } - - public void closeEntry () throws IOException - { - if (current != null) + if (avail <= 0) { - if (avail > 0) - skip (avail); - if ((current_flags & 8) != 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"); - } - current = null; - avail = 0; + fillBuf(); + if (avail <= 0) + throw new ZipException("EOF in header"); } + return buf[len - avail--] & 0xff; } /** - * Closes this InflaterInputStream. - * - * @since 1.2 + * Read an unsigned short in little endian byte order. */ - public void close () throws IOException + private final int readLeShort() throws IOException { - current = null; - closed = true; - super.close(); + return readLeByte() | (readLeByte() << 8); } - 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; + /** + * Read an int in little endian byte order. + */ + private final int readLeInt() throws IOException + { + return readLeShort() | (readLeShort() << 16); + } + + /** + * 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 (crc == null) + throw new IllegalStateException("Closed."); + if (entry != null) + closeEntry(); + + int header = readLeInt(); + if (header == CENSIG) + { + /* Central Header reached. */ + close(); + return null; + } + 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; + } + + private void readDataDescr() throws IOException + { + 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); + } + + /** + * Closes the current zip entry and moves to the next one. + */ + public void closeEntry() throws IOException + { + 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(); + } + + 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; + } + + public int available() throws IOException + { + return entry != null ? 1 : 0; + } + + /** + * 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 + { + byte[] b = new byte[1]; + if (read(b, 0, 1) <= 0) + return -1; + return b[0] & 0xff; + } + + /** + * 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 (crc == null) + throw new IllegalStateException("Closed."); + if (entry == null) + return -1; + boolean finished = false; + switch (method) + { + case ZipOutputStream.DEFLATED: + len = super.read(b, off, len); + if (len < 0) + { + 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; + } + 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 the zip file. + * @exception IOException if a i/o error occured. + */ + public void close() throws IOException + { + super.close(); + crc = null; + entry = null; + } + + /** + * 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); + } } diff --git a/libjava/java/util/zip/ZipOutputStream.java b/libjava/java/util/zip/ZipOutputStream.java index dec71fb1276..e4fb864a955 100644 --- a/libjava/java/util/zip/ZipOutputStream.java +++ b/libjava/java/util/zip/ZipOutputStream.java @@ -1,5 +1,5 @@ -/* 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. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify 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 @@ -36,286 +36,362 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ package java.util.zip; +import java.io.OutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Vector; +import java.util.Enumeration; -import java.io.*; - -/* Written using on-line Java Platform 1.2 API Specification - * and JCL book. - * Believed complete and correct. +/** + * 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; + private Vector entries = new Vector(); + private CRC32 crc = new CRC32(); + private ZipEntry curEntry = null; - public void close () throws IOException + 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; + } + + /** + * 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 (method != STORED && method != DEFLATED) + throw new IllegalArgumentException("Method not supported."); + defaultMethod = method; } - public void write (int bval) 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(bval); - } - else - super.write(bval); - sum.update(bval); - uncompressed_size += 1; + def.setLevel(level); + } + + /** + * Write an unsigned short in little endian byte order. + */ + private final void writeLeShort(int value) throws IOException + { + out.write(value & 0xff); + out.write((value >> 8) & 0xff); } - 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) - out.write(buf, off, len); - else - super.write(buf, off, len); - sum.update(buf, off, len); - uncompressed_size += len; + writeLeShort(value); + writeLeShort(value >> 16); } - public void finish () 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 { - if (current != null) - closeEntry (); + if (entries == null) + throw new IllegalStateException("ZipOutputStream was finished"); - // Write the central directory. - long offset = bytes_written; - int count = 0; - int bytes = 0; - while (chain != null) + int method = entry.getMethod(); + int flags = 0; + if (method == -1) + method = defaultMethod; + + if (method == STORED) { - bytes += write_entry (chain, false); - ++count; - chain = chain.next; - } - - // 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); - } - - // Helper for finish and putNextEntry. - private int write_entry (ZipEntry entry, boolean is_local) - 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())); - } - - byte[] name = entry.name.getBytes("8859_1"); - bytes += put2 (name.length); - bytes += put2 (entry.extra == null ? 0 : entry.extra.length); - - byte[] comment = null; - if (! is_local) - { - 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 { + writeLeInt(0); + writeLeInt(0); + writeLeInt(0); + } + 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; + } + + /** + * 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 (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) + { + writeLeInt(EXTSIG); + writeLeInt((int)curEntry.getCrc()); + writeLeInt((int)curEntry.getCompressedSize()); + writeLeInt((int)curEntry.getSize()); + offset += EXTHDR; + } + + entries.addElement(curEntry); + curEntry = null; + } + + /** + * 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 (curEntry == null) + throw new IllegalStateException("No open entry."); + + switch (curMethod) + { + case DEFLATED: + super.write(b, off, len); + break; + + case STORED: + out.write(b, off, len); + break; + } + + crc.update(b, off, len); + size += len; + } + + /** + * 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 + { + 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); - bytes += comment.length; + numEntries++; + sizeEntries += CENHDR + name.length + extra.length + comment.length; } - bytes_written += bytes; - return bytes; + 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; } - - public void putNextEntry (ZipEntry entry) throws IOException - { - if (current != null) - closeEntry (); - - if (entry.method < 0 ) - entry.method = method; - if (entry.method == STORED) - { - if (entry.getSize() == -1 || entry.getCrc() == -1) - 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 (level != Deflater.DEFAULT_COMPRESSION - && (level < Deflater.NO_COMPRESSION - || level > Deflater.BEST_COMPRESSION)) - throw new IllegalArgumentException (); - this.level = level; - } - - public void setMethod (int method) - { - 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; - } - - public ZipOutputStream (OutputStream out) - { - super (out, new Deflater (Deflater.DEFAULT_COMPRESSION, true), 8192); - sum = new CRC32 (); - } - - private int put2 (int i) throws IOException - { - out.write (i); - out.write (i >> 8); - return 2; - } - - private int put4 (int i) throws IOException - { - out.write (i); - out.write (i >> 8); - out.write (i >> 16); - out.write (i >> 24); - return 4; - } - - private int put_version () throws IOException - { - // 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. - 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; } diff --git a/libjava/java/util/zip/natDeflater.cc b/libjava/java/util/zip/natDeflater.cc index 934d870b0a7..09411439541 100644 --- a/libjava/java/util/zip/natDeflater.cc +++ b/libjava/java/util/zip/natDeflater.cc @@ -1,6 +1,6 @@ // 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. @@ -125,6 +125,7 @@ java::util::zip::Deflater::reset () // Just ignore errors. deflateReset (s); flush_flag = 0; + is_finished = false; } void diff --git a/libjava/java/util/zip/natInflater.cc b/libjava/java/util/zip/natInflater.cc index 0d1529bf044..0568b5e85d3 100644 --- a/libjava/java/util/zip/natInflater.cc +++ b/libjava/java/util/zip/natInflater.cc @@ -1,6 +1,6 @@ // 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. @@ -149,6 +149,8 @@ java::util::zip::Inflater::reset () z_streamp s = (z_streamp) zstream; // Just ignore errors. inflateReset (s); + is_finished = false; + dict_needed = false; } void