diff --git a/jdk/make/java/zip/FILES_c.gmk b/jdk/make/java/zip/FILES_c.gmk index 6426cea2ced..f88386c8ee9 100644 --- a/jdk/make/java/zip/FILES_c.gmk +++ b/jdk/make/java/zip/FILES_c.gmk @@ -29,7 +29,6 @@ FILES_c = \ Deflater.c \ Inflater.c \ ZipFile.c \ - ZipEntry.c \ zadler32.c \ zcrc32.c \ deflate.c \ diff --git a/jdk/make/java/zip/mapfile-vers b/jdk/make/java/zip/mapfile-vers index 443ea67c83e..096fa35e434 100644 --- a/jdk/make/java/zip/mapfile-vers +++ b/jdk/make/java/zip/mapfile-vers @@ -50,15 +50,17 @@ SUNWprivate_1.1 { Java_java_util_zip_Inflater_initIDs; Java_java_util_zip_Inflater_reset; Java_java_util_zip_Inflater_setDictionary; - Java_java_util_zip_ZipEntry_initFields; - Java_java_util_zip_ZipEntry_initIDs; Java_java_util_zip_ZipFile_close; Java_java_util_zip_ZipFile_freeEntry; - Java_java_util_zip_ZipFile_getCSize; Java_java_util_zip_ZipFile_getEntry; - Java_java_util_zip_ZipFile_getMethod; + Java_java_util_zip_ZipFile_getEntryBytes; + Java_java_util_zip_ZipFile_getEntryCrc; + Java_java_util_zip_ZipFile_getEntryCSize; + Java_java_util_zip_ZipFile_getEntryFlag; + Java_java_util_zip_ZipFile_getEntryMethod; + Java_java_util_zip_ZipFile_getEntrySize; + Java_java_util_zip_ZipFile_getEntryTime; Java_java_util_zip_ZipFile_getNextEntry; - Java_java_util_zip_ZipFile_getSize; Java_java_util_zip_ZipFile_getZipMessage; Java_java_util_zip_ZipFile_getTotal; Java_java_util_zip_ZipFile_initIDs; diff --git a/jdk/make/java/zip/reorder-i586 b/jdk/make/java/zip/reorder-i586 index d1af81c9d7a..d9e90ead435 100644 --- a/jdk/make/java/zip/reorder-i586 +++ b/jdk/make/java/zip/reorder-i586 @@ -20,12 +20,14 @@ text: .text%Java_java_util_zip_ZipFile_initIDs; text: .text%Java_java_util_zip_ZipFile_open; text: .text%Java_java_util_zip_ZipFile_getTotal; text: .text%Java_java_util_zip_ZipFile_getEntry; -text: .text%Java_java_util_zip_ZipEntry_initIDs; -text: .text%Java_java_util_zip_ZipEntry_initFields; text: .text%Java_java_util_zip_ZipFile_freeEntry; -text: .text%Java_java_util_zip_ZipFile_getCSize; -text: .text%Java_java_util_zip_ZipFile_getSize; -text: .text%Java_java_util_zip_ZipFile_getMethod; +text: .text%Java_java_util_zip_ZipFile_getEntryTime; +text: .text%Java_java_util_zip_ZipFile_getEntryCrc; +text: .text%Java_java_util_zip_ZipFile_getEntryCSize; +text: .text%Java_java_util_zip_ZipFile_getEntrySize; +text: .text%Java_java_util_zip_ZipFile_getEntryFlag; +text: .text%Java_java_util_zip_ZipFile_getEntryMethod; +text: .text%Java_java_util_zip_ZipFile_getEntryBytes; text: .text%Java_java_util_zip_Inflater_initIDs; text: .text%Java_java_util_zip_Inflater_init; text: .text%inflateInit2_; diff --git a/jdk/make/java/zip/reorder-sparc b/jdk/make/java/zip/reorder-sparc index b004b3c7d0c..82cc81b6322 100644 --- a/jdk/make/java/zip/reorder-sparc +++ b/jdk/make/java/zip/reorder-sparc @@ -19,12 +19,14 @@ text: .text%Java_java_util_zip_ZipFile_initIDs; text: .text%Java_java_util_zip_ZipFile_open; text: .text%Java_java_util_zip_ZipFile_getTotal; text: .text%Java_java_util_zip_ZipFile_getEntry; -text: .text%Java_java_util_zip_ZipEntry_initIDs; -text: .text%Java_java_util_zip_ZipEntry_initFields; text: .text%Java_java_util_zip_ZipFile_freeEntry; -text: .text%Java_java_util_zip_ZipFile_getCSize; -text: .text%Java_java_util_zip_ZipFile_getSize; -text: .text%Java_java_util_zip_ZipFile_getMethod; +text: .text%Java_java_util_zip_ZipFile_getEntryTime; +text: .text%Java_java_util_zip_ZipFile_getEntryCrc; +text: .text%Java_java_util_zip_ZipFile_getEntryCSize; +text: .text%Java_java_util_zip_ZipFile_getEntrySize; +text: .text%Java_java_util_zip_ZipFile_getEntryFlag; +text: .text%Java_java_util_zip_ZipFile_getEntryMethod; +text: .text%Java_java_util_zip_ZipFile_getEntryBytes; text: .text%Java_java_util_zip_Inflater_initIDs; text: .text%Java_java_util_zip_Inflater_init; text: .text%inflateInit2_; diff --git a/jdk/make/java/zip/reorder-sparcv9 b/jdk/make/java/zip/reorder-sparcv9 index e1061f8ebae..a2b0f467df0 100644 --- a/jdk/make/java/zip/reorder-sparcv9 +++ b/jdk/make/java/zip/reorder-sparcv9 @@ -20,12 +20,14 @@ text: .text%Java_java_util_zip_ZipFile_initIDs; text: .text%Java_java_util_zip_ZipFile_open; text: .text%Java_java_util_zip_ZipFile_getTotal; text: .text%Java_java_util_zip_ZipFile_getEntry; -text: .text%Java_java_util_zip_ZipEntry_initIDs; -text: .text%Java_java_util_zip_ZipEntry_initFields; text: .text%Java_java_util_zip_ZipFile_freeEntry; -text: .text%Java_java_util_zip_ZipFile_getCSize; -text: .text%Java_java_util_zip_ZipFile_getSize; -text: .text%Java_java_util_zip_ZipFile_getMethod; +text: .text%Java_java_util_zip_ZipFile_getEntryTime; +text: .text%Java_java_util_zip_ZipFile_getEntryCrc; +text: .text%Java_java_util_zip_ZipFile_getEntryCSize; +text: .text%Java_java_util_zip_ZipFile_getEntrySize; +text: .text%Java_java_util_zip_ZipFile_getEntryFlag; +text: .text%Java_java_util_zip_ZipFile_getEntryMethod; +text: .text%Java_java_util_zip_ZipFile_getEntryBytes; text: .text%Java_java_util_zip_Inflater_initIDs; text: .text%Java_java_util_zip_Inflater_init; text: .text%inflateInit2_; diff --git a/jdk/src/share/classes/java/util/zip/ZipCoder.java b/jdk/src/share/classes/java/util/zip/ZipCoder.java new file mode 100644 index 00000000000..6442bea819d --- /dev/null +++ b/jdk/src/share/classes/java/util/zip/ZipCoder.java @@ -0,0 +1,139 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package java.util.zip; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Arrays; + +/** + * Utility class for zipfile name and comment decoding and encoding + */ + +final class ZipCoder { + + String toString(byte[] ba, int length) { + CharsetDecoder cd = decoder().reset(); + int len = (int)(length * cd.maxCharsPerByte()); + char[] ca = new char[len]; + if (len == 0) + return new String(ca); + ByteBuffer bb = ByteBuffer.wrap(ba, 0, length); + CharBuffer cb = CharBuffer.wrap(ca); + CoderResult cr = cd.decode(bb, cb, true); + if (!cr.isUnderflow()) + throw new IllegalArgumentException(cr.toString()); + cr = cd.flush(cb); + if (!cr.isUnderflow()) + throw new IllegalArgumentException(cr.toString()); + return new String(ca, 0, cb.position()); + } + + String toString(byte[] ba) { + return toString(ba, ba.length); + } + + byte[] getBytes(String s) { + CharsetEncoder ce = encoder().reset(); + char[] ca = s.toCharArray(); + int len = (int)(ca.length * ce.maxBytesPerChar()); + byte[] ba = new byte[len]; + if (len == 0) + return ba; + ByteBuffer bb = ByteBuffer.wrap(ba); + CharBuffer cb = CharBuffer.wrap(ca); + CoderResult cr = ce.encode(cb, bb, true); + if (!cr.isUnderflow()) + throw new IllegalArgumentException(cr.toString()); + cr = ce.flush(bb); + if (!cr.isUnderflow()) + throw new IllegalArgumentException(cr.toString()); + if (bb.position() == ba.length) // defensive copy? + return ba; + else + return Arrays.copyOf(ba, bb.position()); + } + + // assume invoked only if "this" is not utf8 + byte[] getBytesUTF8(String s) { + if (isutf8) + return getBytes(s); + if (utf8 == null) + utf8 = new ZipCoder(Charset.forName("UTF-8")); + return utf8.getBytes(s); + } + + + String toStringUTF8(byte[] ba, int len) { + if (isutf8) + return toString(ba, len); + if (utf8 == null) + utf8 = new ZipCoder(Charset.forName("UTF-8")); + return utf8.toString(ba, len); + } + + boolean isUTF8() { + return isutf8; + } + + private Charset cs; + private CharsetDecoder dec; + private CharsetEncoder enc; + private boolean isutf8; + private ZipCoder utf8; + + private ZipCoder(Charset cs) { + this.cs = cs; + this.isutf8 = cs.name().equals("UTF-8"); + } + + static ZipCoder get(Charset charset) { + return new ZipCoder(charset); + } + + private CharsetDecoder decoder() { + if (dec == null) { + dec = cs.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + } + return dec; + } + + private CharsetEncoder encoder() { + if (enc == null) { + enc = cs.newEncoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + } + return enc; + } +} diff --git a/jdk/src/share/classes/java/util/zip/ZipConstants64.java b/jdk/src/share/classes/java/util/zip/ZipConstants64.java index 1bf3b1841b4..b844b2cccee 100644 --- a/jdk/src/share/classes/java/util/zip/ZipConstants64.java +++ b/jdk/src/share/classes/java/util/zip/ZipConstants64.java @@ -73,5 +73,12 @@ class ZipConstants64 { static final int ZIP64_EXTSIZ = 8; // compressed size, 8-byte static final int ZIP64_EXTLEN = 16; // uncompressed size, 8-byte + /* + * Language encoding flag EFS + */ + static final int EFS = 0x800; // If this bit is set the filename and + // comment fields for this file must be + // encoded using UTF-8. + private ZipConstants64() {} } diff --git a/jdk/src/share/classes/java/util/zip/ZipEntry.java b/jdk/src/share/classes/java/util/zip/ZipEntry.java index 6c16a9adb15..96e3b4d828e 100644 --- a/jdk/src/share/classes/java/util/zip/ZipEntry.java +++ b/jdk/src/share/classes/java/util/zip/ZipEntry.java @@ -40,6 +40,7 @@ class ZipEntry implements ZipConstants, Cloneable { long size = -1; // uncompressed size of entry data long csize = -1; // compressed size of entry data int method = -1; // compression method + int flag = 0; // general purpose flag byte[] extra; // optional extra field data for entry String comment; // optional comment string for entry @@ -53,13 +54,6 @@ class ZipEntry implements ZipConstants, Cloneable { */ public static final int DEFLATED = 8; - static { - /* Zip library is loaded from System.initializeSystemClass */ - initIDs(); - } - - private static native void initIDs(); - /** * Creates a new zip entry with the specified name. * @@ -90,28 +84,15 @@ class ZipEntry implements ZipConstants, Cloneable { size = e.size; csize = e.csize; method = e.method; + flag = e.flag; extra = e.extra; comment = e.comment; } /* - * Creates a new zip entry for the given name with fields initialized - * from the specified jzentry data. + * Creates a new un-initialized zip entry */ - ZipEntry(String name, long jzentry) { - this.name = name; - initFields(jzentry); - } - - private native void initFields(long jzentry); - - /* - * Creates a new zip entry with fields initialized from the specified - * jzentry data. - */ - ZipEntry(long jzentry) { - initFields(jzentry); - } + ZipEntry() {} /** * Returns the name of the entry. @@ -258,16 +239,16 @@ class ZipEntry implements ZipConstants, Cloneable { /** * Sets the optional comment string for the entry. + * + *

ZIP entry comments have maximum length of 0xffff. If the length of the + * specified comment string is greater than 0xFFFF bytes after encoding, only + * the first 0xFFFF bytes are output to the ZIP file entry. + * * @param comment the comment string - * @exception IllegalArgumentException if the length of the specified - * comment string is greater than 0xFFFF bytes + * * @see #getComment() */ public void setComment(String comment) { - if (comment != null && comment.length() > 0xffff/3 - && ZipOutputStream.getUTF8Length(comment) > 0xffff) { - throw new IllegalArgumentException("invalid entry comment length"); - } this.comment = comment; } diff --git a/jdk/src/share/classes/java/util/zip/ZipFile.java b/jdk/src/share/classes/java/util/zip/ZipFile.java index 37e92ddecb7..8ee0bc2eb57 100644 --- a/jdk/src/share/classes/java/util/zip/ZipFile.java +++ b/jdk/src/share/classes/java/util/zip/ZipFile.java @@ -29,9 +29,11 @@ import java.io.InputStream; import java.io.IOException; import java.io.EOFException; import java.io.File; +import java.nio.charset.Charset; import java.util.Vector; import java.util.Enumeration; import java.util.NoSuchElementException; +import static java.util.zip.ZipConstants64.*; /** * This class is used to read entries from a zip file. @@ -76,16 +78,19 @@ class ZipFile implements ZipConstants { /** * Opens a zip file for reading. * - *

First, if there is a security - * manager, its checkRead method - * is called with the name argument - * as its argument to ensure the read is allowed. + *

First, if there is a security manager, its checkRead + * method is called with the name argument as its argument + * to ensure the read is allowed. + * + *

The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names and comments. * * @param name the name of the zip file * @throws ZipException if a ZIP format error has occurred * @throws IOException if an I/O error has occurred * @throws SecurityException if a security manager exists and its * checkRead method doesn't allow read access to the file. + * * @see SecurityManager#checkRead(java.lang.String) */ public ZipFile(String name) throws IOException { @@ -101,6 +106,9 @@ class ZipFile implements ZipConstants { * method is called with the name argument as its argument to * ensure the read is allowed. * + *

The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names and comments + * * @param file the ZIP file to be opened for reading * @param mode the mode in which the file is to be opened * @throws ZipException if a ZIP format error has occurred @@ -115,6 +123,59 @@ class ZipFile implements ZipConstants { * @since 1.3 */ public ZipFile(File file, int mode) throws IOException { + this(file, mode, Charset.forName("UTF-8")); + } + + /** + * Opens a ZIP file for reading given the specified File object. + * + *

The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names and comments. + * + * @param file the ZIP file to be opened for reading + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + */ + public ZipFile(File file) throws ZipException, IOException { + this(file, OPEN_READ); + } + + private ZipCoder zc; + + /** + * Opens a new ZipFile to read from the specified + * File object in the specified mode. The mode argument + * must be either OPEN_READ or OPEN_READ | OPEN_DELETE. + * + *

First, if there is a security manager, its checkRead + * method is called with the name argument as its argument to + * ensure the read is allowed. + * + * @param file the ZIP file to be opened for reading + * @param mode the mode in which the file is to be opened + * @param charset + * the {@link java.nio.charset.Charset {@code charset}} to + * be used to decode the ZIP entry name and comment that are not + * encoded by using UTF-8 encoding (indicated by entry's general + * purpose flag). + * + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * + * @throws SecurityException + * if a security manager exists and its checkRead + * method doesn't allow read access to the file,or its + * checkDelete method doesn't allow deleting the + * file when the OPEN_DELETE flag is set + * + * @throws IllegalArgumentException if the mode argument is invalid + * + * @see SecurityManager#checkRead(java.lang.String) + * + * @since 1.7 + */ + public ZipFile(File file, int mode, Charset charset) throws IOException + { if (((mode & OPEN_READ) == 0) || ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { throw new IllegalArgumentException("Illegal mode: 0x"+ @@ -128,24 +189,61 @@ class ZipFile implements ZipConstants { sm.checkDelete(name); } } + if (charset == null) + throw new NullPointerException("charset is null"); + this.zc = ZipCoder.get(charset); jzfile = open(name, mode, file.lastModified()); - this.name = name; this.total = getTotal(jzfile); } - private static native long open(String name, int mode, long lastModified); - private static native int getTotal(long jzfile); - + /** + * Opens a zip file for reading. + * + *

First, if there is a security manager, its checkRead + * method is called with the name argument as its argument + * to ensure the read is allowed. + * + * @param name the name of the zip file + * @param charset + * the {@link java.nio.charset.Charset {@code charset}} to + * be used to decode the ZIP entry name and comment that are not + * encoded by using UTF-8 encoding (indicated by entry's general + * purpose flag). + * + * @throws ZipException if a ZIP format error has occurred + * @throws IOException if an I/O error has occurred + * @throws SecurityException + * if a security manager exists and its checkRead + * method doesn't allow read access to the file + * + * @see SecurityManager#checkRead(java.lang.String) + * + * @since 1.7 + */ + public ZipFile(String name, Charset charset) throws IOException + { + this(new File(name), OPEN_READ, charset); + } /** * Opens a ZIP file for reading given the specified File object. * @param file the ZIP file to be opened for reading - * @throws ZipException if a ZIP error has occurred + * @param charset + * The {@link java.nio.charset.Charset {@code charset}} to be + * used to decode the ZIP entry name and comment (ignored if + * the language + * encoding bit of the ZIP entry's general purpose bit + * flag is set). + * + * @throws ZipException if a ZIP format error has occurred * @throws IOException if an I/O error has occurred + * + * @since 1.7 */ - public ZipFile(File file) throws ZipException, IOException { - this(file, OPEN_READ); + public ZipFile(File file, Charset charset) throws IOException + { + this(file, OPEN_READ, charset); } /** @@ -163,9 +261,9 @@ class ZipFile implements ZipConstants { long jzentry = 0; synchronized (this) { ensureOpen(); - jzentry = getEntry(jzfile, name, true); + jzentry = getEntry(jzfile, zc.getBytes(name), true); if (jzentry != 0) { - ZipEntry ze = new ZipEntry(name, jzentry); + ZipEntry ze = getZipEntry(name, jzentry); freeEntry(jzfile, jzentry); return ze; } @@ -173,7 +271,7 @@ class ZipFile implements ZipConstants { return null; } - private static native long getEntry(long jzfile, String name, + private static native long getEntry(long jzfile, byte[] name, boolean addSlash); // freeEntry releases the C jzentry struct. @@ -194,36 +292,30 @@ class ZipFile implements ZipConstants { * @throws IllegalStateException if the zip file has been closed */ public InputStream getInputStream(ZipEntry entry) throws IOException { - return getInputStream(entry.name); - } - - /** - * Returns an input stream for reading the contents of the specified - * entry, or null if the entry was not found. - */ - private InputStream getInputStream(String name) throws IOException { - if (name == null) { - throw new NullPointerException("name"); + if (entry == null) { + throw new NullPointerException("entry"); } long jzentry = 0; ZipFileInputStream in = null; synchronized (this) { ensureOpen(); - jzentry = getEntry(jzfile, name, false); + if (!zc.isUTF8() && (entry.flag & EFS) != 0) { + jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false); + } else { + jzentry = getEntry(jzfile, zc.getBytes(entry.name), false); + } if (jzentry == 0) { return null; } - in = new ZipFileInputStream(jzentry); - } final ZipFileInputStream zfin = in; - switch (getMethod(jzentry)) { + switch (getEntryMethod(jzentry)) { case STORED: return zfin; case DEFLATED: // MORE: Compute good size for inflater stream: - long size = getSize(jzentry) + 2; // Inflater likes a bit of slack + long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack if (size > 65536) size = 8192; if (size <= 0) size = 4096; return new InflaterInputStream(zfin, getInflater(), (int)size) { @@ -267,8 +359,6 @@ class ZipFile implements ZipConstants { } } - private static native int getMethod(long jzentry); - /* * Gets an inflater from the list of available inflaters or allocates * a new one. @@ -343,7 +433,7 @@ class ZipFile implements ZipConstants { ",\n message = " + message ); } - ZipEntry ze = new ZipEntry(jzentry); + ZipEntry ze = getZipEntry(null, jzentry); freeEntry(jzfile, jzentry); return ze; } @@ -351,6 +441,38 @@ class ZipFile implements ZipConstants { }; } + private ZipEntry getZipEntry(String name, long jzentry) { + ZipEntry e = new ZipEntry(); + e.flag = getEntryFlag(jzentry); // get the flag first + if (name != null) { + e.name = name; + } else { + byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME); + if (!zc.isUTF8() && (e.flag & EFS) != 0) { + e.name = zc.toStringUTF8(bname, bname.length); + } else { + e.name = zc.toString(bname, bname.length); + } + } + e.time = getEntryTime(jzentry); + e.crc = getEntryCrc(jzentry); + e.size = getEntrySize(jzentry); + e. csize = getEntryCSize(jzentry); + e.method = getEntryMethod(jzentry); + e.extra = getEntryBytes(jzentry, JZENTRY_EXTRA); + byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); + if (bcomm == null) { + e.comment = null; + } else { + if (!zc.isUTF8() && (e.flag & EFS) != 0) { + e.comment = zc.toStringUTF8(bcomm, bcomm.length); + } else { + e.comment = zc.toString(bcomm, bcomm.length); + } + } + return e; + } + private static native long getNextEntry(long jzfile, int i); /** @@ -443,8 +565,8 @@ class ZipFile implements ZipConstants { ZipFileInputStream(long jzentry) { pos = 0; - rem = getCSize(jzentry); - size = getSize(jzentry); + rem = getEntryCSize(jzentry); + size = getEntrySize(jzentry); this.jzentry = jzentry; } @@ -514,13 +636,25 @@ class ZipFile implements ZipConstants { } + + private static native long open(String name, int mode, long lastModified) + throws IOException; + private static native int getTotal(long jzfile); private static native int read(long jzfile, long jzentry, long pos, byte[] b, int off, int len); - private static native long getCSize(long jzentry); + // access to the native zentry object + private static native long getEntryTime(long jzentry); + private static native long getEntryCrc(long jzentry); + private static native long getEntryCSize(long jzentry); + private static native long getEntrySize(long jzentry); + private static native int getEntryMethod(long jzentry); + private static native int getEntryFlag(long jzentry); - private static native long getSize(long jzentry); + private static final int JZENTRY_NAME = 0; + private static final int JZENTRY_EXTRA = 1; + private static final int JZENTRY_COMMENT = 2; + private static native byte[] getEntryBytes(long jzentry, int type); - // Temporary add on for bug troubleshooting private static native String getZipMessage(long jzfile); } diff --git a/jdk/src/share/classes/java/util/zip/ZipInputStream.java b/jdk/src/share/classes/java/util/zip/ZipInputStream.java index 1b9b93415cb..83f9ad4b753 100644 --- a/jdk/src/share/classes/java/util/zip/ZipInputStream.java +++ b/jdk/src/share/classes/java/util/zip/ZipInputStream.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.io.IOException; import java.io.EOFException; import java.io.PushbackInputStream; +import java.nio.charset.Charset; import static java.util.zip.ZipConstants64.*; /** @@ -54,6 +55,8 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { // one entry private boolean entryEOF = false; + private ZipCoder zc; + /** * Check to make sure that this stream has not been closed */ @@ -65,14 +68,39 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { /** * Creates a new ZIP input stream. + * + *

The UTF-8 {@link java.nio.charset.Charset charset} is used to + * decode the entry names. + * * @param in the actual input stream */ public ZipInputStream(InputStream in) { + this(in, Charset.forName("UTF-8")); + } + + /** + * Creates a new ZIP input stream. + * + * @param in the actual input stream + * + * @param charset + * The {@link java.nio.charset.Charset {@code charset}} to be + * used to decode the ZIP entry name (ignored if the + * language + * encoding bit of the ZIP entry's general purpose bit + * flag is set). + * + * @since 1.7 + */ + public ZipInputStream(InputStream in, Charset charset) { super(new PushbackInputStream(in, 512), new Inflater(true), 512); usesDefaultInflater = true; if(in == null) { throw new NullPointerException("in is null"); } + if (charset == null) + throw new NullPointerException("charset is null"); + this.zc = ZipCoder.get(charset); } /** @@ -141,8 +169,8 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { * @param len the maximum number of bytes read * @return the actual number of bytes read, or -1 if the end of the * entry is reached - * @exception NullPointerException If b is null. - * @exception IndexOutOfBoundsException If off is negative, + * @exception NullPointerException if b is null. + * @exception IndexOutOfBoundsException if off is negative, * len is negative, or len is greater than * b.length - off * @exception ZipException if a ZIP file error has occurred @@ -252,6 +280,8 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { if (get32(tmpbuf, 0) != LOCSIG) { return null; } + // get flag first, we need check EFS. + flag = get16(tmpbuf, LOCFLG); // get the entry name and create the ZipEntry first int len = get16(tmpbuf, LOCNAM); int blen = b.length; @@ -262,9 +292,11 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { b = new byte[blen]; } readFully(b, 0, len); - ZipEntry e = createZipEntry(getUTF8String(b, 0, len)); + // Force to use UTF-8 if the EFS bit is ON, even the cs is NOT UTF-8 + ZipEntry e = createZipEntry(((flag & EFS) != 0) + ? zc.toStringUTF8(b, len) + : zc.toString(b, len)); // now get the remaining fields for the entry - flag = get16(tmpbuf, LOCFLG); if ((flag & 1) == 1) { throw new ZipException("encrypted ZIP entry not supported"); } @@ -313,71 +345,6 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { return e; } - /* - * Fetches a UTF8-encoded String from the specified byte array. - */ - private static String getUTF8String(byte[] b, int off, int len) { - // First, count the number of characters in the sequence - int count = 0; - int max = off + len; - int i = off; - while (i < max) { - int c = b[i++] & 0xff; - switch (c >> 4) { - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: - // 0xxxxxxx - count++; - break; - case 12: case 13: - // 110xxxxx 10xxxxxx - if ((b[i++] & 0xc0) != 0x80) { - throw new IllegalArgumentException(); - } - count++; - break; - case 14: - // 1110xxxx 10xxxxxx 10xxxxxx - if (((b[i++] & 0xc0) != 0x80) || - ((b[i++] & 0xc0) != 0x80)) { - throw new IllegalArgumentException(); - } - count++; - break; - default: - // 10xxxxxx, 1111xxxx - throw new IllegalArgumentException(); - } - } - if (i != max) { - throw new IllegalArgumentException(); - } - // Now decode the characters... - char[] cs = new char[count]; - i = 0; - while (off < max) { - int c = b[off++] & 0xff; - switch (c >> 4) { - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: - // 0xxxxxxx - cs[i++] = (char)c; - break; - case 12: case 13: - // 110xxxxx 10xxxxxx - cs[i++] = (char)(((c & 0x1f) << 6) | (b[off++] & 0x3f)); - break; - case 14: - // 1110xxxx 10xxxxxx 10xxxxxx - int t = (b[off++] & 0x3f) << 6; - cs[i++] = (char)(((c & 0x0f) << 12) | t | (b[off++] & 0x3f)); - break; - default: - // 10xxxxxx, 1111xxxx - throw new IllegalArgumentException(); - } - } - return new String(cs, 0, count); - } - /** * Creates a new ZipEntry object for the specified * entry name. diff --git a/jdk/src/share/classes/java/util/zip/ZipOutputStream.java b/jdk/src/share/classes/java/util/zip/ZipOutputStream.java index bd44d3213cf..da35ed97f14 100644 --- a/jdk/src/share/classes/java/util/zip/ZipOutputStream.java +++ b/jdk/src/share/classes/java/util/zip/ZipOutputStream.java @@ -27,6 +27,7 @@ package java.util.zip; import java.io.OutputStream; import java.io.IOException; +import java.nio.charset.Charset; import java.util.Vector; import java.util.HashSet; import static java.util.zip.ZipConstants64.*; @@ -44,19 +45,9 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { private static class XEntry { public final ZipEntry entry; public final long offset; - public final int flag; public XEntry(ZipEntry entry, long offset) { this.entry = entry; this.offset = offset; - this.flag = (entry.method == DEFLATED && - (entry.size == -1 || - entry.csize == -1 || - entry.crc == -1)) - // store size, compressed size, and crc-32 in data descriptor - // immediately following the compressed entry data - ? 8 - // store size, compressed size, and crc-32 in LOC header - : 0; } } @@ -66,12 +57,14 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { private CRC32 crc = new CRC32(); private long written = 0; private long locoff = 0; - private String comment; + private byte[] comment; private int method = DEFLATED; private boolean finished; private boolean closed = false; + private final ZipCoder zc; + private static int version(ZipEntry e) throws ZipException { switch (e.method) { case DEFLATED: return 20; @@ -100,10 +93,31 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { /** * Creates a new ZIP output stream. + * + *

The UTF-8 {@link java.nio.charset.Charset charset} is used + * to encode the entry names and comments. + * * @param out the actual output stream */ public ZipOutputStream(OutputStream out) { + this(out, Charset.forName("UTF-8")); + } + + /** + * Creates a new ZIP output stream. + * + * @param out the actual output stream + * + * @param charset the {@link java.nio.charset.Charset charset} + * to be used to encode the entry names and comments + * + * @since 1.7 + */ + public ZipOutputStream(OutputStream out, Charset charset) { super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); + if (charset == null) + throw new NullPointerException("charset is null"); + this.zc = ZipCoder.get(charset); usesDefaultDeflater = true; } @@ -114,11 +128,11 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { * ZIP file comment is greater than 0xFFFF bytes */ public void setComment(String comment) { - if (comment != null && comment.length() > 0xffff/3 - && getUTF8Length(comment) > 0xffff) { - throw new IllegalArgumentException("ZIP file comment too long."); + if (comment != null) { + this.comment = zc.getBytes(comment); + if (this.comment.length > 0xffff) + throw new IllegalArgumentException("ZIP file comment too long."); } - this.comment = comment; } /** @@ -167,8 +181,15 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { if (e.method == -1) { e.method = method; // use default method } + // store size, compressed size, and crc-32 in LOC header + e.flag = 0; switch (e.method) { case DEFLATED: + // store size, compressed size, and crc-32 in data descriptor + // immediately following the compressed entry data + if (e.size == -1 || e.csize == -1 || e.crc == -1) + e.flag = 8; + break; case STORED: // compressed size, uncompressed size, and crc-32 must all be @@ -192,6 +213,8 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { if (! names.add(e.name)) { throw new ZipException("duplicate entry: " + e.name); } + if (zc.isUTF8()) + e.flag |= EFS; current = new XEntry(e, written); xentries.add(current); writeLOC(current); @@ -213,7 +236,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { while (!def.finished()) { deflate(); } - if ((current.flag & 8) == 0) { + if ((e.flag & 8) == 0) { // verify size, compressed size, and crc-32 settings if (e.size != def.getBytesRead()) { throw new ZipException( @@ -343,11 +366,11 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { */ private void writeLOC(XEntry xentry) throws IOException { ZipEntry e = xentry.entry; - int flag = xentry.flag; + int flag = e.flag; int elen = (e.extra != null) ? e.extra.length : 0; boolean hasZip64 = false; - writeInt(LOCSIG); // LOC header signature + writeInt(LOCSIG); // LOC header signature if ((flag & 8) == 8) { writeShort(version(e)); // version needed to extract @@ -380,7 +403,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { writeInt(e.size); // uncompressed size } } - byte[] nameBytes = getUTF8Bytes(e.name); + byte[] nameBytes = zc.getBytes(e.name); writeShort(nameBytes.length); writeShort(elen); writeBytes(nameBytes, 0, nameBytes.length); @@ -417,7 +440,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { */ private void writeCEN(XEntry xentry) throws IOException { ZipEntry e = xentry.entry; - int flag = xentry.flag; + int flag = e.flag; int version = version(e); long csize = e.csize; @@ -454,7 +477,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { writeInt(e.crc); // crc-32 writeInt(csize); // compressed size writeInt(size); // uncompressed size - byte[] nameBytes = getUTF8Bytes(e.name); + byte[] nameBytes = zc.getBytes(e.name); writeShort(nameBytes.length); if (hasZip64) { // + headid(2) + datasize(2) @@ -464,8 +487,8 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { } byte[] commentBytes; if (e.comment != null) { - commentBytes = getUTF8Bytes(e.comment); - writeShort(commentBytes.length); + commentBytes = zc.getBytes(e.comment); + writeShort(Math.min(commentBytes.length, 0xffff)); } else { commentBytes = null; writeShort(0); @@ -489,7 +512,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { writeBytes(e.extra, 0, e.extra.length); } if (commentBytes != null) { - writeBytes(commentBytes, 0, commentBytes.length); + writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); } } @@ -541,9 +564,8 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { writeInt(xlen); // length of central directory writeInt(xoff); // offset of central directory if (comment != null) { // zip file comment - byte[] b = getUTF8Bytes(comment); - writeShort(b.length); - writeBytes(b, 0, b.length); + writeShort(comment.length); + writeBytes(comment, 0, comment.length); } else { writeShort(0); } @@ -594,60 +616,4 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { super.out.write(b, off, len); written += len; } - - /* - * Returns the length of String's UTF8 encoding. - */ - static int getUTF8Length(String s) { - int count = 0; - for (int i = 0; i < s.length(); i++) { - char ch = s.charAt(i); - if (ch <= 0x7f) { - count++; - } else if (ch <= 0x7ff) { - count += 2; - } else { - count += 3; - } - } - return count; - } - - /* - * Returns an array of bytes representing the UTF8 encoding - * of the specified String. - */ - private static byte[] getUTF8Bytes(String s) { - char[] c = s.toCharArray(); - int len = c.length; - // Count the number of encoded bytes... - int count = 0; - for (int i = 0; i < len; i++) { - int ch = c[i]; - if (ch <= 0x7f) { - count++; - } else if (ch <= 0x7ff) { - count += 2; - } else { - count += 3; - } - } - // Now return the encoded bytes... - byte[] b = new byte[count]; - int off = 0; - for (int i = 0; i < len; i++) { - int ch = c[i]; - if (ch <= 0x7f) { - b[off++] = (byte)ch; - } else if (ch <= 0x7ff) { - b[off++] = (byte)((ch >> 6) | 0xc0); - b[off++] = (byte)((ch & 0x3f) | 0x80); - } else { - b[off++] = (byte)((ch >> 12) | 0xe0); - b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80); - b[off++] = (byte)((ch & 0x3f) | 0x80); - } - } - return b; - } } diff --git a/jdk/src/share/classes/java/util/zip/package.html b/jdk/src/share/classes/java/util/zip/package.html index d4b59263678..83d117de55e 100644 --- a/jdk/src/share/classes/java/util/zip/package.html +++ b/jdk/src/share/classes/java/util/zip/package.html @@ -52,6 +52,11 @@ input streams. PKWARE ZIP File Format Specification. The ZIP64(tm) format extensions are used to overcome the size limitations of the original ZIP format. +

+ +

  • APPENDIX D of + PKWARE ZIP File Format Specification - Language Encoding Flag (EFS) to + encode ZIP entry filename and comment fields using UTF-8.

  • ZLIB Compressed Data Format Specification version 3.3 diff --git a/jdk/src/share/native/java/util/zip/ZipEntry.c b/jdk/src/share/native/java/util/zip/ZipEntry.c deleted file mode 100644 index 94cd68e1b12..00000000000 --- a/jdk/src/share/native/java/util/zip/ZipEntry.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the LICENSE file that accompanied this code. - * - * This code 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 General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -/* - * Native method support for java.util.zip.ZipEntry - */ - -#include -#include -#include "jlong.h" -#include "jvm.h" -#include "jni.h" -#include "jni_util.h" -#include "zip_util.h" - -#include "java_util_zip_ZipEntry.h" - -#define DEFLATED 8 -#define STORED 0 - -static jfieldID nameID; -static jfieldID timeID; -static jfieldID crcID; -static jfieldID sizeID; -static jfieldID csizeID; -static jfieldID methodID; -static jfieldID extraID; -static jfieldID commentID; - -JNIEXPORT void JNICALL -Java_java_util_zip_ZipEntry_initIDs(JNIEnv *env, jclass cls) -{ - nameID = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;"); - timeID = (*env)->GetFieldID(env, cls, "time", "J"); - crcID = (*env)->GetFieldID(env, cls, "crc", "J"); - sizeID = (*env)->GetFieldID(env, cls, "size", "J"); - csizeID = (*env)->GetFieldID(env, cls, "csize", "J"); - methodID = (*env)->GetFieldID(env, cls, "method", "I"); - extraID = (*env)->GetFieldID(env, cls, "extra", "[B"); - commentID = (*env)->GetFieldID(env, cls, "comment", "Ljava/lang/String;"); -} - -JNIEXPORT void JNICALL -Java_java_util_zip_ZipEntry_initFields(JNIEnv *env, jobject obj, jlong zentry) -{ - jzentry *ze = jlong_to_ptr(zentry); - jstring name = (*env)->GetObjectField(env, obj, nameID); - - if (name == 0) { - name = (*env)->NewStringUTF(env, ze->name); - if (name == 0) { - return; - } - (*env)->SetObjectField(env, obj, nameID, name); - } - (*env)->SetLongField(env, obj, timeID, (jlong)ze->time & 0xffffffffUL); - (*env)->SetLongField(env, obj, crcID, (jlong)ze->crc & 0xffffffffUL); - (*env)->SetLongField(env, obj, sizeID, (jlong)ze->size); - if (ze->csize == 0) { - (*env)->SetLongField(env, obj, csizeID, (jlong)ze->size); - (*env)->SetIntField(env, obj, methodID, STORED); - } else { - (*env)->SetLongField(env, obj, csizeID, (jlong)ze->csize); - (*env)->SetIntField(env, obj, methodID, DEFLATED); - } - if (ze->extra != 0) { - unsigned char *bp = (unsigned char *)&ze->extra[0]; - jsize len = (bp[0] | (bp[1] << 8)); - jbyteArray extra = (*env)->NewByteArray(env, len); - if (extra == 0) { - return; - } - (*env)->SetByteArrayRegion(env, extra, 0, len, &ze->extra[2]); - (*env)->SetObjectField(env, obj, extraID, extra); - } - if (ze->comment != 0) { - jstring comment = (*env)->NewStringUTF(env, ze->comment); - if (comment == 0) { - return; - } - (*env)->SetObjectField(env, obj, commentID, comment); - } -} diff --git a/jdk/src/share/native/java/util/zip/ZipFile.c b/jdk/src/share/native/java/util/zip/ZipFile.c index 0c20536b49a..d5cb1ecac8b 100644 --- a/jdk/src/share/native/java/util/zip/ZipFile.c +++ b/jdk/src/share/native/java/util/zip/ZipFile.c @@ -141,12 +141,11 @@ Java_java_util_zip_ZipFile_close(JNIEnv *env, jclass cls, jlong zfile) JNIEXPORT jlong JNICALL Java_java_util_zip_ZipFile_getEntry(JNIEnv *env, jclass cls, jlong zfile, - jstring name, jboolean addSlash) + jbyteArray name, jboolean addSlash) { #define MAXNAME 1024 jzfile *zip = jlong_to_ptr(zfile); - jsize slen = (*env)->GetStringLength(env, name); - jsize ulen = (*env)->GetStringUTFLength(env, name); + jsize ulen = (*env)->GetArrayLength(env, name); char buf[MAXNAME+2], *path; jzentry *ze; @@ -159,7 +158,7 @@ Java_java_util_zip_ZipFile_getEntry(JNIEnv *env, jclass cls, jlong zfile, } else { path = buf; } - (*env)->GetStringUTFRegion(env, name, 0, slen, path); + (*env)->GetByteArrayRegion(env, name, 0, ulen, (jbyte *)path); path[ulen] = '\0'; if (addSlash == JNI_FALSE) { ze = ZIP_GetEntry(zip, path, 0); @@ -186,34 +185,87 @@ Java_java_util_zip_ZipFile_getNextEntry(JNIEnv *env, jclass cls, jlong zfile, jint n) { jzentry *ze = ZIP_GetNextEntry(jlong_to_ptr(zfile), n); - return ptr_to_jlong(ze); } JNIEXPORT jint JNICALL -Java_java_util_zip_ZipFile_getMethod(JNIEnv *env, jclass cls, jlong zentry) +Java_java_util_zip_ZipFile_getEntryMethod(JNIEnv *env, jclass cls, jlong zentry) { jzentry *ze = jlong_to_ptr(zentry); - return ze->csize != 0 ? DEFLATED : STORED; } -JNIEXPORT jlong JNICALL -Java_java_util_zip_ZipFile_getCSize(JNIEnv *env, jclass cls, jlong zentry) +JNIEXPORT jint JNICALL +Java_java_util_zip_ZipFile_getEntryFlag(JNIEnv *env, jclass cls, jlong zentry) { jzentry *ze = jlong_to_ptr(zentry); + return ze->flag; +} +JNIEXPORT jlong JNICALL +Java_java_util_zip_ZipFile_getEntryCSize(JNIEnv *env, jclass cls, jlong zentry) +{ + jzentry *ze = jlong_to_ptr(zentry); return ze->csize != 0 ? ze->csize : ze->size; } JNIEXPORT jlong JNICALL -Java_java_util_zip_ZipFile_getSize(JNIEnv *env, jclass cls, jlong zentry) +Java_java_util_zip_ZipFile_getEntrySize(JNIEnv *env, jclass cls, jlong zentry) { jzentry *ze = jlong_to_ptr(zentry); - return ze->size; } +JNIEXPORT jlong JNICALL +Java_java_util_zip_ZipFile_getEntryTime(JNIEnv *env, jclass cls, jlong zentry) +{ + jzentry *ze = jlong_to_ptr(zentry); + return (jlong)ze->time & 0xffffffffUL; +} + +JNIEXPORT jlong JNICALL +Java_java_util_zip_ZipFile_getEntryCrc(JNIEnv *env, jclass cls, jlong zentry) +{ + jzentry *ze = jlong_to_ptr(zentry); + return (jlong)ze->crc & 0xffffffffUL; +} + +JNIEXPORT jbyteArray JNICALL +Java_java_util_zip_ZipFile_getEntryBytes(JNIEnv *env, jclass cls, jlong zentry, jint type) +{ + jzentry *ze = jlong_to_ptr(zentry); + int len = 0; + jbyteArray jba = NULL; + switch (type) { + case java_util_zip_ZipFile_JZENTRY_NAME: + if (ze->name != 0) { + len = (int)strlen(ze->name); + if (len == 0 || (jba = (*env)->NewByteArray(env, len)) == NULL) + break; + (*env)->SetByteArrayRegion(env, jba, 0, len, (jbyte *)ze->name); + } + break; + case java_util_zip_ZipFile_JZENTRY_EXTRA: + if (ze->extra != 0) { + unsigned char *bp = (unsigned char *)&ze->extra[0]; + len = (bp[0] | (bp[1] << 8)); + if (len <= 0 || (jba = (*env)->NewByteArray(env, len)) == NULL) + break; + (*env)->SetByteArrayRegion(env, jba, 0, len, &ze->extra[2]); + } + break; + case java_util_zip_ZipFile_JZENTRY_COMMENT: + if (ze->comment != 0) { + len = (int)strlen(ze->comment); + if (len == 0 || (jba = (*env)->NewByteArray(env, len)) == NULL) + break; + (*env)->SetByteArrayRegion(env, jba, 0, len, (jbyte*)ze->comment); + } + break; + } + return jba; +} + JNIEXPORT jint JNICALL Java_java_util_zip_ZipFile_read(JNIEnv *env, jclass cls, jlong zfile, jlong zentry, jlong pos, jbyteArray bytes, diff --git a/jdk/src/share/native/java/util/zip/zip_util.c b/jdk/src/share/native/java/util/zip/zip_util.c index 89f52ae3bcc..626d4118f75 100644 --- a/jdk/src/share/native/java/util/zip/zip_util.c +++ b/jdk/src/share/native/java/util/zip/zip_util.c @@ -512,7 +512,6 @@ readCEN(jzfile *zip, jint knownTotal) /* Clear previous zip error */ zip->msg = NULL; - /* Get position of END header */ if ((endpos = findEND(zip, endbuf)) == -1) return -1; /* no END header or system error */ @@ -520,7 +519,6 @@ readCEN(jzfile *zip, jint knownTotal) if (endpos == 0) return 0; /* only END header present */ freeCEN(zip); - /* Get position and length of central directory */ cenlen = ENDSIZ(endbuf); cenoff = ENDOFF(endbuf); @@ -935,6 +933,7 @@ newEntry(jzfile *zip, jzcell *zc, AccessHint accessHint) ze->crc = CENCRC(cen); locoff = CENOFF(cen); ze->pos = -(zip->locpos + locoff); + ze->flag = CENFLG(cen); if ((ze->name = malloc(nlen + 1)) == NULL) goto Catch; memcpy(ze->name, cen + CENHDR, nlen); diff --git a/jdk/src/share/native/java/util/zip/zip_util.h b/jdk/src/share/native/java/util/zip/zip_util.h index 7ad11715e5a..311a9fcd9de 100644 --- a/jdk/src/share/native/java/util/zip/zip_util.h +++ b/jdk/src/share/native/java/util/zip/zip_util.h @@ -168,6 +168,7 @@ typedef struct jzentry { /* Zip file entry */ char *comment; /* optional zip file comment */ jbyte *extra; /* optional extra data */ jlong pos; /* position of LOC header or entry data */ + jint flag; /* general purpose flag */ } jzentry; /* diff --git a/jdk/src/windows/native/sun/security/krb5/NativeCreds.c b/jdk/src/windows/native/sun/security/krb5/NativeCreds.c index 968ff119153..c910c62be4a 100644 --- a/jdk/src/windows/native/sun/security/krb5/NativeCreds.c +++ b/jdk/src/windows/native/sun/security/krb5/NativeCreds.c @@ -1,5 +1,5 @@ /* - * Portions Copyright 2000-2008 Sun Microsystems, Inc. All Rights Reserved. + * Portions Copyright 2000-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,7 +73,7 @@ jmethodID setRealmMethod = 0; * Function prototypes for internal routines * */ -BOOL DEBUG = 0; +BOOL native_debug = 0; BOOL PackageConnectLookup(PHANDLE,PULONG); @@ -130,7 +130,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Krb5 has no DEBUG field\n"); return JNI_ERR; } - DEBUG = (*env)->GetStaticBooleanField(env, cls, fldDEBUG); + native_debug = (*env)->GetStaticBooleanField(env, cls, fldDEBUG); cls = (*env)->FindClass(env,"sun/security/krb5/internal/Ticket"); @@ -138,17 +138,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find Ticket\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found Ticket\n"); - } /* DEBUG */ + } ticketClass = (*env)->NewWeakGlobalRef(env,cls); if (ticketClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } cls = (*env)->FindClass(env, "sun/security/krb5/PrincipalName"); @@ -156,17 +156,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find PrincipalName\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found PrincipalName\n"); - } /* DEBUG */ + } principalNameClass = (*env)->NewWeakGlobalRef(env,cls); if (principalNameClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } cls = (*env)->FindClass(env,"sun/security/util/DerValue"); @@ -174,17 +174,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find DerValue\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found DerValue\n"); - } /* DEBUG */ + } derValueClass = (*env)->NewWeakGlobalRef(env,cls); if (derValueClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } cls = (*env)->FindClass(env,"sun/security/krb5/EncryptionKey"); @@ -192,17 +192,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find EncryptionKey\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found EncryptionKey\n"); - } /* DEBUG */ + } encryptionKeyClass = (*env)->NewWeakGlobalRef(env,cls); if (encryptionKeyClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } cls = (*env)->FindClass(env,"sun/security/krb5/internal/TicketFlags"); @@ -210,17 +210,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find TicketFlags\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found TicketFlags\n"); - } /* DEBUG */ + } ticketFlagsClass = (*env)->NewWeakGlobalRef(env,cls); if (ticketFlagsClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } cls = (*env)->FindClass(env,"sun/security/krb5/internal/KerberosTime"); @@ -228,17 +228,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find KerberosTime\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found KerberosTime\n"); - } /* DEBUG */ + } kerberosTimeClass = (*env)->NewWeakGlobalRef(env,cls); if (kerberosTimeClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } cls = (*env)->FindClass(env,"java/lang/String"); @@ -246,17 +246,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find String\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found String\n"); - } /* DEBUG */ + } javaLangStringClass = (*env)->NewWeakGlobalRef(env,cls); if (javaLangStringClass == NULL) { return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Made NewWeakGlobalRef\n"); - } /* DEBUG */ + } derValueConstructor = (*env)->GetMethodID(env, derValueClass, "", "([B)V"); @@ -264,9 +264,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find DerValue constructor\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found DerValue constructor\n"); - } /* DEBUG */ + } ticketConstructor = (*env)->GetMethodID(env, ticketClass, "", "(Lsun/security/util/DerValue;)V"); @@ -274,9 +274,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find Ticket constructor\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found Ticket constructor\n"); - } /* DEBUG */ + } principalNameConstructor = (*env)->GetMethodID(env, principalNameClass, "", "([Ljava/lang/String;)V"); @@ -284,9 +284,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find PrincipalName constructor\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found PrincipalName constructor\n"); - } /* DEBUG */ + } encryptionKeyConstructor = (*env)->GetMethodID(env, encryptionKeyClass, "", "(I[B)V"); @@ -294,9 +294,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find EncryptionKey constructor\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found EncryptionKey constructor\n"); - } /* DEBUG */ + } ticketFlagsConstructor = (*env)->GetMethodID(env, ticketFlagsClass, "", "(I[B)V"); @@ -304,9 +304,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find TicketFlags constructor\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found TicketFlags constructor\n"); - } /* DEBUG */ + } kerberosTimeConstructor = (*env)->GetMethodID(env, kerberosTimeClass, "", "(Ljava/lang/String;)V"); @@ -314,9 +314,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( printf("LSA: Couldn't find KerberosTime constructor\n"); return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Found KerberosTime constructor\n"); - } /* DEBUG */ + } // load the setRealm method in PrincipalName setRealmMethod = (*env)->GetMethodID(env, principalNameClass, @@ -326,9 +326,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad( return JNI_ERR; } - if (DEBUG) { + if (native_debug) { printf("LSA: Finished OnLoad processing\n"); - } /* DEBUG */ + } return JNI_VERSION_1_2; } @@ -419,7 +419,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ } } - if (DEBUG) { + if (native_debug) { printf("LSA: Found KrbCreds constructor\n"); } @@ -430,9 +430,9 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ if (!PackageConnectLookup(&LogonHandle, &PackageId)) break; - if (DEBUG) { + if (native_debug) { printf("LSA: Got handle to Kerberos package\n"); - } /* DEBUG */ + } // Get the MS TGT from cache CacheRequest.MessageType = KerbRetrieveTicketMessage; @@ -449,7 +449,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ &SubStatus ); - if (DEBUG) { + if (native_debug) { printf("LSA: Response size is %d\n", rspSize); } @@ -490,9 +490,9 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ } if (ignore_cache) { - if (DEBUG) { + if (native_debug) { printf("LSA: MS TGT in cache is invalid/not supported; request new ticket\n"); - } /* DEBUG */ + } // use domain to request Ticket Status = ConstructTicketRequest(msticket->TargetDomainName, @@ -516,9 +516,9 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ &SubStatus ); - if (DEBUG) { + if (native_debug) { printf("LSA: Response size is %d\n", responseSize); - } /* DEBUG */ + } if (!LSA_SUCCESS(Status) || !LSA_SUCCESS(SubStatus)) { if (!LSA_SUCCESS(Status)) { @@ -811,7 +811,7 @@ ShowLastError( static WCHAR szMsgBuf[MAX_MSG_SIZE]; DWORD dwRes; - if (DEBUG) { + if (native_debug) { printf("LSA: Error calling function %s: %lu\n", szAPI, dwError); } @@ -823,7 +823,7 @@ ShowLastError( szMsgBuf, MAX_MSG_SIZE, NULL); - if (DEBUG) { + if (native_debug) { if (0 == dwRes) { printf("LSA: FormatMessage failed with %d\n", GetLastError()); // ExitProcess(EXIT_FAILURE); @@ -923,7 +923,7 @@ jobject BuildPrincipal(JNIEnv *env, PKERB_EXTERNAL_NAME principalName, ((domainName.Length)*sizeof(WCHAR) + sizeof(UNICODE_NULL))); wcsncpy(realm, domainName.Buffer, domainName.Length/sizeof(WCHAR)); - if (DEBUG) { + if (native_debug) { printf("LSA: Principal domain is %S\n", realm); printf("LSA: Name type is %x\n", principalName->NameType); printf("LSA: Name count is %x\n", principalName->NameCount); @@ -933,7 +933,7 @@ jobject BuildPrincipal(JNIEnv *env, PKERB_EXTERNAL_NAME principalName, stringArray = (*env)->NewObjectArray(env, nameCount, javaLangStringClass, NULL); if (stringArray == NULL) { - if (DEBUG) { + if (native_debug) { printf("LSA: Can't allocate String array for Principal\n"); } LocalFree(realm); @@ -976,7 +976,7 @@ jobject BuildEncryptionKey(JNIEnv *env, PKERB_CRYPTO_KEY cryptoKey) { if (cryptoKey->Value[i]) break; } if (i == cryptoKey->Length) { - if (DEBUG) { + if (native_debug) { printf("LSA: Session key all zero. Stop.\n"); } return NULL; @@ -1045,9 +1045,9 @@ jobject BuildKerberosTime(JNIEnv *env, PLARGE_INTEGER kerbtime) { hour, minute, second ); - if (DEBUG) { + if (native_debug) { printf("LSA: %S\n", (wchar_t *)timeString); - } /* DEBUG */ + } stringTime = (*env)->NewString(env, timeString, (sizeof(timeString)/sizeof(WCHAR))-1); if (stringTime != NULL) { // everything's OK so far diff --git a/jdk/test/java/util/zip/ZipCoding.java b/jdk/test/java/util/zip/ZipCoding.java new file mode 100644 index 00000000000..baedc449bfb --- /dev/null +++ b/jdk/test/java/util/zip/ZipCoding.java @@ -0,0 +1,133 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/** + * @test + * @bug 4244499 4532049 4700978 4820807 4980042 + * @summary Test ZipInputStream, ZipOutputStream and ZipFile with non-UTF8 encoding + */ + +import java.io.*; +import java.nio.charset.*; +import java.util.*; +import java.util.zip.*; + +public class ZipCoding { + + public static void main(String[] args) throws Exception { + + test("MS932", + "\u4e00\u4e01", "\uff67\uff68\uff69\uff6a\uff6b\uff6c"); + + test("ibm437", + "\u00e4\u00fc", "German Umlaut \u00fc in comment"); + + test("utf-8", + "\u4e00\u4e01", "\uff67\uff68\uff69\uff6a\uff6b\uff6c"); + + test("utf-8", + "\u00e4\u00fc", "German Umlaut \u00fc in comment"); + + test("utf-8", + "Surrogate\ud801\udc01", "Surrogates \ud800\udc00 in comment"); + + } + + static void testZipInputStream(InputStream is, Charset cs, + String name, String comment, byte[] bb) + throws Exception + { + ZipInputStream zis = new ZipInputStream(is, cs); + ZipEntry e = zis.getNextEntry(); + if (e == null || ! name.equals(e.getName())) + throw new RuntimeException("ZipIS name doesn't match!"); + byte[] bBuf = new byte[bb.length << 1]; + int n = zis.read(bBuf, 0, bBuf.length); + if (n != bb.length || + !Arrays.equals(bb, Arrays.copyOf(bBuf, n))) { + throw new RuntimeException("ZipIS content doesn't match!"); + } + zis.close(); + } + + static void testZipFile(File f, Charset cs, + String name, String comment, byte[] bb) + throws Exception + { + ZipFile zf = new ZipFile(f, cs); + Enumeration zes = zf.entries(); + ZipEntry e = (ZipEntry)zes.nextElement(); + if (! name.equals(e.getName()) || + ! comment.equals(e.getComment())) + throw new RuntimeException("ZipFile: name/comment doesn't match!"); + InputStream is = zf.getInputStream(e); + if (is == null) + throw new RuntimeException("ZipFile: getIS failed!"); + byte[] bBuf = new byte[bb.length << 1]; + int n = 0; + int nn =0; + while ((nn = is.read(bBuf, n, bBuf.length-n)) != -1) { + n += nn; + } + if (n != bb.length || + !Arrays.equals(bb, Arrays.copyOf(bBuf, n))) { + throw new RuntimeException("ZipFile content doesn't match!"); + } + zf.close(); + } + + static void test(String csn, String name, String comment) + throws Exception + { + byte[] bb = "This is the conent of the zipfile".getBytes("ISO-8859-1"); + Charset cs = Charset.forName(csn); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(baos, cs); + + ZipEntry e = new ZipEntry(name); + e.setComment(comment); + zos.putNextEntry(e); + zos.write(bb, 0, bb.length); + zos.closeEntry(); + zos.close(); + ByteArrayInputStream bis = new ByteArrayInputStream(baos.toByteArray()); + testZipInputStream(bis, cs, name, comment, bb); + + if ("utf-8".equals(csn)) { + // EFS should be set + bis.reset(); + testZipInputStream(bis, Charset.forName("MS932"), name, comment, bb); + } + + File f = new File(new File(System.getProperty("test.dir", ".")), + "zfcoding.zip"); + FileOutputStream fos = new FileOutputStream(f); + baos.writeTo(fos); + fos.close(); + testZipFile(f, cs, name, comment, bb); + if ("utf-8".equals(csn)) { + testZipFile(f, Charset.forName("MS932"), name, comment, bb); + } + f.delete(); + } +} diff --git a/jdk/test/java/util/zip/zip.java b/jdk/test/java/util/zip/zip.java new file mode 100644 index 00000000000..33eccf9496d --- /dev/null +++ b/jdk/test/java/util/zip/zip.java @@ -0,0 +1,743 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.io.*; +import java.nio.charset.Charset; +import java.util.*; +import java.util.zip.*; +import java.text.MessageFormat; + +/** + * A stripped-down version of Jar tool with a "-encoding" option to + * support non-UTF8 encoidng for entry name and comment. + */ +public class zip { + String program; + PrintStream out, err; + String fname; + String zname = ""; + String[] files; + Charset cs = Charset.forName("UTF-8"); + + Map entryMap = new HashMap(); + Set entries = new LinkedHashSet(); + List paths = new ArrayList(); + + CRC32 crc32 = new CRC32(); + /* + * cflag: create + * uflag: update + * xflag: xtract + * tflag: table + * vflag: verbose + * flag0: no zip compression (store only) + */ + boolean cflag, uflag, xflag, tflag, vflag, flag0; + + private static ResourceBundle rsrc; + static { + try { + // just use the jar message + rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar"); + } catch (MissingResourceException e) { + throw new Error("Fatal: Resource for jar is missing"); + } + } + + public zip(PrintStream out, PrintStream err, String program) { + this.out = out; + this.err = err; + this.program = program; + } + + private boolean ok; + + public synchronized boolean run(String args[]) { + ok = true; + if (!parseArgs(args)) { + return false; + } + try { + if (cflag || uflag) { + if (fname != null) { + zname = fname.replace(File.separatorChar, '/'); + if (zname.startsWith("./")) { + zname = zname.substring(2); + } + } + } + if (cflag) { + OutputStream out; + if (fname != null) { + out = new FileOutputStream(fname); + } else { + out = new FileOutputStream(FileDescriptor.out); + if (vflag) { + vflag = false; + } + } + expand(null, files, false); + create(new BufferedOutputStream(out, 4096)); + out.close(); + } else if (uflag) { + File inputFile = null, tmpFile = null; + FileInputStream in; + FileOutputStream out; + if (fname != null) { + inputFile = new File(fname); + String path = inputFile.getParent(); + tmpFile = File.createTempFile("tmp", null, + new File((path == null) ? "." : path)); + in = new FileInputStream(inputFile); + out = new FileOutputStream(tmpFile); + } else { + in = new FileInputStream(FileDescriptor.in); + out = new FileOutputStream(FileDescriptor.out); + vflag = false; + } + expand(null, files, true); + boolean updateOk = update(in, new BufferedOutputStream(out)); + if (ok) { + ok = updateOk; + } + in.close(); + out.close(); + if (fname != null) { + inputFile.delete(); + if (!tmpFile.renameTo(inputFile)) { + tmpFile.delete(); + throw new IOException(getMsg("error.write.file")); + } + tmpFile.delete(); + } + } else if (tflag) { + replaceFSC(files); + if (fname != null) { + list(fname, files); + } else { + InputStream in = new FileInputStream(FileDescriptor.in); + try{ + list(new BufferedInputStream(in), files); + } finally { + in.close(); + } + } + } else if (xflag) { + replaceFSC(files); + if (fname != null && files != null) { + extract(fname, files); + } else { + InputStream in = (fname == null) + ? new FileInputStream(FileDescriptor.in) + : new FileInputStream(fname); + try { + extract(new BufferedInputStream(in), files); + } finally { + in.close(); + } + } + } + } catch (IOException e) { + fatalError(e); + ok = false; + } catch (Error ee) { + ee.printStackTrace(); + ok = false; + } catch (Throwable t) { + t.printStackTrace(); + ok = false; + } + out.flush(); + err.flush(); + return ok; + } + + + boolean parseArgs(String args[]) { + try { + args = parse(args); + } catch (FileNotFoundException e) { + fatalError(formatMsg("error.cant.open", e.getMessage())); + return false; + } catch (IOException e) { + fatalError(e); + return false; + } + int count = 1; + try { + String flags = args[0]; + if (flags.startsWith("-")) { + flags = flags.substring(1); + } + for (int i = 0; i < flags.length(); i++) { + switch (flags.charAt(i)) { + case 'c': + if (xflag || tflag || uflag) { + usageError(); + return false; + } + cflag = true; + break; + case 'u': + if (cflag || xflag || tflag) { + usageError(); + return false; + } + uflag = true; + break; + case 'x': + if (cflag || uflag || tflag) { + usageError(); + return false; + } + xflag = true; + break; + case 't': + if (cflag || uflag || xflag) { + usageError(); + return false; + } + tflag = true; + break; + case 'v': + vflag = true; + break; + case 'f': + fname = args[count++]; + break; + case '0': + flag0 = true; + break; + default: + error(formatMsg("error.illegal.option", + String.valueOf(flags.charAt(i)))); + usageError(); + return false; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + usageError(); + return false; + } + if (!cflag && !tflag && !xflag && !uflag) { + error(getMsg("error.bad.option")); + usageError(); + return false; + } + /* parse file arguments */ + int n = args.length - count; + if (n > 0) { + int k = 0; + String[] nameBuf = new String[n]; + try { + for (int i = count; i < args.length; i++) { + if (args[i].equals("-encoding")) { + cs = Charset.forName(args[++i]); + } else if (args[i].equals("-C")) { + /* change the directory */ + String dir = args[++i]; + dir = (dir.endsWith(File.separator) ? + dir : (dir + File.separator)); + dir = dir.replace(File.separatorChar, '/'); + while (dir.indexOf("//") > -1) { + dir = dir.replace("//", "/"); + } + paths.add(dir.replace(File.separatorChar, '/')); + nameBuf[k++] = dir + args[++i]; + } else { + nameBuf[k++] = args[i]; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + usageError(); + return false; + } + if (k != 0) { + files = new String[k]; + System.arraycopy(nameBuf, 0, files, 0, k); + } + } else if (cflag || uflag) { + error(getMsg("error.bad.uflag")); + usageError(); + return false; + } + return true; + } + + void expand(File dir, String[] files, boolean isUpdate) { + if (files == null) { + return; + } + for (int i = 0; i < files.length; i++) { + File f; + if (dir == null) { + f = new File(files[i]); + } else { + f = new File(dir, files[i]); + } + if (f.isFile()) { + if (entries.add(f)) { + if (isUpdate) + entryMap.put(entryName(f.getPath()), f); + } + } else if (f.isDirectory()) { + if (entries.add(f)) { + if (isUpdate) { + String dirPath = f.getPath(); + dirPath = (dirPath.endsWith(File.separator)) ? dirPath : + (dirPath + File.separator); + entryMap.put(entryName(dirPath), f); + } + expand(f, f.list(), isUpdate); + } + } else { + error(formatMsg("error.nosuch.fileordir", String.valueOf(f))); + ok = false; + } + } + } + + void create(OutputStream out) throws IOException + { + ZipOutputStream zos = new ZipOutputStream(out, cs); + if (flag0) { + zos.setMethod(ZipOutputStream.STORED); + } + for (File file: entries) { + addFile(zos, file); + } + zos.close(); + } + + boolean update(InputStream in, OutputStream out) throws IOException + { + ZipInputStream zis = new ZipInputStream(in, cs); + ZipOutputStream zos = new ZipOutputStream(out, cs); + ZipEntry e = null; + byte[] buf = new byte[1024]; + int n = 0; + boolean updateOk = true; + + // put the old entries first, replace if necessary + while ((e = zis.getNextEntry()) != null) { + String name = e.getName(); + if (!entryMap.containsKey(name)) { // copy the old stuff + // do our own compression + ZipEntry e2 = new ZipEntry(name); + e2.setMethod(e.getMethod()); + e2.setTime(e.getTime()); + e2.setComment(e.getComment()); + e2.setExtra(e.getExtra()); + if (e.getMethod() == ZipEntry.STORED) { + e2.setSize(e.getSize()); + e2.setCrc(e.getCrc()); + } + zos.putNextEntry(e2); + while ((n = zis.read(buf, 0, buf.length)) != -1) { + zos.write(buf, 0, n); + } + } else { // replace with the new files + File f = entryMap.get(name); + addFile(zos, f); + entryMap.remove(name); + entries.remove(f); + } + } + + // add the remaining new files + for (File f: entries) { + addFile(zos, f); + } + zis.close(); + zos.close(); + return updateOk; + } + + private String entryName(String name) { + name = name.replace(File.separatorChar, '/'); + String matchPath = ""; + for (String path : paths) { + if (name.startsWith(path) && (path.length() > matchPath.length())) { + matchPath = path; + } + } + name = name.substring(matchPath.length()); + + if (name.startsWith("/")) { + name = name.substring(1); + } else if (name.startsWith("./")) { + name = name.substring(2); + } + return name; + } + + void addFile(ZipOutputStream zos, File file) throws IOException { + String name = file.getPath(); + boolean isDir = file.isDirectory(); + if (isDir) { + name = name.endsWith(File.separator) ? name : + (name + File.separator); + } + name = entryName(name); + + if (name.equals("") || name.equals(".") || name.equals(zname)) { + return; + } + + long size = isDir ? 0 : file.length(); + + if (vflag) { + out.print(formatMsg("out.adding", name)); + } + ZipEntry e = new ZipEntry(name); + e.setTime(file.lastModified()); + if (size == 0) { + e.setMethod(ZipEntry.STORED); + e.setSize(0); + e.setCrc(0); + } else if (flag0) { + e.setSize(size); + e.setMethod(ZipEntry.STORED); + crc32File(e, file); + } + zos.putNextEntry(e); + if (!isDir) { + byte[] buf = new byte[8192]; + int len; + InputStream is = new BufferedInputStream(new FileInputStream(file)); + while ((len = is.read(buf, 0, buf.length)) != -1) { + zos.write(buf, 0, len); + } + is.close(); + } + zos.closeEntry(); + /* report how much compression occurred. */ + if (vflag) { + size = e.getSize(); + long csize = e.getCompressedSize(); + out.print(formatMsg2("out.size", String.valueOf(size), + String.valueOf(csize))); + if (e.getMethod() == ZipEntry.DEFLATED) { + long ratio = 0; + if (size != 0) { + ratio = ((size - csize) * 100) / size; + } + output(formatMsg("out.deflated", String.valueOf(ratio))); + } else { + output(getMsg("out.stored")); + } + } + } + + private void crc32File(ZipEntry e, File f) throws IOException { + InputStream is = new BufferedInputStream(new FileInputStream(f)); + byte[] buf = new byte[8192]; + crc32.reset(); + int r = 0; + int nread = 0; + long len = f.length(); + while ((r = is.read(buf)) != -1) { + nread += r; + crc32.update(buf, 0, r); + } + is.close(); + if (nread != (int) len) { + throw new ZipException(formatMsg( + "error.incorrect.length", f.getPath())); + } + e.setCrc(crc32.getValue()); + } + + void replaceFSC(String files[]) { + if (files != null) { + for (String file : files) { + file = file.replace(File.separatorChar, '/'); + } + } + } + + Set newDirSet() { + return new HashSet() { + public boolean add(ZipEntry e) { + return (e == null || super.add(e)); + }}; + } + + void updateLastModifiedTime(Set zes) throws IOException { + for (ZipEntry ze : zes) { + long lastModified = ze.getTime(); + if (lastModified != -1) { + File f = new File(ze.getName().replace('/', File.separatorChar)); + f.setLastModified(lastModified); + } + } + } + + void extract(InputStream in, String files[]) throws IOException { + ZipInputStream zis = new ZipInputStream(in, cs); + ZipEntry e; + Set dirs = newDirSet(); + while ((e = zis.getNextEntry()) != null) { + if (files == null) { + dirs.add(extractFile(zis, e)); + } else { + String name = e.getName(); + for (String file : files) { + if (name.startsWith(file)) { + dirs.add(extractFile(zis, e)); + break; + } + } + } + } + updateLastModifiedTime(dirs); + } + + void extract(String fname, String files[]) throws IOException { + ZipFile zf = new ZipFile(fname, cs); + Set dirs = newDirSet(); + Enumeration zes = zf.entries(); + while (zes.hasMoreElements()) { + ZipEntry e = zes.nextElement(); + InputStream is; + if (files == null) { + dirs.add(extractFile(zf.getInputStream(e), e)); + } else { + String name = e.getName(); + for (String file : files) { + if (name.startsWith(file)) { + dirs.add(extractFile(zf.getInputStream(e), e)); + break; + } + } + } + } + zf.close(); + updateLastModifiedTime(dirs); + } + + ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException { + ZipEntry rc = null; + String name = e.getName(); + File f = new File(e.getName().replace('/', File.separatorChar)); + if (e.isDirectory()) { + if (f.exists()) { + if (!f.isDirectory()) { + throw new IOException(formatMsg("error.create.dir", + f.getPath())); + } + } else { + if (!f.mkdirs()) { + throw new IOException(formatMsg("error.create.dir", + f.getPath())); + } else { + rc = e; + } + } + if (vflag) { + output(formatMsg("out.create", name)); + } + } else { + if (f.getParent() != null) { + File d = new File(f.getParent()); + if (!d.exists() && !d.mkdirs() || !d.isDirectory()) { + throw new IOException(formatMsg( + "error.create.dir", d.getPath())); + } + } + OutputStream os = new FileOutputStream(f); + byte[] b = new byte[8192]; + int len; + try { + while ((len = is.read(b, 0, b.length)) != -1) { + os.write(b, 0, len); + } + } finally { + if (is instanceof ZipInputStream) + ((ZipInputStream)is).closeEntry(); + else + is.close(); + os.close(); + } + if (vflag) { + if (e.getMethod() == ZipEntry.DEFLATED) { + output(formatMsg("out.inflated", name)); + } else { + output(formatMsg("out.extracted", name)); + } + } + } + long lastModified = e.getTime(); + if (lastModified != -1) { + f.setLastModified(lastModified); + } + return rc; + } + + void list(InputStream in, String files[]) throws IOException { + ZipInputStream zis = new ZipInputStream(in, cs); + ZipEntry e; + while ((e = zis.getNextEntry()) != null) { + zis.closeEntry(); + printEntry(e, files); + } + } + + void list(String fname, String files[]) throws IOException { + ZipFile zf = new ZipFile(fname, cs); + Enumeration zes = zf.entries(); + while (zes.hasMoreElements()) { + printEntry(zes.nextElement(), files); + } + zf.close(); + } + + void printEntry(ZipEntry e, String[] files) throws IOException { + if (files == null) { + printEntry(e); + } else { + String name = e.getName(); + for (String file : files) { + if (name.startsWith(file)) { + printEntry(e); + return; + } + } + } + } + + void printEntry(ZipEntry e) throws IOException { + if (vflag) { + StringBuilder sb = new StringBuilder(); + String s = Long.toString(e.getSize()); + for (int i = 6 - s.length(); i > 0; --i) { + sb.append(' '); + } + sb.append(s).append(' ').append(new Date(e.getTime()).toString()); + sb.append(' ').append(e.getName()); + output(sb.toString()); + } else { + output(e.getName()); + } + } + + void usageError() { + error( + "Usage: zip {ctxu}[vf0] [zip-file] [-encoding encname][-C dir] files ...\n" + + "Options:\n" + + " -c create new archive\n" + + " -t list table of contents for archive\n" + + " -x extract named (or all) files from archive\n" + + " -u update existing archive\n" + + " -v generate verbose output on standard output\n" + + " -f specify archive file name\n" + + " -0 store only; use no ZIP compression\n" + + " -C change to the specified directory and include the following file\n" + + "If any file is a directory then it is processed recursively.\n"); + } + + void fatalError(Exception e) { + e.printStackTrace(); + } + + + void fatalError(String s) { + error(program + ": " + s); + } + + + protected void output(String s) { + out.println(s); + } + + protected void error(String s) { + err.println(s); + } + + private String getMsg(String key) { + try { + return (rsrc.getString(key)); + } catch (MissingResourceException e) { + throw new Error("Error in message file"); + } + } + + private String formatMsg(String key, String arg) { + String msg = getMsg(key); + String[] args = new String[1]; + args[0] = arg; + return MessageFormat.format(msg, (Object[]) args); + } + + private String formatMsg2(String key, String arg, String arg1) { + String msg = getMsg(key); + String[] args = new String[2]; + args[0] = arg; + args[1] = arg1; + return MessageFormat.format(msg, (Object[]) args); + } + + public static String[] parse(String[] args) throws IOException + { + ArrayList newArgs = new ArrayList(args.length); + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.length() > 1 && arg.charAt(0) == '@') { + arg = arg.substring(1); + if (arg.charAt(0) == '@') { + newArgs.add(arg); + } else { + loadCmdFile(arg, newArgs); + } + } else { + newArgs.add(arg); + } + } + return newArgs.toArray(new String[newArgs.size()]); + } + + private static void loadCmdFile(String name, List args) throws IOException + { + Reader r = new BufferedReader(new FileReader(name)); + StreamTokenizer st = new StreamTokenizer(r); + st.resetSyntax(); + st.wordChars(' ', 255); + st.whitespaceChars(0, ' '); + st.commentChar('#'); + st.quoteChar('"'); + st.quoteChar('\''); + while (st.nextToken() != st.TT_EOF) { + args.add(st.sval); + } + r.close(); + } + + public static void main(String args[]) { + zip z = new zip(System.out, System.err, "zip"); + System.exit(z.run(args) ? 0 : 1); + } +} +