/* * 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 8146431 * @modules java.base/java.util.zip:open * @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<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)); } } } // 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, 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 { // (0) check zero-length entry name, no AIOOBE try { check(zf.getEntry("") == null);; } catch (Throwable t) { unexpected(t); } 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, 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<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");} }