From 2b91819eec9fb873c4bd0012b45a5a8fe92e510e Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Mon, 14 Dec 2015 09:27:15 -0800 Subject: [PATCH] 8145260: To bring j.u.z.ZipFile's native implementation to Java to remove the expensive jni cost and mmap crash risk [2] Reviewed-by: coffeys --- jdk/make/mapfiles/libzip/mapfile-vers | 22 +- jdk/make/mapfiles/libzip/reorder-sparc | 16 - jdk/make/mapfiles/libzip/reorder-sparcv9 | 15 - jdk/make/mapfiles/libzip/reorder-x86 | 18 - .../share/classes/java/util/jar/JarFile.java | 5 +- .../share/classes/java/util/zip/ZipCoder.java | 21 +- .../share/classes/java/util/zip/ZipFile.java | 853 ++++++++++++++---- .../share/classes/java/util/zip/ZipUtils.java | 79 +- .../internal/misc/JavaUtilZipFileAccess.java | 1 + .../java.base/share/classes/sun/misc/VM.java | 3 - .../java.base/share/native/libzip/ZipFile.c | 406 --------- jdk/test/java/util/zip/TestZipError.java | 3 +- jdk/test/java/util/zip/ZipFile/ReadZip.java | 3 +- .../java/util/zip/ZipFile/TestZipFile.java | 361 ++++++++ 14 files changed, 1130 insertions(+), 676 deletions(-) delete mode 100644 jdk/src/java.base/share/native/libzip/ZipFile.c create mode 100644 jdk/test/java/util/zip/ZipFile/TestZipFile.java diff --git a/jdk/make/mapfiles/libzip/mapfile-vers b/jdk/make/mapfiles/libzip/mapfile-vers index ceace23f26d..c1ab48c10cf 100644 --- a/jdk/make/mapfiles/libzip/mapfile-vers +++ b/jdk/make/mapfiles/libzip/mapfile-vers @@ -1,5 +1,5 @@ # -# Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1997, 2015, Oracle and/or its affiliates. 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 @@ -27,7 +27,6 @@ SUNWprivate_1.1 { global: - Java_java_util_jar_JarFile_getMetaInfEntryNames; Java_java_util_zip_Adler32_update; Java_java_util_zip_Adler32_updateBytes; Java_java_util_zip_Adler32_updateByteBuffer; @@ -48,25 +47,6 @@ 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_ZipFile_close; - Java_java_util_zip_ZipFile_getCommentBytes; - Java_java_util_zip_ZipFile_freeEntry; - Java_java_util_zip_ZipFile_getEntry; - 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_getZipMessage; - Java_java_util_zip_ZipFile_getTotal; - Java_java_util_zip_ZipFile_initIDs; - Java_java_util_zip_ZipFile_open; - Java_java_util_zip_ZipFile_read; - Java_java_util_zip_ZipFile_startsWithLOC; - ZIP_Close; ZIP_CRC32; ZIP_FindEntry; diff --git a/jdk/make/mapfiles/libzip/reorder-sparc b/jdk/make/mapfiles/libzip/reorder-sparc index 154e7998a53..63b2ad6fc28 100644 --- a/jdk/make/mapfiles/libzip/reorder-sparc +++ b/jdk/make/mapfiles/libzip/reorder-sparc @@ -16,30 +16,14 @@ text: .text%ZIP_InflateFully; text: .text%ZIP_Lock; text: .text%ZIP_Unlock; text: .text%ZIP_FreeEntry; -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_startsWithLOC; -text: .text%Java_java_util_zip_ZipFile_getEntry; -text: .text%Java_java_util_zip_ZipFile_freeEntry; -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_; text: .text%zcalloc; text: .text%Java_java_util_zip_Inflater_inflateBytes; -text: .text%Java_java_util_zip_ZipFile_read; text: .text%ZIP_Read; text: .text%zcfree; -text: .text%Java_java_util_jar_JarFile_getMetaInfEntryNames; text: .text%Java_java_util_zip_Inflater_reset; text: .text%Java_java_util_zip_Inflater_end; text: .text%inflateEnd; -text: .text%Java_java_util_zip_ZipFile_close; text: .text%ZIP_Close; diff --git a/jdk/make/mapfiles/libzip/reorder-sparcv9 b/jdk/make/mapfiles/libzip/reorder-sparcv9 index 89690b8dabb..caca118de98 100644 --- a/jdk/make/mapfiles/libzip/reorder-sparcv9 +++ b/jdk/make/mapfiles/libzip/reorder-sparcv9 @@ -15,19 +15,6 @@ text: .text%ZIP_InflateFully; text: .text%ZIP_Lock; text: .text%ZIP_Unlock; text: .text%ZIP_FreeEntry; -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_startsWithLOC; -text: .text%Java_java_util_zip_ZipFile_getEntry; -text: .text%Java_java_util_zip_ZipFile_freeEntry; -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_; @@ -35,7 +22,6 @@ text: .text%zcalloc; text: .text%inflateReset; text: .text%Java_java_util_zip_Inflater_inflateBytes; text: .text%inflate; -text: .text%Java_java_util_zip_ZipFile_read; text: .text%ZIP_Read; text: .text%zcfree; text: .text%Java_java_util_jar_JarFile_getMetaInfEntryNames; @@ -43,6 +29,5 @@ text: .text%ZIP_ReadEntry; text: .text%InflateFully; text: .text%inflateEnd; text: .text%Java_java_util_zip_Inflater_reset; -text: .text%Java_java_util_zip_ZipFile_close; text: .text%ZIP_Close; text: .text%Java_java_util_zip_Inflater_end; diff --git a/jdk/make/mapfiles/libzip/reorder-x86 b/jdk/make/mapfiles/libzip/reorder-x86 index 746948315eb..dfd57c7752e 100644 --- a/jdk/make/mapfiles/libzip/reorder-x86 +++ b/jdk/make/mapfiles/libzip/reorder-x86 @@ -16,34 +16,16 @@ text: .text%ZIP_InflateFully; text: .text%ZIP_Lock; text: .text%ZIP_Unlock; text: .text%ZIP_FreeEntry; -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_startsWithLOC; -text: .text%Java_java_util_zip_ZipFile_getEntry; -text: .text%Java_java_util_zip_ZipFile_freeEntry; -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_; text: .text%zcalloc; text: .text%inflateReset; text: .text%Java_java_util_zip_Inflater_inflateBytes; text: .text%inflate; -text: .text%Java_java_util_zip_ZipFile_read; text: .text%ZIP_Read; text: .text%zcfree; -text: .text%Java_java_util_jar_JarFile_getMetaInfEntryNames; text: .text%ZIP_ReadEntry; text: .text%InflateFully; text: .text%inflateEnd; text: .text%Java_java_util_zip_Inflater_reset; -text: .text%Java_java_util_zip_ZipFile_close; text: .text%ZIP_Close; text: .text%Java_java_util_zip_Inflater_end; diff --git a/jdk/src/java.base/share/classes/java/util/jar/JarFile.java b/jdk/src/java.base/share/classes/java/util/jar/JarFile.java index 6d16a517ac9..62734ceefbd 100644 --- a/jdk/src/java.base/share/classes/java/util/jar/JarFile.java +++ b/jdk/src/java.base/share/classes/java/util/jar/JarFile.java @@ -203,7 +203,10 @@ class JarFile extends ZipFile { return man; } - private native String[] getMetaInfEntryNames(); + private String[] getMetaInfEntryNames() { + return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess() + .getMetaInfEntryNames((ZipFile)this); + } /** * Returns the {@code JarEntry} for the given entry name or diff --git a/jdk/src/java.base/share/classes/java/util/zip/ZipCoder.java b/jdk/src/java.base/share/classes/java/util/zip/ZipCoder.java index b920b820e03..243d6e8c065 100644 --- a/jdk/src/java.base/share/classes/java/util/zip/ZipCoder.java +++ b/jdk/src/java.base/share/classes/java/util/zip/ZipCoder.java @@ -43,7 +43,7 @@ import sun.nio.cs.ArrayEncoder; final class ZipCoder { - String toString(byte[] ba, int length) { + String toString(byte[] ba, int off, int length) { CharsetDecoder cd = decoder().reset(); int len = (int)(length * cd.maxCharsPerByte()); char[] ca = new char[len]; @@ -53,12 +53,12 @@ final class ZipCoder { // CodingErrorAction.REPLACE mode. ZipCoder uses // REPORT mode. if (isUTF8 && cd instanceof ArrayDecoder) { - int clen = ((ArrayDecoder)cd).decode(ba, 0, length, ca); + int clen = ((ArrayDecoder)cd).decode(ba, off, length, ca); if (clen == -1) // malformed throw new IllegalArgumentException("MALFORMED"); return new String(ca, 0, clen); } - ByteBuffer bb = ByteBuffer.wrap(ba, 0, length); + ByteBuffer bb = ByteBuffer.wrap(ba, off, length); CharBuffer cb = CharBuffer.wrap(ca); CoderResult cr = cd.decode(bb, cb, true); if (!cr.isUnderflow()) @@ -69,8 +69,12 @@ final class ZipCoder { return new String(ca, 0, cb.position()); } + String toString(byte[] ba, int length) { + return toString(ba, 0, length); + } + String toString(byte[] ba) { - return toString(ba, ba.length); + return toString(ba, 0, ba.length); } byte[] getBytes(String s) { @@ -111,13 +115,16 @@ final class ZipCoder { return utf8.getBytes(s); } - String toStringUTF8(byte[] ba, int len) { + return toStringUTF8(ba, 0, len); + } + + String toStringUTF8(byte[] ba, int off, int len) { if (isUTF8) - return toString(ba, len); + return toString(ba, off, len); if (utf8 == null) utf8 = new ZipCoder(StandardCharsets.UTF_8); - return utf8.toString(ba, len); + return utf8.toString(ba, off, len); } boolean isUTF8() { diff --git a/jdk/src/java.base/share/classes/java/util/zip/ZipFile.java b/jdk/src/java.base/share/classes/java/util/zip/ZipFile.java index 4e3a6d20417..1c5eb9c90b5 100644 --- a/jdk/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/jdk/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -30,14 +30,22 @@ import java.io.InputStream; import java.io.IOException; import java.io.EOFException; import java.io.File; +import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.Path; +import java.nio.file.Files; + import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.NoSuchElementException; import java.util.Spliterator; import java.util.Spliterators; @@ -47,7 +55,9 @@ import java.util.stream.StreamSupport; import jdk.internal.misc.JavaUtilZipFileAccess; import jdk.internal.misc.SharedSecrets; +import static java.util.zip.ZipConstants.*; import static java.util.zip.ZipConstants64.*; +import static java.util.zip.ZipUtils.*; /** * This class is used to read entries from a zip file. @@ -60,11 +70,11 @@ import static java.util.zip.ZipConstants64.*; */ public class ZipFile implements ZipConstants, Closeable { - private long jzfile; // address of jzfile data + private final String name; // zip file name - private final int total; // total number of entries - private final boolean locsig; // if zip file starts with LOCSIG (usually true) private volatile boolean closeRequested = false; + private Source zsrc; + private ZipCoder zc; private static final int STORED = ZipEntry.STORED; private static final int DEFLATED = ZipEntry.DEFLATED; @@ -83,23 +93,6 @@ class ZipFile implements ZipConstants, Closeable { */ public static final int OPEN_DELETE = 0x4; - static { - /* Zip library is loaded from System.initializeSystemClass */ - initIDs(); - } - - private static native void initIDs(); - - private static final boolean usemmap; - - static { - // A system prpperty to disable mmap use to avoid vm crash when - // in-use zip file is accidently overwritten by others. - String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping"); - usemmap = (prop == null || - !(prop.length() == 0 || prop.equalsIgnoreCase("true"))); - } - /** * Opens a zip file for reading. * @@ -165,8 +158,6 @@ class ZipFile implements ZipConstants, Closeable { this(file, OPEN_READ); } - private ZipCoder zc; - /** * Opens a new {@code ZipFile} to read from the specified * {@code File} object in the specified mode. The mode argument @@ -214,16 +205,13 @@ class ZipFile implements ZipConstants, Closeable { sm.checkDelete(name); } } - if (charset == null) - throw new NullPointerException("charset is null"); + Objects.requireNonNull(charset, "charset"); this.zc = ZipCoder.get(charset); + this.name = name; long t0 = System.nanoTime(); - jzfile = open(name, mode, file.lastModified(), usemmap); + this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0); sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); sun.misc.PerfCounter.getZipFileCount().increment(); - this.name = name; - this.total = getTotal(jzfile); - this.locsig = startsWithLOC(jzfile); } /** @@ -257,6 +245,7 @@ class ZipFile implements ZipConstants, Closeable { /** * Opens a ZIP file for reading given the specified File object. + * * @param file the ZIP file to be opened for reading * @param charset * The {@linkplain java.nio.charset.Charset charset} to be @@ -287,10 +276,10 @@ class ZipFile implements ZipConstants, Closeable { public String getComment() { synchronized (this) { ensureOpen(); - byte[] bcomm = getCommentBytes(jzfile); - if (bcomm == null) + if (zsrc.comment == null) { return null; - return zc.toString(bcomm, bcomm.length); + } + return zc.toString(zsrc.comment); } } @@ -303,38 +292,28 @@ class ZipFile implements ZipConstants, Closeable { * @throws IllegalStateException if the zip file has been closed */ public ZipEntry getEntry(String name) { - if (name == null) { - throw new NullPointerException("name"); - } - long jzentry = 0; + + Objects.requireNonNull(name, "name"); synchronized (this) { ensureOpen(); - jzentry = getEntry(jzfile, zc.getBytes(name), true); - if (jzentry != 0) { - ZipEntry ze = getZipEntry(name, jzentry); - freeEntry(jzfile, jzentry); - return ze; + int pos = zsrc.getEntryPos(zc.getBytes(name), true); + if (pos != -1) { + return getZipEntry(name, pos); } } return null; } - private static native long getEntry(long jzfile, byte[] name, - boolean addSlash); - - // freeEntry releases the C jzentry struct. - private static native void freeEntry(long jzfile, long jzentry); - - // the outstanding inputstreams that need to be closed, + // The outstanding inputstreams that need to be closed, // mapped to the inflater objects they use. private final Map streams = new WeakHashMap<>(); /** * Returns an input stream for reading the contents of the specified * zip file entry. - * - *

