Projects
Mega:24.03:SP1:Everything
openjdk-1.8.0
_service:tar_scm:add-8142508-To-bring-j.u.z.Zip...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:add-8142508-To-bring-j.u.z.ZipFile-s-native-implemen.patch of Package openjdk-1.8.0
From 7590fffae62f5a4157ab7612c186b442c81d5682 Mon Sep 17 00:00:00 2001 Date: Thu, 21 Sep 2023 15:15:39 +0800 Subject: add 8142508-To-bring-j.u.z.ZipFile-s-native-implementati --- jdk/make/mapfiles/libzip/mapfile-vers | 23 +- jdk/make/mapfiles/libzip/reorder-sparc | 17 - jdk/make/mapfiles/libzip/reorder-sparcv9 | 16 - jdk/make/mapfiles/libzip/reorder-x86 | 19 - .../share/classes/java/util/jar/JarFile.java | 5 +- .../share/classes/java/util/zip/ZipCoder.java | 21 +- .../share/classes/java/util/zip/ZipFile.java | 933 ++++++++++++++---- .../share/classes/java/util/zip/ZipUtils.java | 79 +- .../sun/misc/JavaUtilZipFileAccess.java | 1 + jdk/src/share/classes/sun/misc/VM.java | 3 - jdk/src/share/native/java/util/zip/ZipFile.c | 403 -------- jdk/src/share/native/java/util/zip/zip_util.c | 29 +- jdk/src/share/native/java/util/zip/zip_util.h | 1 - jdk/test/java/nio/file/spi/TestProvider.java | 274 ++++- .../java/util/zip/ZipFile/TestZipFile.java | 375 +++++++ .../imageio/plugins/png/ItxtUtf8Test.java | 2 +- 16 files changed, 1440 insertions(+), 761 deletions(-) delete mode 100644 jdk/src/share/native/java/util/zip/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 5d33990c3..e7394ae61 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,26 +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; - Java_java_util_zip_ZipFile_getManifestNum; - ZIP_Close; ZIP_CRC32; ZIP_FindEntry; diff --git a/jdk/make/mapfiles/libzip/reorder-sparc b/jdk/make/mapfiles/libzip/reorder-sparc index 9d3aae88e..b1957dce7 100644 --- a/jdk/make/mapfiles/libzip/reorder-sparc +++ b/jdk/make/mapfiles/libzip/reorder-sparc @@ -15,31 +15,14 @@ text: .text%ZIP_GetEntry; 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_getManifestNum; -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 bf127e9cf..458d4c34f 100644 --- a/jdk/make/mapfiles/libzip/reorder-sparcv9 +++ b/jdk/make/mapfiles/libzip/reorder-sparcv9 @@ -15,20 +15,6 @@ text: .text%ZIP_Lock; text: .text%readLOC: OUTPUTDIR/zip_util.o; 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_getManifestNum; -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_; @@ -36,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%huft_build: OUTPUTDIR/inftrees.o; text: .text%zcfree; @@ -45,6 +30,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 d4e84c38c..62f11a990 100644 --- a/jdk/make/mapfiles/libzip/reorder-x86 +++ b/jdk/make/mapfiles/libzip/reorder-x86 @@ -16,36 +16,17 @@ text: .text%ZIP_Lock; text: .text%readLOC: OUTPUTDIR/zip_util.o; 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_getManifestNum; -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%huft_build: OUTPUTDIR/inftrees.o; 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/share/classes/java/util/jar/JarFile.java b/jdk/src/share/classes/java/util/jar/JarFile.java index a26dcc4a1..2878e175e 100644 --- a/jdk/src/share/classes/java/util/jar/JarFile.java +++ b/jdk/src/share/classes/java/util/jar/JarFile.java @@ -213,7 +213,10 @@ class JarFile extends ZipFile { return man; } - private native String[] getMetaInfEntryNames(); + private String[] getMetaInfEntryNames() { + return sun.misc.SharedSecrets.getJavaUtilZipFileAccess() + .getMetaInfEntryNames((ZipFile)this); + } /** * Returns the <code>JarEntry</code> for the given entry name or diff --git a/jdk/src/share/classes/java/util/zip/ZipCoder.java b/jdk/src/share/classes/java/util/zip/ZipCoder.java index b920b820e..243d6e8c0 100644 --- a/jdk/src/share/classes/java/util/zip/ZipCoder.java +++ b/jdk/src/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/share/classes/java/util/zip/ZipFile.java b/jdk/src/share/classes/java/util/zip/ZipFile.java index 9f8aff6ef..5d9b0de97 100644 --- a/jdk/src/share/classes/java/util/zip/ZipFile.java +++ b/jdk/src/share/classes/java/util/zip/ZipFile.java @@ -30,14 +30,24 @@ import java.io.InputStream; import java.io.IOException; import java.io.EOFException; import java.io.File; +import java.io.FileNotFoundException; +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.nio.file.NoSuchFileException; + 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; @@ -46,7 +56,9 @@ import java.util.jar.JarFile; import java.util.stream.Stream; import java.util.stream.StreamSupport; +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. @@ -59,12 +71,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 int manifestNum = 0; // number of META-INF/MANIFEST.MF, case insensitive + private Source zsrc; + private ZipCoder zc; private static final int STORED = ZipEntry.STORED; private static final int DEFLATED = ZipEntry.DEFLATED; @@ -83,26 +94,11 @@ 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; - private static final boolean ensuretrailingslash; 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"))); - // see getEntry() for details - prop = sun.misc.VM.getSavedProperty("jdk.util.zip.ensureTrailingSlash"); + String prop = sun.misc.VM.getSavedProperty("jdk.util.zip.ensureTrailingSlash"); ensuretrailingslash = prop == null || !prop.equalsIgnoreCase("false"); } @@ -171,8 +167,6 @@ class ZipFile implements ZipConstants, Closeable { this(file, OPEN_READ); } - private ZipCoder zc; - /** * Opens a new <code>ZipFile</code> to read from the specified * <code>File</code> object in the specified mode. The mode argument @@ -224,17 +218,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); - this.manifestNum = getManifestNum(jzfile); } /** @@ -268,6 +258,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 @@ -298,10 +289,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); } } @@ -314,47 +305,29 @@ 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) { - // If no entry is found for the specified 'name' and - // the 'name' does not end with a forward slash '/', - // the implementation tries to find the entry with a - // slash '/' appended to the end of the 'name', before - // returning null. When such entry is found, the name - // that actually is found (with a slash '/' attached) - // is used - // (disabled if jdk.util.zip.ensureTrailingSlash=false) - ZipEntry ze = ensuretrailingslash ? getZipEntry(null, jzentry) - : getZipEntry(name, jzentry); - freeEntry(jzfile, jzentry); - return ze; + int pos = zsrc.getEntryPos(zc.getBytes(name), true); + if (pos != -1) { + return ensuretrailingslash ? getZipEntry(null, pos) + : 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<InputStream, Inflater> streams = new WeakHashMap<>(); /** * Returns an input stream for reading the contents of the specified * zip file entry. - * - * <p> Closing this ZIP file will, in turn, close all input - * streams that have been returned by invocations of this method. + * <p> + * 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 @@ -364,37 +337,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); } @@ -467,8 +441,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; } } @@ -480,7 +454,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); @@ -489,7 +463,7 @@ class ZipFile implements ZipConstants, Closeable { } // List of available Inflater objects for decompression - private Deque<Inflater> inflaterCache = new ArrayDeque<>(); + private final Deque<Inflater> inflaterCache = new ArrayDeque<>(); /** * Returns the path name of the ZIP file. @@ -501,9 +475,13 @@ class ZipFile implements ZipConstants, Closeable { private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { private int i = 0; + private final int entryCount; public ZipEntryIterator() { - ensureOpen(); + synchronized (ZipFile.this) { + ensureOpen(); + this.entryCount = zsrc.total; + } } public boolean hasMoreElements() { @@ -511,10 +489,7 @@ class ZipFile implements ZipConstants, Closeable { } public boolean hasNext() { - synchronized (ZipFile.this) { - ensureOpen(); - return i < total; - } + return i < entryCount; } public ZipEntry nextElement() { @@ -524,28 +499,11 @@ class ZipFile implements ZipConstants, Closeable { public ZipEntry next() { synchronized (ZipFile.this) { ensureOpen(); - if (i >= total) { + if (!hasNext()) { 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)); } } } @@ -575,50 +533,53 @@ class ZipFile implements ZipConstants, Closeable { Spliterator.IMMUTABLE | Spliterator.NONNULL), false); } - 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 (bname == null) { - e.name = ""; // length 0 empty name - } else if (!zc.isUTF8() && (e.flag & EFS) != 0) { - e.name = zc.toStringUTF8(bname, bname.length); + /* Checks ensureOpen() before invoke this method */ + private ZipEntry getZipEntry(String name, int pos) { + byte[] cen = zsrc.cen; + int nlen = CENNAM(cen, pos); + int elen = CENEXT(cen, pos); + int clen = CENCOM(cen, pos); + int flag = CENFLG(cen, pos); + if (name == null) { + if (!zc.isUTF8() && (flag & EFS) != 0) { + name = zc.toStringUTF8(cen, pos + CENHDR, nlen); } else { - e.name = zc.toString(bname, bname.length); - } - } - 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 { - if (!zc.isUTF8() && (e.flag & EFS) != 0) { - e.comment = zc.toStringUTF8(bcomm, bcomm.length); + name = zc.toString(cen, pos + CENHDR, nlen); + } + } + ZipEntry e = new ZipEntry(name); + e.flag = flag; + 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) { + int start = pos + CENHDR + nlen; + e.setExtra0(Arrays.copyOfRange(cen, start, start + elen), true); + } + if (clen != 0) { + int start = pos + CENHDR + nlen + elen; + if (!zc.isUTF8() && (flag & EFS) != 0) { + e.comment = zc.toStringUTF8(cen, start, clen); } else { - e.comment = zc.toString(bcomm, bcomm.length); + e.comment = zc.toString(cen, start, 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; + } } /** @@ -630,14 +591,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<InputStream, Inflater> copy = new HashMap<>(streams); streams.clear(); for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { @@ -649,21 +611,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; } } } @@ -686,14 +644,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."); } } @@ -709,40 +664,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) { @@ -760,11 +774,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(); } @@ -780,17 +799,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); } @@ -805,7 +818,10 @@ class ZipFile implements ZipConstants, Closeable { sun.misc.SharedSecrets.setJavaUtilZipFileAccess( new sun.misc.JavaUtilZipFileAccess() { public boolean startsWithLocHeader(ZipFile zip) { - return zip.startsWithLocHeader(); + return zip.zsrc.startsWithLoc; + } + public String[] getMetaInfEntryNames(ZipFile zip) { + return zip.getMetaInfEntryNames(); } public int getManifestNum(JarFile jar) { return ((ZipFile)jar).getManifestNum(); @@ -815,11 +831,27 @@ class ZipFile implements ZipConstants, Closeable { } /** - * 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 == null) { + return null; + } + String[] names = new String[zsrc.metanames.length]; + byte[] cen = zsrc.cen; + for (int i = 0; i < names.length; i++) { + int pos = zsrc.metanames[i]; + names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos), + StandardCharsets.UTF_8); + } + return names; + } } /* @@ -830,31 +862,506 @@ class ZipFile implements ZipConstants, Closeable { private int getManifestNum() { synchronized (this) { ensureOpen(); - return manifestNum; + return zsrc.manifestNum; } } + + private static class Source { + // "META-INF/".length() + private static final int META_INF_LEN = 9; + private final Key key; // the key in files + private int refs = 1; + + 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 int[] metanames; + private int manifestNum = 0; // number of META-INF/MANIFEST.MF, case insensitive + private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) + + // 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 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<Key, Source> files = new HashMap<>(); + + + public static Source get(File file, boolean toDelete) throws IOException { + Key key = null; + try { + key = new Key(file, + Files.readAttributes(file.toPath(), BasicFileAttributes.class)); + } catch (NoSuchFileException exception) { + throw new FileNotFoundException(exception.getMessage()); + } + 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; + manifestNum = 0; + 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 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 getManifestNum(long jzfile); - private static native int read(long jzfile, long jzentry, - long pos, byte[] b, int off, int len); - - // 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 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); - - private static native String getZipMessage(long jzfile); + 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 + ArrayList<Integer> metanamesList = null; + + // Iterate through the entries in the central directory + int i = 0; + int hsh = 0; + int pos = 0; + int limit = cen.length - ENDHDR; + manifestNum = 0; + 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)) { + if (metanamesList == null) + metanamesList = new ArrayList<>(4); + metanamesList.add(pos); + if (isManifestName(cen, pos + CENHDR + + META_INF_LEN, nlen - META_INF_LEN)) { + manifestNum++; + } + } + // skip ext and comment + pos += (CENHDR + nlen + elen + clen); + i++; + } + total = i; + if (metanamesList != null) { + metanames = new int[metanamesList.size()]; + for (int j = 0, len = metanames.length; j < len; j++) { + metanames[j] = metanamesList.get(j); + } + } + 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; + } + } + + /** + * Returns true if the bytes represent a non-directory name + * beginning with "META-INF/", disregarding ASCII case. + */ + private static boolean isMetaName(byte[] name, int off, int len) { + // Use the "oldest ASCII trick in the book" + return len > 9 // "META-INF/".length() + && name[off + len - 1] != '/' // non-directory + && (name[off++] | 0x20) == 'm' + && (name[off++] | 0x20) == 'e' + && (name[off++] | 0x20) == 't' + && (name[off++] | 0x20) == 'a' + && (name[off++] ) == '-' + && (name[off++] | 0x20) == 'i' + && (name[off++] | 0x20) == 'n' + && (name[off++] | 0x20) == 'f' + && (name[off] ) == '/'; + } + + /* + * Check if the bytes represents a name equals to MANIFEST.MF + */ + private boolean isManifestName(byte[] name, int off, int len) { + return (len == 11 // "MANIFEST.MF".length() + && (name[off++] | 0x20) == 'm' + && (name[off++] | 0x20) == 'a' + && (name[off++] | 0x20) == 'n' + && (name[off++] | 0x20) == 'i' + && (name[off++] | 0x20) == 'f' + && (name[off++] | 0x20) == 'e' + && (name[off++] | 0x20) == 's' + && (name[off++] | 0x20) == 't' + && (name[off++] ) == '.' + && (name[off++] | 0x20) == 'm' + && (name[off] | 0x20) == 'f'); + } + + /* + * 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/share/classes/java/util/zip/ZipUtils.java b/jdk/src/share/classes/java/util/zip/ZipUtils.java index cd8b05278..a9b9db948 100644 --- a/jdk/src/share/classes/java/util/zip/ZipUtils.java +++ b/jdk/src/share/classes/java/util/zip/ZipUtils.java @@ -29,6 +29,8 @@ import java.nio.file.attribute.FileTime; import java.util.Date; import java.util.concurrent.TimeUnit; +import static java.util.zip.ZipConstants.ENDHDR; + class ZipUtils { // used to adjust values between Windows and java epoch @@ -126,7 +128,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); } /** @@ -144,4 +146,79 @@ class ZipUtils { public static final long get64(byte b[], int off) { return get32(b, off) | (get32(b, off+4) << 32); } + + // 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/share/classes/sun/misc/JavaUtilZipFileAccess.java b/jdk/src/share/classes/sun/misc/JavaUtilZipFileAccess.java index 0d931d1db..d140f54a5 100644 --- a/jdk/src/share/classes/sun/misc/JavaUtilZipFileAccess.java +++ b/jdk/src/share/classes/sun/misc/JavaUtilZipFileAccess.java @@ -31,5 +31,6 @@ import java.util.zip.ZipFile; public interface JavaUtilZipFileAccess { public boolean startsWithLocHeader(ZipFile zip); public int getManifestNum(JarFile zip); + public String[] getMetaInfEntryNames(ZipFile zip); } diff --git a/jdk/src/share/classes/sun/misc/VM.java b/jdk/src/share/classes/sun/misc/VM.java index 3e64628c6..cb757b3e3 100644 --- a/jdk/src/share/classes/sun/misc/VM.java +++ b/jdk/src/share/classes/sun/misc/VM.java @@ -310,9 +310,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/share/native/java/util/zip/ZipFile.c b/jdk/src/share/native/java/util/zip/ZipFile.c deleted file mode 100644 index 8e3698290..000000000 --- a/jdk/src/share/native/java/util/zip/ZipFile.c +++ /dev/null @@ -1,403 +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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <ctype.h> -#include <assert.h> -#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; - -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 (mode & OPEN_DELETE) flag |= JVM_O_DELETE; - - if (path != 0) { - zip = ZIP_Get_From_Cache(path, &msg, lastModified); - if (zip == 0 && msg == 0) { - ZFILE zfd = 0; -#ifdef WIN32 - zfd = winFileHandleOpen(env, name, flag); - if (zfd == -1) { - /* Exception already pending. */ - goto finally; - } -#else - zfd = JVM_Open(path, flag, 0); - if (zfd < 0) { - throwFileNotFoundException(env, name); - goto finally; - } -#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 jint JNICALL -Java_java_util_zip_ZipFile_getManifestNum(JNIEnv *env, jclass cls, jlong zfile) -{ - jzfile *zip = jlong_to_ptr(zfile); - - return zip->manifestNum; -} - -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; - 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, - 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/src/share/native/java/util/zip/zip_util.c b/jdk/src/share/native/java/util/zip/zip_util.c index ff59c5ecc..ffe094065 100644 --- a/jdk/src/share/native/java/util/zip/zip_util.c +++ b/jdk/src/share/native/java/util/zip/zip_util.c @@ -74,8 +74,6 @@ static void freeCEN(jzfile *); #define PATH_MAX 1024 #endif -#define META_INF_LEN 9 /* "META-INF/".length() */ - static jint INITIAL_META_COUNT = 2; /* initial number of entries in meta name array */ #ifdef LINUX @@ -475,25 +473,6 @@ isMetaName(const char *name, int length) return 1; } -/* - * Check if the bytes represents a name equals to MANIFEST.MF - */ -static int -isManifestName(const char *name, int length) -{ - const char *s; - if (length != (int)sizeof("MANIFEST.MF") - 1) - return 0; - for (s = "MANIFEST.MF"; *s != '\0'; s++) { - char c = *name++; - // Avoid toupper; it's locale-dependent - if (c >= 'a' && c <= 'z') c += 'A' - 'a'; - if (*s != c) - return 0; - } - return 1; -} - /* * Increases the capacity of zip->metanames. * Returns non-zero in case of allocation error. @@ -564,7 +543,6 @@ freeCEN(jzfile *zip) { free(zip->entries); zip->entries = NULL; free(zip->table); zip->table = NULL; - zip->manifestNum = 0; freeMetaNames(zip); } @@ -718,8 +696,6 @@ readCEN(jzfile *zip, jint knownTotal) for (j = 0; j < tablelen; j++) table[j] = ZIP_ENDCHAIN; - zip->manifestNum = 0; - /* Iterate through the entries in the central directory */ for (i = 0, cp = cenbuf; cp <= cenend - CENHDR; i++, cp += CENSIZE(cp)) { /* Following are unsigned 16-bit */ @@ -747,12 +723,9 @@ readCEN(jzfile *zip, jint knownTotal) ZIP_FORMAT_ERROR("invalid CEN header (bad header size)"); /* if the entry is metadata add it to our metadata names */ - if (isMetaName((char *)cp+CENHDR, nlen)) { - if (isManifestName((char *)cp+CENHDR+META_INF_LEN, nlen-META_INF_LEN)) - zip->manifestNum++; + if (isMetaName((char *)cp+CENHDR, nlen)) if (addMetaName(zip, (char *)cp+CENHDR, nlen) != 0) goto Catch; - } /* Record the CEN offset and the name hash in our hash cell. */ entries[i].cenpos = cenpos + (cp - cenbuf); 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 2a7ae4b7e..a64668cd6 100644 --- a/jdk/src/share/native/java/util/zip/zip_util.h +++ b/jdk/src/share/native/java/util/zip/zip_util.h @@ -229,7 +229,6 @@ typedef struct jzfile { /* Zip file */ char **metanames; /* array of meta names (may have null names) */ jint metacurrent; /* the next empty slot in metanames array */ jint metacount; /* number of slots in metanames array */ - jint manifestNum; /* number of META-INF/MANIFEST.MF, case insensitive */ jlong lastModified; /* last modified time */ jlong locpos; /* position of first LOC header (usually 0) */ } jzfile; diff --git a/jdk/test/java/nio/file/spi/TestProvider.java b/jdk/test/java/nio/file/spi/TestProvider.java index b2744f4c0..c975b8e27 100644 --- a/jdk/test/java/nio/file/spi/TestProvider.java +++ b/jdk/test/java/nio/file/spi/TestProvider.java @@ -21,20 +21,34 @@ * questions. */ -import java.nio.file.spi.FileSystemProvider; +import java.io.File; import java.nio.file.*; -import java.nio.file.attribute.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; import java.nio.channels.SeekableByteChannel; import java.net.URI; -import java.util.*; import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; public class TestProvider extends FileSystemProvider { - private final FileSystem theFileSystem; + private final FileSystemProvider defaultProvider; + private final TestFileSystem theFileSystem; public TestProvider(FileSystemProvider defaultProvider) { - theFileSystem = new TestFileSystem(this); + this.defaultProvider = defaultProvider; + FileSystem fs = defaultProvider.getFileSystem(URI.create("file:/")); + this.theFileSystem = new TestFileSystem(fs, this); + } + + FileSystemProvider defaultProvider() { + return defaultProvider; } @Override @@ -43,8 +57,8 @@ public class TestProvider extends FileSystemProvider { } @Override - public FileSystem newFileSystem(URI uri, Map<String,?> env) { - throw new RuntimeException("not implemented"); + public FileSystem newFileSystem(URI uri, Map<String,?> env) throws IOException { + return defaultProvider.newFileSystem(uri, env); } @Override @@ -54,7 +68,8 @@ public class TestProvider extends FileSystemProvider { @Override public Path getPath(URI uri) { - throw new RuntimeException("not implemented"); + Path path = defaultProvider.getPath(uri); + return theFileSystem.wrap(path); } @Override @@ -70,7 +85,8 @@ public class TestProvider extends FileSystemProvider { LinkOption... options) throws IOException { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(file); + return defaultProvider.readAttributes(delegate, attributes, options); } @Override @@ -79,7 +95,8 @@ public class TestProvider extends FileSystemProvider { LinkOption... options) throws IOException { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(file); + return defaultProvider.readAttributes(delegate, type, options); } @Override @@ -87,13 +104,14 @@ public class TestProvider extends FileSystemProvider { Class<V> type, LinkOption... options) { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(file); + return defaultProvider.getFileAttributeView(delegate, type, options); } - @Override public void delete(Path file) throws IOException { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(file); + defaultProvider.delete(delegate); } @Override @@ -110,10 +128,11 @@ public class TestProvider extends FileSystemProvider { @Override public Path readSymbolicLink(Path link) throws IOException { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(link); + Path target = defaultProvider.readSymbolicLink(delegate); + return theFileSystem.wrap(target); } - @Override public void copy(Path source, Path target, CopyOption... options) throws IOException @@ -140,7 +159,8 @@ public class TestProvider extends FileSystemProvider { public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(dir); + defaultProvider.createDirectory(delegate, attrs); } @Override @@ -149,13 +169,13 @@ public class TestProvider extends FileSystemProvider { FileAttribute<?>... attrs) throws IOException { - throw new RuntimeException("not implemented"); + Path delegate = theFileSystem.unwrap(file); + return defaultProvider.newByteChannel(delegate, options, attrs); } - @Override public boolean isHidden(Path file) throws IOException { - throw new RuntimeException("not implemented"); + throw new ReadOnlyFileSystemException(); } @Override @@ -176,12 +196,26 @@ public class TestProvider extends FileSystemProvider { } static class TestFileSystem extends FileSystem { + private final FileSystem delegate; private final TestProvider provider; - TestFileSystem(TestProvider provider) { + TestFileSystem(FileSystem delegate, TestProvider provider) { + this.delegate = delegate; this.provider = provider; } + Path wrap(Path path) { + return (path != null) ? new TestPath(this, path) : null; + } + + Path unwrap(Path wrapper) { + if (wrapper == null) + throw new NullPointerException(); + if (!(wrapper instanceof TestPath)) + throw new ProviderMismatchException(); + return ((TestPath)wrapper).unwrap(); + } + @Override public FileSystemProvider provider() { return provider; @@ -194,17 +228,17 @@ public class TestProvider extends FileSystemProvider { @Override public boolean isOpen() { - throw new RuntimeException("not implemented"); + return true; } @Override public boolean isReadOnly() { - throw new RuntimeException("not implemented"); + return false; } @Override public String getSeparator() { - throw new RuntimeException("not implemented"); + return delegate.getSeparator(); } @Override @@ -219,27 +253,209 @@ public class TestProvider extends FileSystemProvider { @Override public Set<String> supportedFileAttributeViews() { - throw new RuntimeException("not implemented"); + return delegate.supportedFileAttributeViews(); } @Override public Path getPath(String first, String... more) { - throw new RuntimeException("not implemented"); + Path path = delegate.getPath(first, more); + return wrap(path); } @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { - throw new RuntimeException("not implemented"); + return delegate.getPathMatcher(syntaxAndPattern); } @Override public UserPrincipalLookupService getUserPrincipalLookupService() { - throw new RuntimeException("not implemented"); + return delegate.getUserPrincipalLookupService(); } @Override public WatchService newWatchService() throws IOException { - throw new RuntimeException("not implemented"); + throw new UnsupportedOperationException(); + } + } + + static class TestPath implements Path { + private final TestFileSystem fs; + private final Path delegate; + + TestPath(TestFileSystem fs, Path delegate) { + this.fs = fs; + this.delegate = delegate; + } + + Path unwrap() { + return delegate; + } + + @Override + public FileSystem getFileSystem() { + return fs; + } + + @Override + public boolean isAbsolute() { + return delegate.isAbsolute(); + } + + @Override + public Path getRoot() { + return fs.wrap(delegate.getRoot()); + } + + @Override + public Path getParent() { + return fs.wrap(delegate.getParent()); + } + + @Override + public int getNameCount() { + return delegate.getNameCount(); + } + + @Override + public Path getFileName() { + return fs.wrap(delegate.getFileName()); + } + + @Override + public Path getName(int index) { + return fs.wrap(delegate.getName(index)); + } + + @Override + public Path subpath(int beginIndex, int endIndex) { + return fs.wrap(delegate.subpath(beginIndex, endIndex)); + } + + @Override + public boolean startsWith(Path other) { + return delegate.startsWith(fs.unwrap(other)); + } + + @Override + public boolean startsWith(String other) { + return delegate.startsWith(other); + } + + @Override + public boolean endsWith(Path other) { + return delegate.endsWith(fs.unwrap(other)); + } + + @Override + public boolean endsWith(String other) { + return delegate.endsWith(other); + } + + @Override + public Path normalize() { + return fs.wrap(delegate.normalize()); + } + + @Override + public Path resolve(Path other) { + return fs.wrap(delegate.resolve(fs.unwrap(other))); + } + + @Override + public Path resolve(String other) { + return fs.wrap(delegate.resolve(other)); + } + + @Override + public Path resolveSibling(Path other) { + return fs.wrap(delegate.resolveSibling(fs.unwrap(other))); + } + + @Override + public Path resolveSibling(String other) { + return fs.wrap(delegate.resolveSibling(other)); + } + + @Override + public Path relativize(Path other) { + return fs.wrap(delegate.relativize(fs.unwrap(other))); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof TestPath)) + return false; + return delegate.equals(fs.unwrap((TestPath) other)); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public URI toUri() { + String ssp = delegate.toUri().getSchemeSpecificPart(); + return URI.create(fs.provider().getScheme() + ":" + ssp); + } + + @Override + public Path toAbsolutePath() { + return fs.wrap(delegate.toAbsolutePath()); + } + + @Override + public Path toRealPath(LinkOption... options) throws IOException { + return fs.wrap(delegate.toRealPath(options)); + } + + @Override + public File toFile() { + return new File(toString()); + } + + @Override + public Iterator<Path> iterator() { + final Iterator<Path> itr = delegate.iterator(); + return new Iterator<Path>() { + @Override + public boolean hasNext() { + return itr.hasNext(); + } + @Override + public Path next() { + return fs.wrap(itr.next()); + } + @Override + public void remove() { + itr.remove(); + } + }; + } + + @Override + public int compareTo(Path other) { + return delegate.compareTo(fs.unwrap(other)); + } + + @Override + public WatchKey register(WatchService watcher, + WatchEvent.Kind<?>[] events, + WatchEvent.Modifier... modifiers) + { + throw new UnsupportedOperationException(); + } + + @Override + public WatchKey register(WatchService watcher, + WatchEvent.Kind<?>... events) + { + throw new UnsupportedOperationException(); } } -} +} \ No newline at end of file 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 000000000..30bae3bb9 --- /dev/null +++ b/jdk/test/java/util/zip/ZipFile/TestZipFile.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Huawei Technologies Co., Ltd. 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.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<Path> 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)); + } + } + } + + // This method is used to replace the InputStream.readAllBytes method(need JDK version >= 9). + public static byte[] readAllBytes(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024]; + int len; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + while ((len = inputStream.read(buffer)) > -1) { + outputStream.write(buffer, 0, len); + } + return outputStream.toByteArray(); + } catch (IOException e) { + throw e; + } + } + + // 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<ZipEntry> 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, readAllBytes(is))) { + //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<ZipEntry> 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, readAllBytes(is))); + } + // 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<ZipEntry, byte[]> 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");} +} \ No newline at end of file diff --git a/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java b/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java index 89853c639..74938bcda 100644 --- a/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java +++ b/jdk/test/javax/imageio/plugins/png/ItxtUtf8Test.java @@ -30,7 +30,7 @@ * * @run main ItxtUtf8Test * - * @run main/othervm/timeout=10 -Xmx2m ItxtUtf8Test truncate + * @run main/othervm/timeout=10 -Xmx4m ItxtUtf8Test truncate */ import java.awt.image.BufferedImage; -- 2.22.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2