Closing this ZIP file will, in turn, close all input - * streams that have been returned by invocations of this method. + *

+ * Closing this ZIP file will, in turn, close all input streams that + * have been returned by invocations of this method. * * @param entry the zip file entry * @return the input stream for reading the contents of the specified @@ -344,37 +323,38 @@ class ZipFile implements ZipConstants, Closeable { * @throws IllegalStateException if the zip file has been closed */ public InputStream getInputStream(ZipEntry entry) throws IOException { - if (entry == null) { - throw new NullPointerException("entry"); - } - long jzentry = 0; + Objects.requireNonNull(entry, "entry"); + int pos = -1; ZipFileInputStream in = null; synchronized (this) { ensureOpen(); if (!zc.isUTF8() && (entry.flag & EFS) != 0) { - jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false); + pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false); } else { - jzentry = getEntry(jzfile, zc.getBytes(entry.name), false); + pos = zsrc.getEntryPos(zc.getBytes(entry.name), false); } - if (jzentry == 0) { + if (pos == -1) { return null; } - in = new ZipFileInputStream(jzentry); - - switch (getEntryMethod(jzentry)) { + in = new ZipFileInputStream(zsrc.cen, pos); + switch (CENHOW(zsrc.cen, pos)) { case STORED: synchronized (streams) { streams.put(in, null); } return in; case DEFLATED: + // Inflater likes a bit of slack // MORE: Compute good size for inflater stream: - long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack - if (size > 65536) size = 8192; - if (size <= 0) size = 4096; + long size = CENLEN(zsrc.cen, pos) + 2; + if (size > 65536) { + size = 8192; + } + if (size <= 0) { + size = 4096; + } Inflater inf = getInflater(); - InputStream is = - new ZipFileInflaterInputStream(in, inf, (int)size); + InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size); synchronized (streams) { streams.put(is, inf); } @@ -447,8 +427,8 @@ class ZipFile implements ZipConstants, Closeable { private Inflater getInflater() { Inflater inf; synchronized (inflaterCache) { - while (null != (inf = inflaterCache.poll())) { - if (false == inf.ended()) { + while ((inf = inflaterCache.poll()) != null) { + if (!inf.ended()) { return inf; } } @@ -460,7 +440,7 @@ class ZipFile implements ZipConstants, Closeable { * Releases the specified inflater to the list of available inflaters. */ private void releaseInflater(Inflater inf) { - if (false == inf.ended()) { + if (!inf.ended()) { inf.reset(); synchronized (inflaterCache) { inflaterCache.add(inf); @@ -469,7 +449,7 @@ class ZipFile implements ZipConstants, Closeable { } // List of available Inflater objects for decompression - private Deque inflaterCache = new ArrayDeque<>(); + private final Deque inflaterCache = new ArrayDeque<>(); /** * Returns the path name of the ZIP file. @@ -493,7 +473,7 @@ class ZipFile implements ZipConstants, Closeable { public boolean hasNext() { synchronized (ZipFile.this) { ensureOpen(); - return i < total; + return i < zsrc.total; } } @@ -504,28 +484,11 @@ class ZipFile implements ZipConstants, Closeable { public ZipEntry next() { synchronized (ZipFile.this) { ensureOpen(); - if (i >= total) { + if (i >= zsrc.total) { throw new NoSuchElementException(); } - long jzentry = getNextEntry(jzfile, i++); - if (jzentry == 0) { - String message; - if (closeRequested) { - message = "ZipFile concurrently closed"; - } else { - message = getZipMessage(ZipFile.this.jzfile); - } - throw new ZipError("jzentry == 0" + - ",\n jzfile = " + ZipFile.this.jzfile + - ",\n total = " + ZipFile.this.total + - ",\n name = " + ZipFile.this.name + - ",\n i = " + i + - ",\n message = " + message - ); - } - ZipEntry ze = getZipEntry(null, jzentry); - freeEntry(jzfile, jzentry); - return ze; + // each "entry" has 3 ints in table entries + return getZipEntry(null, zsrc.getEntryPos(i++ * 3)); } } @@ -559,48 +522,53 @@ class ZipFile implements ZipConstants, Closeable { Spliterator.IMMUTABLE | Spliterator.NONNULL), false); } - private ZipEntry getZipEntry(String name, long jzentry) { + /* Checks ensureOpen() before invoke this method */ + private ZipEntry getZipEntry(String name, int pos) { + byte[] cen = zsrc.cen; ZipEntry e = new ZipEntry(); - e.flag = getEntryFlag(jzentry); // get the flag first + int nlen = CENNAM(cen, pos); + int elen = CENEXT(cen, pos); + int clen = CENCOM(cen, pos); + e.flag = CENFLG(cen, pos); // 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); + e.name = zc.toStringUTF8(cen, pos + CENHDR, nlen); } else { - e.name = zc.toString(bname, bname.length); + e.name = zc.toString(cen, pos + CENHDR, nlen); } } - e.xdostime = getEntryTime(jzentry); - e.crc = getEntryCrc(jzentry); - e.size = getEntrySize(jzentry); - e.csize = getEntryCSize(jzentry); - e.method = getEntryMethod(jzentry); - e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false); - byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); - if (bcomm == null) { - e.comment = null; - } else { + e.xdostime = CENTIM(cen, pos); + e.crc = CENCRC(cen, pos); + e.size = CENLEN(cen, pos); + e.csize = CENSIZ(cen, pos); + e.method = CENHOW(cen, pos); + if (elen != 0) { + e.setExtra0(Arrays.copyOfRange(cen, pos + CENHDR + nlen, + pos + CENHDR + nlen + elen), true); + } + if (clen != 0) { if (!zc.isUTF8() && (e.flag & EFS) != 0) { - e.comment = zc.toStringUTF8(bcomm, bcomm.length); + e.comment = zc.toStringUTF8(cen, pos + CENHDR + nlen + elen, clen); } else { - e.comment = zc.toString(bcomm, bcomm.length); + e.comment = zc.toString(cen, pos + CENHDR + nlen + elen, clen); } } return e; } - private static native long getNextEntry(long jzfile, int i); - /** * Returns the number of entries in the ZIP file. + * * @return the number of entries in the ZIP file * @throws IllegalStateException if the zip file has been closed */ public int size() { - ensureOpen(); - return total; + synchronized (this) { + ensureOpen(); + return zsrc.total; + } } /** @@ -612,14 +580,15 @@ class ZipFile implements ZipConstants, Closeable { * @throws IOException if an I/O error has occurred */ public void close() throws IOException { - if (closeRequested) + if (closeRequested) { return; + } closeRequested = true; synchronized (this) { // Close streams, release their inflaters synchronized (streams) { - if (false == streams.isEmpty()) { + if (!streams.isEmpty()) { Map copy = new HashMap<>(streams); streams.clear(); for (Map.Entry e : copy.entrySet()) { @@ -631,21 +600,17 @@ class ZipFile implements ZipConstants, Closeable { } } } - // Release cached inflaters - Inflater inf; synchronized (inflaterCache) { - while (null != (inf = inflaterCache.poll())) { + Inflater inf; + while ((inf = inflaterCache.poll()) != null) { inf.end(); } } - - if (jzfile != 0) { - // Close the zip file - long zf = this.jzfile; - jzfile = 0; - - close(zf); + // Release zip src + if (zsrc != null) { + Source.close(zsrc); + zsrc = null; } } } @@ -668,14 +633,11 @@ class ZipFile implements ZipConstants, Closeable { close(); } - private static native void close(long jzfile); - private void ensureOpen() { if (closeRequested) { throw new IllegalStateException("zip file closed"); } - - if (jzfile == 0) { + if (zsrc == null) { throw new IllegalStateException("The object is not initialized."); } } @@ -691,40 +653,99 @@ class ZipFile implements ZipConstants, Closeable { * (possibly compressed) zip file entry. */ private class ZipFileInputStream extends InputStream { - private volatile boolean zfisCloseRequested = false; - protected long jzentry; // address of jzentry data + private volatile boolean closeRequested = false; private long pos; // current position within entry data protected long rem; // number of remaining bytes within entry protected long size; // uncompressed size of this entry - ZipFileInputStream(long jzentry) { - pos = 0; - rem = getEntryCSize(jzentry); - size = getEntrySize(jzentry); - this.jzentry = jzentry; + ZipFileInputStream(byte[] cen, int cenpos) throws IOException { + rem = CENSIZ(cen, cenpos); + size = CENLEN(cen, cenpos); + pos = CENOFF(cen, cenpos); + // zip64 + if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || + pos == ZIP64_MAGICVAL) { + checkZIP64(cen, cenpos); + } + // negative for lazy initialization, see getDataOffset(); + pos = - (pos + ZipFile.this.zsrc.locpos); + } + + private void checkZIP64(byte[] cen, int cenpos) throws IOException { + int off = cenpos + CENHDR + CENNAM(cen, cenpos); + int end = off + CENEXT(cen, cenpos); + while (off + 4 < end) { + int tag = get16(cen, off); + int sz = get16(cen, off + 2); + off += 4; + if (off + sz > end) // invalid data + break; + if (tag == EXTID_ZIP64) { + if (size == ZIP64_MAGICVAL) { + if (sz < 8 || (off + 8) > end) + break; + size = get64(cen, off); + sz -= 8; + off += 8; + } + if (rem == ZIP64_MAGICVAL) { + if (sz < 8 || (off + 8) > end) + break; + rem = get64(cen, off); + sz -= 8; + off += 8; + } + if (pos == ZIP64_MAGICVAL) { + if (sz < 8 || (off + 8) > end) + break; + pos = get64(cen, off); + sz -= 8; + off += 8; + } + break; + } + off += sz; + } + } + + /* The Zip file spec explicitly allows the LOC extra data size to + * be different from the CEN extra data size. Since we cannot trust + * the CEN extra data size, we need to read the LOC to determine + * the entry data offset. + */ + private long initDataOffset() throws IOException { + if (pos <= 0) { + byte[] loc = new byte[LOCHDR]; + pos = -pos; + int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos); + if (len != LOCHDR) { + throw new ZipException("ZipFile error reading zip file"); + } + if (LOCSIG(loc) != LOCSIG) { + throw new ZipException("ZipFile invalid LOC header (bad signature)"); + } + pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc); + } + return pos; } public int read(byte b[], int off, int len) throws IOException { synchronized (ZipFile.this) { - long rem = this.rem; - long pos = this.pos; + ensureOpenOrZipException(); + initDataOffset(); if (rem == 0) { return -1; } - if (len <= 0) { - return 0; - } if (len > rem) { len = (int) rem; } - - // Check if ZipFile open - ensureOpenOrZipException(); - len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b, - off, len); + if (len <= 0) { + return 0; + } + len = ZipFile.this.zsrc.readAt(b, off, len, pos); if (len > 0) { - this.pos = (pos + len); - this.rem = (rem - len); + pos += len; + rem -= len; } } if (rem == 0) { @@ -742,11 +763,16 @@ class ZipFile implements ZipConstants, Closeable { } } - public long skip(long n) { - if (n > rem) - n = rem; - pos += n; - rem -= n; + public long skip(long n) throws IOException { + synchronized (ZipFile.this) { + ensureOpenOrZipException(); + initDataOffset(); + if (n > rem) { + n = rem; + } + pos += n; + rem -= n; + } if (rem == 0) { close(); } @@ -762,17 +788,11 @@ class ZipFile implements ZipConstants, Closeable { } public void close() { - if (zfisCloseRequested) + if (closeRequested) { return; - zfisCloseRequested = true; - - rem = 0; - synchronized (ZipFile.this) { - if (jzentry != 0 && ZipFile.this.jzfile != 0) { - freeEntry(ZipFile.this.jzfile, jzentry); - jzentry = 0; - } } + closeRequested = true; + rem = 0; synchronized (streams) { streams.remove(this); } @@ -787,40 +807,501 @@ class ZipFile implements ZipConstants, Closeable { SharedSecrets.setJavaUtilZipFileAccess( new JavaUtilZipFileAccess() { public boolean startsWithLocHeader(ZipFile zip) { - return zip.startsWithLocHeader(); + return zip.zsrc.startsWithLoc; } - } + public String[] getMetaInfEntryNames(ZipFile zip) { + return zip.getMetaInfEntryNames(); + } + } ); } - /** - * Returns {@code true} if, and only if, the zip file begins with {@code - * LOCSIG}. + /* + * Returns an array of strings representing the names of all entries + * that begin with "META-INF/" (case ignored). This method is used + * in JarFile, via SharedSecrets, as an optimization when looking up + * manifest and signature file entries. Returns null if no entries + * were found. */ - private boolean startsWithLocHeader() { - return locsig; + private String[] getMetaInfEntryNames() { + synchronized (this) { + ensureOpen(); + if (zsrc.metanames.size() == 0) { + return null; + } + String[] names = new String[zsrc.metanames.size()]; + byte[] cen = zsrc.cen; + for (int i = 0; i < names.length; i++) { + int pos = zsrc.metanames.get(i); + names[i] = zc.toStringUTF8(cen, pos + CENHDR, CENNAM(cen, pos)); + } + return names; + } } - private static native long open(String name, int mode, long lastModified, - boolean usemmap) throws IOException; - private static native int getTotal(long jzfile); - private static native boolean startsWithLOC(long jzfile); - private static native int read(long jzfile, long jzentry, - long pos, byte[] b, int off, int len); + private static class Source { + private final Key key; // the key in files + private int refs = 1; - // 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 byte[] getCommentBytes(long jzfile); + private RandomAccessFile zfile; // zfile of the underlying zip file + private byte[] cen; // CEN & ENDHDR + private long locpos; // position of first LOC header (usually 0) + private byte[] comment; // zip file comment + // list of meta entries in META-INF dir + private ArrayList metanames = new ArrayList<>(); + private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) - 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); + // A Hashmap for all entries. + // + // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR, + // We might have a lot of these in a typical system. In order to save space we don't + // keep the name in memory, but merely remember a 32 bit {@code hash} value of the + // entry name and its offset {@code pos} in the central directory hdeader. + // + // private static class Entry { + // int hash; // 32 bit hashcode on name + // int next; // hash chain: index into entries + // int pos; // Offset of central directory file header + // } + // private Entry[] entries; // array of hashed cen entry + // + // To reduce the total size of entries further, we use a int[] here to store 3 "int" + // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be + // referred by their index of their positions in the {@code entries}. + // + private int[] entries; // array of hashed cen entry + private int addEntry(int index, int hash, int next, int pos) { + entries[index++] = hash; + entries[index++] = next; + entries[index++] = pos; + return index; + } + private int getEntryHash(int index) { return entries[index]; } + private int getEntryNext(int index) { return entries[index + 1]; } + private int getEntryPos(int index) { return entries[index + 2]; } + private static final int ZIP_ENDCHAIN = -1; + private int total; // total number of entries + private int[] table; // Hash chain heads: indexes into entries + private int tablelen; // number of hash heads - private static native String getZipMessage(long jzfile); + private static class Key { + BasicFileAttributes attrs; + File file; + + public Key(File file, BasicFileAttributes attrs) { + this.attrs = attrs; + this.file = file; + } + + public int hashCode() { + long t = attrs.lastModifiedTime().toMillis(); + return ((int)(t ^ (t >>> 32))) + file.hashCode(); + } + + public boolean equals(Object obj) { + if (obj instanceof Key) { + Key key = (Key)obj; + if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { + return false; + } + Object fk = attrs.fileKey(); + if (fk != null) { + return fk.equals(key.attrs.fileKey()); + } else { + return file.equals(key.file); + } + } + return false; + } + } + private static final HashMap files = new HashMap<>(); + + + public static Source get(File file, boolean toDelete) throws IOException { + Key key = new Key(file, + Files.readAttributes(file.toPath(), BasicFileAttributes.class)); + Source src = null; + synchronized (files) { + src = files.get(key); + if (src != null) { + src.refs++; + return src; + } + } + src = new Source(key, toDelete); + + synchronized (files) { + if (files.containsKey(key)) { // someone else put in first + src.close(); // close the newly created one + src = files.get(key); + src.refs++; + return src; + } + files.put(key, src); + return src; + } + } + + private static void close(Source src) throws IOException { + synchronized (files) { + if (--src.refs == 0) { + files.remove(src.key); + src.close(); + } + } + } + + private Source(Key key, boolean toDelete) throws IOException { + this.key = key; + this.zfile = new RandomAccessFile(key.file, "r"); + if (toDelete) { + key.file.delete(); + } + try { + initCEN(-1); + byte[] buf = new byte[4]; + readFullyAt(buf, 0, 4, 0); + this.startsWithLoc = (LOCSIG(buf) == LOCSIG); + } catch (IOException x) { + try { + this.zfile.close(); + } catch (IOException xx) {} + throw x; + } + } + + private void close() throws IOException { + zfile.close(); + zfile = null; + cen = null; + entries = null; + table = null; + metanames = null; + } + + private static final int BUF_SIZE = 8192; + private final int readFullyAt(byte[] buf, int off, int len, long pos) + throws IOException + { + synchronized(zfile) { + zfile.seek(pos); + int N = len; + while (N > 0) { + int n = Math.min(BUF_SIZE, N); + zfile.readFully(buf, off, n); + off += n; + N -= n; + } + return len; + } + } + + private final int readAt(byte[] buf, int off, int len, long pos) + throws IOException + { + synchronized(zfile) { + zfile.seek(pos); + return zfile.read(buf, off, len); + } + } + + private static final int hashN(byte[] a, int off, int len) { + int h = 1; + while (len-- > 0) { + h = 31 * h + a[off++]; + } + return h; + } + + private static final int hash_append(int hash, byte b) { + return hash * 31 + b; + } + + private static class End { + int centot; // 4 bytes + long cenlen; // 4 bytes + long cenoff; // 4 bytes + long endpos; // 4 bytes + } + + /* + * Searches for end of central directory (END) header. The contents of + * the END header will be read and placed in endbuf. Returns the file + * position of the END header, otherwise returns -1 if the END header + * was not found or an error occurred. + */ + private End findEND() throws IOException { + long ziplen = zfile.length(); + if (ziplen <= 0) + zerror("zip file is empty"); + End end = new End(); + byte[] buf = new byte[READBLOCKSZ]; + long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0; + long minPos = minHDR - (buf.length - ENDHDR); + for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) { + int off = 0; + if (pos < 0) { + // Pretend there are some NUL bytes before start of file + off = (int)-pos; + Arrays.fill(buf, 0, off, (byte)0); + } + int len = buf.length - off; + if (readFullyAt(buf, off, len, pos + off) != len ) { + zerror("zip END header not found"); + } + // Now scan the block backwards for END header signature + for (int i = buf.length - ENDHDR; i >= 0; i--) { + if (buf[i+0] == (byte)'P' && + buf[i+1] == (byte)'K' && + buf[i+2] == (byte)'\005' && + buf[i+3] == (byte)'\006') { + // Found ENDSIG header + byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR); + end.centot = ENDTOT(endbuf); + end.cenlen = ENDSIZ(endbuf); + end.cenoff = ENDOFF(endbuf); + end.endpos = pos + i; + int comlen = ENDCOM(endbuf); + if (end.endpos + ENDHDR + comlen != ziplen) { + // ENDSIG matched, however the size of file comment in it does + // not match the real size. One "common" cause for this problem + // is some "extra" bytes are padded at the end of the zipfile. + // Let's do some extra verification, we don't care about the + // performance in this situation. + byte[] sbuf = new byte[4]; + long cenpos = end.endpos - end.cenlen; + long locpos = cenpos - end.cenoff; + if (cenpos < 0 || + locpos < 0 || + readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 || + GETSIG(sbuf) != CENSIG || + readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 || + GETSIG(sbuf) != LOCSIG) { + continue; + } + } + if (comlen > 0) { // this zip file has comlen + comment = new byte[comlen]; + if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) { + zerror("zip comment read failed"); + } + } + if (end.cenlen == ZIP64_MAGICVAL || + end.cenoff == ZIP64_MAGICVAL || + end.centot == ZIP64_MAGICCOUNT) + { + // need to find the zip64 end; + try { + byte[] loc64 = new byte[ZIP64_LOCHDR]; + if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) + != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { + return end; + } + long end64pos = ZIP64_LOCOFF(loc64); + byte[] end64buf = new byte[ZIP64_ENDHDR]; + if (readFullyAt(end64buf, 0, end64buf.length, end64pos) + != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { + return end; + } + // end64 found, re-calcualte everything. + end.cenlen = ZIP64_ENDSIZ(end64buf); + end.cenoff = ZIP64_ENDOFF(end64buf); + end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g + end.endpos = end64pos; + } catch (IOException x) {} // no zip64 loc/end + } + return end; + } + } + } + zerror("zip END header not found"); + return null; //make compiler happy + } + + // Reads zip file central directory. + private void initCEN(int knownTotal) throws IOException { + if (knownTotal == -1) { + End end = findEND(); + if (end.endpos == 0) { + locpos = 0; + total = 0; + entries = new int[0]; + cen = null; + return; // only END header present + } + if (end.cenlen > end.endpos) + zerror("invalid END header (bad central directory size)"); + long cenpos = end.endpos - end.cenlen; // position of CEN table + // Get position of first local file (LOC) header, taking into + // account that there may be a stub prefixed to the zip file. + locpos = cenpos - end.cenoff; + if (locpos < 0) { + zerror("invalid END header (bad central directory offset)"); + } + // read in the CEN and END + cen = new byte[(int)(end.cenlen + ENDHDR)]; + if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { + zerror("read CEN tables failed"); + } + total = end.centot; + } else { + total = knownTotal; + } + // hash table for entries + entries = new int[total * 3]; + tablelen = ((total/2) | 1); // Odd -> fewer collisions + table = new int[tablelen]; + Arrays.fill(table, ZIP_ENDCHAIN); + int idx = 0; + int hash = 0; + int next = -1; + + // list for all meta entries + metanames = new ArrayList<>(); + + // Iterate through the entries in the central directory + int i = 0; + int hsh = 0; + int pos = 0; + int limit = cen.length - ENDHDR; + while (pos + CENHDR <= limit) { + if (i >= total) { + // This will only happen if the zip file has an incorrect + // ENDTOT field, which usually means it contains more than + // 65535 entries. + initCEN(countCENHeaders(cen, limit)); + return; + } + if (CENSIG(cen, pos) != CENSIG) + zerror("invalid CEN header (bad signature)"); + int method = CENHOW(cen, pos); + int nlen = CENNAM(cen, pos); + int elen = CENEXT(cen, pos); + int clen = CENCOM(cen, pos); + if ((CENFLG(cen, pos) & 1) != 0) + zerror("invalid CEN header (encrypted entry)"); + if (method != STORED && method != DEFLATED) + zerror("invalid CEN header (bad compression method: " + method + ")"); + if (pos + CENHDR + nlen > limit) + zerror("invalid CEN header (bad header size)"); + // Record the CEN offset and the name hash in our hash cell. + hash = hashN(cen, pos + CENHDR, nlen); + hsh = (hash & 0x7fffffff) % tablelen; + next = table[hsh]; + table[hsh] = idx; + idx = addEntry(idx, hash, next, pos); + // Adds name to metanames. + if (isMetaName(cen, pos + CENHDR, nlen)) { + metanames.add(pos); + } + // skip ext and comment + pos += (CENHDR + nlen + elen + clen); + i++; + } + total = i; + if (pos + ENDHDR != cen.length) { + zerror("invalid CEN header (bad header size)"); + } + } + + private static void zerror(String msg) throws ZipException { + throw new ZipException(msg); + } + + /* + * Returns the {@code pos} of the zip cen entry corresponding to the + * specified entry name, or -1 if not found. + */ + private int getEntryPos(byte[] name, boolean addSlash) { + if (total == 0) { + return -1; + } + int hsh = hashN(name, 0, name.length); + int idx = table[(hsh & 0x7fffffff) % tablelen]; + /* + * This while loop is an optimization where a double lookup + * for name and name+/ is being performed. The name char + * array has enough room at the end to try again with a + * slash appended if the first table lookup does not succeed. + */ + while(true) { + /* + * Search down the target hash chain for a entry whose + * 32 bit hash matches the hashed name. + */ + while (idx != ZIP_ENDCHAIN) { + if (getEntryHash(idx) == hsh) { + // The CEN name must match the specfied one + int pos = getEntryPos(idx); + if (name.length == CENNAM(cen, pos)) { + boolean matched = true; + int nameoff = pos + CENHDR; + for (int i = 0; i < name.length; i++) { + if (name[i] != cen[nameoff++]) { + matched = false; + break; + } + } + if (matched) { + return pos; + } + } + } + idx = getEntryNext(idx); + } + /* If not addSlash, or slash is already there, we are done */ + if (!addSlash || name[name.length - 1] == '/') { + return -1; + } + /* Add slash and try once more */ + name = Arrays.copyOf(name, name.length + 1); + name[name.length - 1] = '/'; + hsh = hash_append(hsh, (byte)'/'); + //idx = table[hsh % tablelen]; + idx = table[(hsh & 0x7fffffff) % tablelen]; + addSlash = false; + } + } + + private static byte[] metainf = new byte[] { + 'M', 'E', 'T', 'A', '-', 'I' , 'N', 'F', '/', + }; + + /* + * Returns true if the specified entry's name begins with the string + * "META-INF/" irrespective of case. + */ + private static boolean isMetaName(byte[] name, int off, int len) { + if (len < 9 || (name[off] != 'M' && name[off] != 'm')) { // sizeof("META-INF/") - 1 + return false; + } + off++; + for (int i = 1; i < metainf.length; i++) { + byte c = name[off++]; + // Avoid toupper; it's locale-dependent + if (c >= 'a' && c <= 'z') { + c += 'A' - 'a'; + } + if (metainf[i] != c) { + return false; + } + } + return true; + } + + /* + * Counts the number of CEN headers in a central directory extending + * from BEG to END. Might return a bogus answer if the zip file is + * corrupt, but will not crash. + */ + static int countCENHeaders(byte[] cen, int end) { + int count = 0; + int pos = 0; + while (pos + CENHDR <= end) { + count++; + pos += (CENHDR + CENNAM(cen, pos) + CENEXT(cen, pos) + CENCOM(cen, pos)); + } + return count; + } + } } diff --git a/jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java b/jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java index 81882fdcec6..a6632f0fa83 100644 --- a/jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java +++ b/jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java @@ -31,6 +31,8 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.util.concurrent.TimeUnit; +import static java.util.zip.ZipConstants.ENDHDR; + class ZipUtils { // used to adjust values between Windows and java epoch @@ -133,7 +135,7 @@ class ZipUtils { * The bytes are assumed to be in Intel (little-endian) byte order. */ public static final int get16(byte b[], int off) { - return Byte.toUnsignedInt(b[off]) | (Byte.toUnsignedInt(b[off+1]) << 8); + return (b[off] & 0xff) | ((b[off + 1] & 0xff) << 8); } /** @@ -160,4 +162,79 @@ class ZipUtils { public static final int get32S(byte b[], int off) { return (get16(b, off) | (get16(b, off+2) << 16)); } + + // fields access methods + static final int CH(byte[] b, int n) { + return b[n] & 0xff ; + } + + static final int SH(byte[] b, int n) { + return (b[n] & 0xff) | ((b[n + 1] & 0xff) << 8); + } + + static final long LG(byte[] b, int n) { + return ((SH(b, n)) | (SH(b, n + 2) << 16)) & 0xffffffffL; + } + + static final long LL(byte[] b, int n) { + return (LG(b, n)) | (LG(b, n + 4) << 32); + } + + static final long GETSIG(byte[] b) { + return LG(b, 0); + } + + // local file (LOC) header fields + static final long LOCSIG(byte[] b) { return LG(b, 0); } // signature + static final int LOCVER(byte[] b) { return SH(b, 4); } // version needed to extract + static final int LOCFLG(byte[] b) { return SH(b, 6); } // general purpose bit flags + static final int LOCHOW(byte[] b) { return SH(b, 8); } // compression method + static final long LOCTIM(byte[] b) { return LG(b, 10);} // modification time + static final long LOCCRC(byte[] b) { return LG(b, 14);} // crc of uncompressed data + static final long LOCSIZ(byte[] b) { return LG(b, 18);} // compressed data size + static final long LOCLEN(byte[] b) { return LG(b, 22);} // uncompressed data size + static final int LOCNAM(byte[] b) { return SH(b, 26);} // filename length + static final int LOCEXT(byte[] b) { return SH(b, 28);} // extra field length + + // extra local (EXT) header fields + static final long EXTCRC(byte[] b) { return LG(b, 4);} // crc of uncompressed data + static final long EXTSIZ(byte[] b) { return LG(b, 8);} // compressed size + static final long EXTLEN(byte[] b) { return LG(b, 12);} // uncompressed size + + // end of central directory header (END) fields + static final int ENDSUB(byte[] b) { return SH(b, 8); } // number of entries on this disk + static final int ENDTOT(byte[] b) { return SH(b, 10);} // total number of entries + static final long ENDSIZ(byte[] b) { return LG(b, 12);} // central directory size + static final long ENDOFF(byte[] b) { return LG(b, 16);} // central directory offset + static final int ENDCOM(byte[] b) { return SH(b, 20);} // size of zip file comment + static final int ENDCOM(byte[] b, int off) { return SH(b, off + 20);} + + // zip64 end of central directory recoder fields + static final long ZIP64_ENDTOD(byte[] b) { return LL(b, 24);} // total number of entries on disk + static final long ZIP64_ENDTOT(byte[] b) { return LL(b, 32);} // total number of entries + static final long ZIP64_ENDSIZ(byte[] b) { return LL(b, 40);} // central directory size + static final long ZIP64_ENDOFF(byte[] b) { return LL(b, 48);} // central directory offset + static final long ZIP64_LOCOFF(byte[] b) { return LL(b, 8);} // zip64 end offset + + // central directory header (CEN) fields + static final long CENSIG(byte[] b, int pos) { return LG(b, pos + 0); } + static final int CENVEM(byte[] b, int pos) { return SH(b, pos + 4); } + static final int CENVER(byte[] b, int pos) { return SH(b, pos + 6); } + static final int CENFLG(byte[] b, int pos) { return SH(b, pos + 8); } + static final int CENHOW(byte[] b, int pos) { return SH(b, pos + 10);} + static final long CENTIM(byte[] b, int pos) { return LG(b, pos + 12);} + static final long CENCRC(byte[] b, int pos) { return LG(b, pos + 16);} + static final long CENSIZ(byte[] b, int pos) { return LG(b, pos + 20);} + static final long CENLEN(byte[] b, int pos) { return LG(b, pos + 24);} + static final int CENNAM(byte[] b, int pos) { return SH(b, pos + 28);} + static final int CENEXT(byte[] b, int pos) { return SH(b, pos + 30);} + static final int CENCOM(byte[] b, int pos) { return SH(b, pos + 32);} + static final int CENDSK(byte[] b, int pos) { return SH(b, pos + 34);} + static final int CENATT(byte[] b, int pos) { return SH(b, pos + 36);} + static final long CENATX(byte[] b, int pos) { return LG(b, pos + 38);} + static final long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);} + + // The END header is followed by a variable length comment of size < 64k. + static final long END_MAXLEN = 0xFFFF + ENDHDR; + static final int READBLOCKSZ = 128; } diff --git a/jdk/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java b/jdk/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java index df10fda22ca..9b9b1e85788 100644 --- a/jdk/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java +++ b/jdk/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java @@ -29,5 +29,6 @@ import java.util.zip.ZipFile; public interface JavaUtilZipFileAccess { public boolean startsWithLocHeader(ZipFile zip); + public String[] getMetaInfEntryNames(ZipFile zip); } diff --git a/jdk/src/java.base/share/classes/sun/misc/VM.java b/jdk/src/java.base/share/classes/sun/misc/VM.java index 0c75f10c657..37dc3b38fa1 100644 --- a/jdk/src/java.base/share/classes/sun/misc/VM.java +++ b/jdk/src/java.base/share/classes/sun/misc/VM.java @@ -274,9 +274,6 @@ public class VM { // used by java.lang.Integer.IntegerCache props.remove("java.lang.Integer.IntegerCache.high"); - // used by java.util.zip.ZipFile - props.remove("sun.zip.disableMemoryMapping"); - // used by sun.launcher.LauncherHelper props.remove("sun.java.launcher.diag"); } diff --git a/jdk/src/java.base/share/native/libzip/ZipFile.c b/jdk/src/java.base/share/native/libzip/ZipFile.c deleted file mode 100644 index d7a21a6cf88..00000000000 --- a/jdk/src/java.base/share/native/libzip/ZipFile.c +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (c) 1998, 2015, Oracle and/or its affiliates. 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * Native method support for java.util.zip.ZipFile - */ - -#include -#include -#include -#include -#include -#include -#include "jlong.h" -#include "jvm.h" -#include "jni.h" -#include "jni_util.h" -#include "zip_util.h" -#ifdef WIN32 -#include "io_util_md.h" -#else -#include "io_util.h" -#endif - -#include "java_util_zip_ZipFile.h" -#include "java_util_jar_JarFile.h" - -#define DEFLATED 8 -#define STORED 0 - -static jfieldID jzfileID; - -static int OPEN_READ = java_util_zip_ZipFile_OPEN_READ; -static int OPEN_DELETE = java_util_zip_ZipFile_OPEN_DELETE; - - -/* - * Declare library specific JNI_Onload entry if static build - */ -DEF_STATIC_JNI_OnLoad - -JNIEXPORT void JNICALL -Java_java_util_zip_ZipFile_initIDs(JNIEnv *env, jclass cls) -{ - jzfileID = (*env)->GetFieldID(env, cls, "jzfile", "J"); - assert(jzfileID != 0); -} - -static void -ThrowZipException(JNIEnv *env, const char *msg) -{ - jstring s = NULL; - jobject x; - - if (msg != NULL) { - s = JNU_NewStringPlatform(env, msg); - } - if (s != NULL) { - x = JNU_NewObjectByName(env, - "java/util/zip/ZipException", - "(Ljava/lang/String;)V", s); - if (x != NULL) { - (*env)->Throw(env, x); - } - } -} - -JNIEXPORT jlong JNICALL -Java_java_util_zip_ZipFile_open(JNIEnv *env, jclass cls, jstring name, - jint mode, jlong lastModified, - jboolean usemmap) -{ - const char *path = JNU_GetStringPlatformChars(env, name, 0); - char *msg = 0; - jlong result = 0; - int flag = 0; - jzfile *zip = 0; - - if (mode & OPEN_READ) flag |= O_RDONLY; - - if (path != 0) { - zip = ZIP_Get_From_Cache(path, &msg, lastModified); - if (zip == 0 && msg == 0) { - ZFILE zfd = 0; -#ifdef WIN32 - if (mode & OPEN_DELETE) flag |= O_TEMPORARY; - zfd = winFileHandleOpen(env, name, flag); - if (zfd == -1) { - /* Exception already pending. */ - goto finally; - } -#else - zfd = open(path, flag, 0); - if (zfd < 0) { - throwFileNotFoundException(env, name); - goto finally; - } - if (mode & OPEN_DELETE) { - unlink(path); - } -#endif - zip = ZIP_Put_In_Cache0(path, zfd, &msg, lastModified, usemmap); - } - - if (zip != 0) { - result = ptr_to_jlong(zip); - } else if (msg != 0) { - ThrowZipException(env, msg); - free(msg); - } else if (errno == ENOMEM) { - JNU_ThrowOutOfMemoryError(env, 0); - } else { - ThrowZipException(env, "error in opening zip file"); - } -finally: - JNU_ReleaseStringPlatformChars(env, name, path); - } - return result; -} - -JNIEXPORT jint JNICALL -Java_java_util_zip_ZipFile_getTotal(JNIEnv *env, jclass cls, jlong zfile) -{ - jzfile *zip = jlong_to_ptr(zfile); - - return zip->total; -} - -JNIEXPORT jboolean JNICALL -Java_java_util_zip_ZipFile_startsWithLOC(JNIEnv *env, jclass cls, jlong zfile) -{ - jzfile *zip = jlong_to_ptr(zfile); - - return zip->locsig; -} - -JNIEXPORT void JNICALL -Java_java_util_zip_ZipFile_close(JNIEnv *env, jclass cls, jlong zfile) -{ - ZIP_Close(jlong_to_ptr(zfile)); -} - -JNIEXPORT jlong JNICALL -Java_java_util_zip_ZipFile_getEntry(JNIEnv *env, jclass cls, jlong zfile, - jbyteArray name, jboolean addSlash) -{ -#define MAXNAME 1024 - jzfile *zip = jlong_to_ptr(zfile); - jsize ulen = (*env)->GetArrayLength(env, name); - char buf[MAXNAME+2], *path; - jzentry *ze; - - if (ulen > MAXNAME) { - path = malloc(ulen + 2); - if (path == 0) { - JNU_ThrowOutOfMemoryError(env, 0); - return 0; - } - } else { - path = buf; - } - (*env)->GetByteArrayRegion(env, name, 0, ulen, (jbyte *)path); - path[ulen] = '\0'; - ze = ZIP_GetEntry2(zip, path, (jint)ulen, addSlash); - if (path != buf) { - free(path); - } - return ptr_to_jlong(ze); -} - -JNIEXPORT void JNICALL -Java_java_util_zip_ZipFile_freeEntry(JNIEnv *env, jclass cls, jlong zfile, - jlong zentry) -{ - jzfile *zip = jlong_to_ptr(zfile); - jzentry *ze = jlong_to_ptr(zentry); - ZIP_FreeEntry(zip, ze); -} - -JNIEXPORT jlong JNICALL -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_getEntryMethod(JNIEnv *env, jclass cls, jlong zentry) -{ - jzentry *ze = jlong_to_ptr(zentry); - return ze->csize != 0 ? DEFLATED : STORED; -} - -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_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_getCommentBytes(JNIEnv *env, - jclass cls, - jlong zfile) -{ - jzfile *zip = jlong_to_ptr(zfile); - jbyteArray jba = NULL; - - if (zip->comment != NULL) { - if ((jba = (*env)->NewByteArray(env, zip->clen)) == NULL) - return NULL; - (*env)->SetByteArrayRegion(env, jba, 0, zip->clen, (jbyte*)zip->comment); - } - return jba; -} - -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)ze->nlen; - // Unlike for extra and comment, we never return null for - // an (extremely rarely seen) empty name - if ((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, - jint off, jint len) -{ - jzfile *zip = jlong_to_ptr(zfile); - char *msg; - -#define BUFSIZE 8192 - /* copy via tmp stack buffer: */ - jbyte buf[BUFSIZE]; - - if (len > BUFSIZE) { - len = BUFSIZE; - } - - ZIP_Lock(zip); - len = ZIP_Read(zip, jlong_to_ptr(zentry), pos, buf, len); - msg = zip->msg; - ZIP_Unlock(zip); - if (len != -1) { - (*env)->SetByteArrayRegion(env, bytes, off, len, buf); - } - - if (len == -1) { - if (msg != 0) { - ThrowZipException(env, msg); - } else { - char errmsg[128]; - sprintf(errmsg, "errno: %d, error: %s\n", - errno, "Error reading ZIP file"); - JNU_ThrowIOExceptionWithLastError(env, errmsg); - } - } - - return len; -} - -/* - * Returns an array of strings representing the names of all entries - * that begin with "META-INF/" (case ignored). This native method is - * used in JarFile as an optimization when looking up manifest and - * signature file entries. Returns null if no entries were found. - */ -JNIEXPORT jobjectArray JNICALL -Java_java_util_jar_JarFile_getMetaInfEntryNames(JNIEnv *env, jobject obj) -{ - jlong zfile = (*env)->GetLongField(env, obj, jzfileID); - jzfile *zip; - int i, count; - jobjectArray result = 0; - - if (zfile == 0) { - JNU_ThrowByName(env, - "java/lang/IllegalStateException", "zip file closed"); - return NULL; - } - zip = jlong_to_ptr(zfile); - - /* count the number of valid ZIP metanames */ - count = 0; - if (zip->metanames != 0) { - for (i = 0; i < zip->metacount; i++) { - if (zip->metanames[i] != 0) { - count++; - } - } - } - - /* If some names were found then build array of java strings */ - if (count > 0) { - jclass cls = JNU_ClassString(env); - CHECK_NULL_RETURN(cls, NULL); - result = (*env)->NewObjectArray(env, count, cls, 0); - CHECK_NULL_RETURN(result, NULL); - if (result != 0) { - for (i = 0; i < count; i++) { - jstring str = (*env)->NewStringUTF(env, zip->metanames[i]); - if (str == 0) { - break; - } - (*env)->SetObjectArrayElement(env, result, i, str); - (*env)->DeleteLocalRef(env, str); - } - } - } - return result; -} - -JNIEXPORT jstring JNICALL -Java_java_util_zip_ZipFile_getZipMessage(JNIEnv *env, jclass cls, jlong zfile) -{ - jzfile *zip = jlong_to_ptr(zfile); - char *msg = zip->msg; - if (msg == NULL) { - return NULL; - } - return JNU_NewStringPlatform(env, msg); -} diff --git a/jdk/test/java/util/zip/TestZipError.java b/jdk/test/java/util/zip/TestZipError.java index 90d05c03ba3..5448add92da 100644 --- a/jdk/test/java/util/zip/TestZipError.java +++ b/jdk/test/java/util/zip/TestZipError.java @@ -84,9 +84,10 @@ public class TestZipError { try { while (entries.hasMoreElements()) { ze = entries.nextElement(); + zf.getInputStream(ze).readAllBytes(); } fail("Did not get expected exception"); - } catch (ZipError e) { + } catch (ZipException e) { pass(); } catch (InternalError e) { fail("Caught InternalError instead of expected ZipError"); diff --git a/jdk/test/java/util/zip/ZipFile/ReadZip.java b/jdk/test/java/util/zip/ZipFile/ReadZip.java index 1052642eda7..fe923e81eee 100644 --- a/jdk/test/java/util/zip/ZipFile/ReadZip.java +++ b/jdk/test/java/util/zip/ZipFile/ReadZip.java @@ -30,6 +30,7 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; +import java.nio.file.NoSuchFileException; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.zip.*; @@ -110,6 +111,6 @@ public class ReadZip { "input" + String.valueOf(new java.util.Random().nextInt()) + ".zip"))); - } catch (FileNotFoundException fnfe) {} + } catch (NoSuchFileException nsfe) {} } } diff --git a/jdk/test/java/util/zip/ZipFile/TestZipFile.java b/jdk/test/java/util/zip/ZipFile/TestZipFile.java new file mode 100644 index 00000000000..986877731db --- /dev/null +++ b/jdk/test/java/util/zip/ZipFile/TestZipFile.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8142508 + * @summary Tests various ZipFile apis + * @run main/manual TestZipFile + */ + +import java.io.*; +import java.lang.reflect.Method; +import java.nio.*; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.zip.*; + +public class TestZipFile { + + private static Random r = new Random(); + private static int N = 50; + private static int NN = 10; + private static int ENUM = 10000; + private static int ESZ = 10000; + private static ExecutorService executor = Executors.newFixedThreadPool(20); + private static Set paths = new HashSet<>(); + + static void realMain (String[] args) throws Throwable { + + try { + for (int i = 0; i < N; i++) { + test(r.nextInt(ENUM), r.nextInt(ESZ), false, true); + test(r.nextInt(ENUM), r.nextInt(ESZ), true, true); + } + + for (int i = 0; i < NN; i++) { + test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), false, true); + test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), true, true); + testCachedDelete(); + testCachedOverwrite(); + //test(r.nextInt(ENUM), r.nextInt(ESZ), false, true); + } + + test(70000, 1000, false, true); // > 65536 entry number; + testDelete(); // OPEN_DELETE + + executor.shutdown(); + executor.awaitTermination(10, TimeUnit.MINUTES); + } finally { + for (Path path : paths) { + Files.deleteIfExists(path); + } + } + } + + static void test(int numEntry, int szMax, boolean addPrefix, boolean cleanOld) { + String name = "zftest" + r.nextInt() + ".zip"; + Zip zip = new Zip(name, numEntry, szMax, addPrefix, cleanOld); + for (int i = 0; i < NN; i++) { + executor.submit(() -> doTest(zip)); + } + } + + // test scenario: + // (1) open the ZipFile(zip) with OPEN_READ | OPEN_DELETE + // (2) test the ZipFile works correctly + // (3) check the zip is deleted after ZipFile gets closed + static void testDelete() throws Throwable { + String name = "zftest" + r.nextInt() + ".zip"; + Zip zip = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); + try (ZipFile zf = new ZipFile(new File(zip.name), + ZipFile.OPEN_READ | ZipFile.OPEN_DELETE )) + { + doTest0(zip, zf); + } + Path p = Paths.get(name); + if (Files.exists(p)) { + fail("Failed to delete " + name + " with OPEN_DELETE"); + } + } + + // test scenario: + // (1) keep a ZipFile(zip1) alive (in ZipFile's cache), dont close it + // (2) delete zip1 and create zip2 with the same name the zip1 with zip2 + // (3) zip1 tests should fail, but no crash + // (4) zip2 tasks should all get zip2, then pass normal testing. + static void testCachedDelete() throws Throwable { + String name = "zftest" + r.nextInt() + ".zip"; + Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); + + try (ZipFile zf = new ZipFile(zip1.name)) { + for (int i = 0; i < NN; i++) { + executor.submit(() -> verifyNoCrash(zip1)); + } + // delete the "zip1" and create a new one to test + Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); + /* + System.out.println("========================================"); + System.out.printf(" zip1=%s, mt=%d, enum=%d%n ->attrs=[key=%s, sz=%d, mt=%d]%n", + zip1.name, zip1.lastModified, zip1.entries.size(), + zip1.attrs.fileKey(), zip1.attrs.size(), zip1.attrs.lastModifiedTime().toMillis()); + System.out.printf(" zip2=%s, mt=%d, enum=%d%n ->attrs=[key=%s, sz=%d, mt=%d]%n", + zip2.name, zip2.lastModified, zip2.entries.size(), + zip2.attrs.fileKey(), zip2.attrs.size(), zip2.attrs.lastModifiedTime().toMillis()); + */ + for (int i = 0; i < NN; i++) { + executor.submit(() -> doTest(zip2)); + } + } + } + + // overwrite the "zip1" and create a new one to test. So the two zip files + // have the same fileKey, but probably different lastModified() + static void testCachedOverwrite() throws Throwable { + String name = "zftest" + r.nextInt() + ".zip"; + Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); + try (ZipFile zf = new ZipFile(zip1.name)) { + for (int i = 0; i < NN; i++) { + executor.submit(() -> verifyNoCrash(zip1)); + } + // overwrite the "zip1" with new contents + Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, false); + for (int i = 0; i < NN; i++) { + executor.submit(() -> doTest(zip2)); + } + } + } + + // just check the entries and contents. since the file has been either overwritten + // or deleted/rewritten, we only care if it crahes or not. + static void verifyNoCrash(Zip zip) throws RuntimeException { + try (ZipFile zf = new ZipFile(zip.name)) { + List zlist = new ArrayList(zip.entries.keySet()); + String[] elist = zf.stream().map( e -> e.getName()).toArray(String[]::new); + if (!Arrays.equals(elist, + zlist.stream().map( e -> e.getName()).toArray(String[]::new))) + { + //System.out.printf("++++++ LIST NG [%s] entries.len=%d, expected=%d+++++++%n", + // zf.getName(), elist.length, zlist.size()); + return; + } + for (ZipEntry ze : zlist) { + byte[] zdata = zip.entries.get(ze); + ZipEntry e = zf.getEntry(ze.getName()); + if (e != null) { + checkEqual(e, ze); + if (!e.isDirectory()) { + // check with readAllBytes + try (InputStream is = zf.getInputStream(e)) { + if (!Arrays.equals(zdata, is.readAllBytes())) { + //System.out.printf("++++++ BYTES NG [%s]/[%s] ++++++++%n", + // zf.getName(), ze.getName()); + } + } + } + } + } + } catch (Throwable t) { + // t.printStackTrace(); + // fail(t.toString()); + } + } + + static void checkEqual(ZipEntry x, ZipEntry y) { + if (x.getName().equals(y.getName()) && + x.isDirectory() == y.isDirectory() && + x.getMethod() == y.getMethod() && + (x.getTime() / 2000) == y.getTime() / 2000 && + x.getSize() == y.getSize() && + x.getCompressedSize() == y.getCompressedSize() && + x.getCrc() == y.getCrc() && + x.getComment().equals(y.getComment()) + ) { + pass(); + } else { + fail(x + " not equal to " + y); + System.out.printf(" %s %s%n", x.getName(), y.getName()); + System.out.printf(" %d %d%n", x.getMethod(), y.getMethod()); + System.out.printf(" %d %d%n", x.getTime(), y.getTime()); + System.out.printf(" %d %d%n", x.getSize(), y.getSize()); + System.out.printf(" %d %d%n", x.getCompressedSize(), y.getCompressedSize()); + System.out.printf(" %d %d%n", x.getCrc(), y.getCrc()); + System.out.println("-----------------"); + } + } + + static void doTest(Zip zip) throws RuntimeException { + //Thread me = Thread.currentThread(); + try (ZipFile zf = new ZipFile(zip.name)) { + doTest0(zip, zf); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + static void doTest0(Zip zip, ZipFile zf) throws Throwable { + List list = new ArrayList(zip.entries.keySet()); + // (1) check entry list, in expected order + if (!check(Arrays.equals( + list.stream().map( e -> e.getName()).toArray(String[]::new), + zf.stream().map( e -> e.getName()).toArray(String[]::new)))) { + return; + } + // (2) shuffle, and check each entry and its bytes + Collections.shuffle(list); + for (ZipEntry ze : list) { + byte[] data = zip.entries.get(ze); + ZipEntry e = zf.getEntry(ze.getName()); + checkEqual(e, ze); + if (!e.isDirectory()) { + // check with readAllBytes + try (InputStream is = zf.getInputStream(e)) { + check(Arrays.equals(data, is.readAllBytes())); + } + // check with smaller sized buf + try (InputStream is = zf.getInputStream(e)) { + byte[] buf = new byte[(int)e.getSize()]; + int sz = r.nextInt((int)e.getSize()/4 + 1) + 1; + int off = 0; + int n; + while ((n = is.read(buf, off, buf.length - off)) > 0) { + off += n; + } + check(is.read() == -1); + check(Arrays.equals(data, buf)); + } + } + } + // (3) check getMetaInfEntryNames + String[] metas = list.stream() + .map( e -> e.getName()) + .filter( s -> s.startsWith("META-INF/")) + .sorted() + .toArray(String[]::new); + if (metas.length > 0) { + // meta-inf entries + Method getMetas = ZipFile.class.getDeclaredMethod("getMetaInfEntryNames"); + getMetas.setAccessible(true); + String[] names = (String[])getMetas.invoke(zf); + if (names == null) { + fail("Failed to get metanames from " + zf); + } else { + Arrays.sort(names); + check(Arrays.equals(names, metas)); + } + } + } + + private static class Zip { + String name; + Map entries; + BasicFileAttributes attrs; + long lastModified; + + Zip(String name, int num, int szMax, boolean prefix, boolean clean) { + this.name = name; + entries = new LinkedHashMap<>(num); + try { + Path p = Paths.get(name); + if (clean) { + Files.deleteIfExists(p); + } + paths.add(p); + } catch (Exception x) { + throw (RuntimeException)x; + } + + try (FileOutputStream fos = new FileOutputStream(name); + BufferedOutputStream bos = new BufferedOutputStream(fos); + ZipOutputStream zos = new ZipOutputStream(bos)) + { + if (prefix) { + byte[] bytes = new byte[r.nextInt(1000)]; + r.nextBytes(bytes); + bos.write(bytes); + } + CRC32 crc = new CRC32(); + for (int i = 0; i < num; i++) { + String ename = "entry-" + i + "-name-" + r.nextLong(); + ZipEntry ze = new ZipEntry(ename); + int method = r.nextBoolean() ? ZipEntry.STORED : ZipEntry.DEFLATED; + writeEntry(zos, crc, ze, ZipEntry.STORED, szMax); + } + // add some manifest entries + for (int i = 0; i < r.nextInt(20); i++) { + String meta = "META-INF/" + "entry-" + i + "-metainf-" + r.nextLong(); + ZipEntry ze = new ZipEntry(meta); + writeEntry(zos, crc, ze, ZipEntry.STORED, szMax); + } + } catch (Exception x) { + throw (RuntimeException)x; + } + try { + this.attrs = Files.readAttributes(Paths.get(name), BasicFileAttributes.class); + this.lastModified = new File(name).lastModified(); + } catch (Exception x) { + throw (RuntimeException)x; + } + } + + private void writeEntry(ZipOutputStream zos, CRC32 crc, + ZipEntry ze, int method, int szMax) + throws IOException + { + ze.setMethod(method); + byte[] data = new byte[r.nextInt(szMax + 1)]; + r.nextBytes(data); + if (method == ZipEntry.STORED) { // must set size/csize/crc + ze.setSize(data.length); + ze.setCompressedSize(data.length); + crc.reset(); + crc.update(data); + ze.setCrc(crc.getValue()); + } + ze.setTime(System.currentTimeMillis()); + ze.setComment(ze.getName()); + zos.putNextEntry(ze); + zos.write(data); + zos.closeEntry(); + entries.put(ze, data); + } + } + + //--------------------- Infrastructure --------------------------- + static volatile int passed = 0, failed = 0; + static void pass() {passed++;} + static void pass(String msg) {System.out.println(msg); passed++;} + static void fail() {failed++; Thread.dumpStack();} + static void fail(String msg) {System.out.println(msg); fail();} + static void unexpected(Throwable t) {failed++; t.printStackTrace();} + static void unexpected(Throwable t, String msg) { + System.out.println(msg); failed++; t.printStackTrace();} + static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;} + + public static void main(String[] args) throws Throwable { + try {realMain(args);} catch (Throwable t) {unexpected(t);} + System.out.println("\nPassed = " + passed + " failed = " + failed); + if (failed > 0) throw new AssertionError("Some tests failed");} +}