1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8142508 8146431
27 * @modules java.base/java.util.zip:open
28 * @summary Tests various ZipFile apis
29 * @run main/manual TestZipFile
30 */
31
32import java.io.*;
33import java.lang.reflect.Method;
34import java.nio.*;
35import java.nio.file.*;
36import java.nio.file.attribute.*;
37import java.util.*;
38import java.util.concurrent.*;
39import java.util.zip.*;
40
41public class TestZipFile {
42
43    private static Random r = new Random();
44    private static int    N = 50;
45    private static int    NN = 10;
46    private static int    ENUM = 10000;
47    private static int    ESZ = 10000;
48    private static ExecutorService executor = Executors.newFixedThreadPool(20);
49    private static Set<Path> paths = new HashSet<>();
50
51    static void realMain (String[] args) throws Throwable {
52
53        try {
54            for (int i = 0; i < N; i++) {
55                test(r.nextInt(ENUM), r.nextInt(ESZ), false, true);
56                test(r.nextInt(ENUM), r.nextInt(ESZ), true, true);
57            }
58
59            for (int i = 0; i < NN; i++) {
60                test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), false, true);
61                test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), true, true);
62                testCachedDelete();
63                testCachedOverwrite();
64                //test(r.nextInt(ENUM), r.nextInt(ESZ), false, true);
65            }
66
67            test(70000, 1000, false, true);   // > 65536 entry number;
68            testDelete();                     // OPEN_DELETE
69
70            executor.shutdown();
71            executor.awaitTermination(10, TimeUnit.MINUTES);
72        } finally {
73            for (Path path : paths) {
74                Files.deleteIfExists(path);
75            }
76        }
77    }
78
79    static void test(int numEntry, int szMax, boolean addPrefix, boolean cleanOld) {
80        String name = "zftest" + r.nextInt() + ".zip";
81        Zip zip = new Zip(name, numEntry, szMax, addPrefix, cleanOld);
82        for (int i = 0; i < NN; i++) {
83            executor.submit(() -> doTest(zip));
84        }
85     }
86
87    // test scenario:
88    // (1) open the ZipFile(zip) with OPEN_READ | OPEN_DELETE
89    // (2) test the ZipFile works correctly
90    // (3) check the zip is deleted after ZipFile gets closed
91    static void testDelete() throws Throwable {
92        String name = "zftest" + r.nextInt() + ".zip";
93        Zip zip = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
94        try (ZipFile zf = new ZipFile(new File(zip.name),
95                                      ZipFile.OPEN_READ | ZipFile.OPEN_DELETE ))
96        {
97            doTest0(zip, zf);
98        }
99        Path p = Paths.get(name);
100        if (Files.exists(p)) {
101            fail("Failed to delete " + name + " with OPEN_DELETE");
102        }
103    }
104
105    // test scenario:
106    // (1) keep a ZipFile(zip1) alive (in ZipFile's cache), dont close it
107    // (2) delete zip1 and create zip2 with the same name the zip1 with zip2
108    // (3) zip1 tests should fail, but no crash
109    // (4) zip2 tasks should all get zip2, then pass normal testing.
110    static void testCachedDelete() throws Throwable {
111        String name = "zftest" + r.nextInt() + ".zip";
112        Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
113
114        try (ZipFile zf = new ZipFile(zip1.name)) {
115            for (int i = 0; i < NN; i++) {
116                executor.submit(() -> verifyNoCrash(zip1));
117            }
118            // delete the "zip1"  and create a new one to test
119            Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
120            /*
121                System.out.println("========================================");
122                System.out.printf("    zip1=%s, mt=%d, enum=%d%n    ->attrs=[key=%s, sz=%d, mt=%d]%n",
123                    zip1.name, zip1.lastModified, zip1.entries.size(),
124                    zip1.attrs.fileKey(), zip1.attrs.size(), zip1.attrs.lastModifiedTime().toMillis());
125                System.out.printf("    zip2=%s, mt=%d, enum=%d%n    ->attrs=[key=%s, sz=%d, mt=%d]%n",
126                    zip2.name, zip2.lastModified, zip2.entries.size(),
127                    zip2.attrs.fileKey(), zip2.attrs.size(), zip2.attrs.lastModifiedTime().toMillis());
128            */
129            for (int i = 0; i < NN; i++) {
130                executor.submit(() -> doTest(zip2));
131            }
132        }
133    }
134
135   // overwrite the "zip1"  and create a new one to test. So the two zip files
136   // have the same fileKey, but probably different lastModified()
137    static void testCachedOverwrite() throws Throwable {
138        String name = "zftest" + r.nextInt() + ".zip";
139        Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
140        try (ZipFile zf = new ZipFile(zip1.name)) {
141            for (int i = 0; i < NN; i++) {
142                executor.submit(() -> verifyNoCrash(zip1));
143            }
144            // overwrite the "zip1"  with new contents
145            Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, false);
146            for (int i = 0; i < NN; i++) {
147                executor.submit(() -> doTest(zip2));
148            }
149        }
150    }
151
152    // just check the entries and contents. since the file has been either overwritten
153    // or deleted/rewritten, we only care if it crahes or not.
154    static void verifyNoCrash(Zip zip) throws RuntimeException {
155        try (ZipFile zf = new ZipFile(zip.name)) {
156            List<ZipEntry> zlist = new ArrayList(zip.entries.keySet());
157            String[] elist = zf.stream().map( e -> e.getName()).toArray(String[]::new);
158            if (!Arrays.equals(elist,
159                               zlist.stream().map( e -> e.getName()).toArray(String[]::new)))
160            {
161                //System.out.printf("++++++ LIST NG [%s] entries.len=%d, expected=%d+++++++%n",
162                //                  zf.getName(), elist.length, zlist.size());
163                return;
164            }
165            for (ZipEntry ze : zlist) {
166                byte[] zdata = zip.entries.get(ze);
167                ZipEntry e = zf.getEntry(ze.getName());
168                if (e != null) {
169                    checkEqual(e, ze);
170                    if (!e.isDirectory()) {
171                        // check with readAllBytes
172                        try (InputStream is = zf.getInputStream(e)) {
173                            if (!Arrays.equals(zdata, is.readAllBytes())) {
174                                //System.out.printf("++++++ BYTES NG  [%s]/[%s] ++++++++%n",
175                                //                  zf.getName(), ze.getName());
176                            }
177                        }
178                    }
179                }
180            }
181        } catch (Throwable t) {
182            // t.printStackTrace();
183            // fail(t.toString());
184        }
185    }
186
187    static void checkEqual(ZipEntry x, ZipEntry y) {
188        if (x.getName().equals(y.getName()) &&
189            x.isDirectory() == y.isDirectory() &&
190            x.getMethod() == y.getMethod() &&
191            (x.getTime() / 2000) == y.getTime() / 2000 &&
192            x.getSize() == y.getSize() &&
193            x.getCompressedSize() == y.getCompressedSize() &&
194            x.getCrc() == y.getCrc() &&
195            x.getComment().equals(y.getComment())
196        ) {
197            pass();
198        } else {
199            fail(x + " not equal to " + y);
200            System.out.printf("      %s       %s%n", x.getName(), y.getName());
201            System.out.printf("      %d       %d%n", x.getMethod(), y.getMethod());
202            System.out.printf("      %d       %d%n", x.getTime(), y.getTime());
203            System.out.printf("      %d       %d%n", x.getSize(), y.getSize());
204            System.out.printf("      %d       %d%n", x.getCompressedSize(), y.getCompressedSize());
205            System.out.printf("      %d       %d%n", x.getCrc(), y.getCrc());
206            System.out.println("-----------------");
207        }
208    }
209
210    static void doTest(Zip zip) throws RuntimeException {
211        //Thread me = Thread.currentThread();
212        try (ZipFile zf = new ZipFile(zip.name)) {
213            doTest0(zip, zf);
214        } catch (Throwable t) {
215            throw new RuntimeException(t);
216        }
217    }
218
219    static void doTest0(Zip zip, ZipFile zf) throws Throwable {
220        // (0) check zero-length entry name, no AIOOBE
221        try {
222            check(zf.getEntry("") == null);;
223        } catch (Throwable t) {
224            unexpected(t);
225        }
226
227        List<ZipEntry> list = new ArrayList(zip.entries.keySet());
228        // (1) check entry list, in expected order
229        if (!check(Arrays.equals(
230                list.stream().map( e -> e.getName()).toArray(String[]::new),
231                zf.stream().map( e -> e.getName()).toArray(String[]::new)))) {
232            return;
233        }
234        // (2) shuffle, and check each entry and its bytes
235        Collections.shuffle(list);
236        for (ZipEntry ze : list) {
237            byte[] data = zip.entries.get(ze);
238            ZipEntry e = zf.getEntry(ze.getName());
239            checkEqual(e, ze);
240            if (!e.isDirectory()) {
241                // check with readAllBytes
242                try (InputStream is = zf.getInputStream(e)) {
243                    check(Arrays.equals(data, is.readAllBytes()));
244                }
245                // check with smaller sized buf
246                try (InputStream is = zf.getInputStream(e)) {
247                    byte[] buf = new byte[(int)e.getSize()];
248                    int sz = r.nextInt((int)e.getSize()/4 + 1) + 1;
249                    int off = 0;
250                    int n;
251                    while ((n = is.read(buf, off, buf.length - off)) > 0) {
252                        off += n;
253                    }
254                    check(is.read() == -1);
255                    check(Arrays.equals(data, buf));
256                }
257            }
258        }
259        // (3) check getMetaInfEntryNames
260        String[] metas = list.stream()
261                             .map( e -> e.getName())
262                             .filter( s -> s.startsWith("META-INF/"))
263                             .sorted()
264                             .toArray(String[]::new);
265        if (metas.length > 0) {
266            // meta-inf entries
267            Method getMetas = ZipFile.class.getDeclaredMethod("getMetaInfEntryNames");
268            getMetas.setAccessible(true);
269            String[] names = (String[])getMetas.invoke(zf);
270            if (names == null) {
271                fail("Failed to get metanames from " + zf);
272            } else {
273                Arrays.sort(names);
274                check(Arrays.equals(names, metas));
275            }
276        }
277    }
278
279    private static class Zip {
280        String name;
281        Map<ZipEntry, byte[]> entries;
282        BasicFileAttributes attrs;
283        long lastModified;
284
285        Zip(String name, int num, int szMax, boolean prefix, boolean clean) {
286            this.name = name;
287            entries = new LinkedHashMap<>(num);
288            try {
289                Path p = Paths.get(name);
290                if (clean) {
291                    Files.deleteIfExists(p);
292                }
293                paths.add(p);
294            } catch (Exception x) {
295                throw (RuntimeException)x;
296            }
297
298            try (FileOutputStream fos = new FileOutputStream(name);
299                 BufferedOutputStream bos = new BufferedOutputStream(fos);
300                 ZipOutputStream zos = new ZipOutputStream(bos))
301            {
302                if (prefix) {
303                    byte[] bytes = new byte[r.nextInt(1000)];
304                    r.nextBytes(bytes);
305                    bos.write(bytes);
306                }
307                CRC32 crc = new CRC32();
308                for (int i = 0; i < num; i++) {
309                    String ename = "entry-" + i + "-name-" + r.nextLong();
310                    ZipEntry ze = new ZipEntry(ename);
311                    int method = r.nextBoolean() ? ZipEntry.STORED : ZipEntry.DEFLATED;
312                    writeEntry(zos, crc, ze, ZipEntry.STORED, szMax);
313                }
314                // add some manifest entries
315                for (int i = 0; i < r.nextInt(20); i++) {
316                    String meta = "META-INF/" + "entry-" + i + "-metainf-" + r.nextLong();
317                    ZipEntry ze = new ZipEntry(meta);
318                    writeEntry(zos, crc, ze, ZipEntry.STORED, szMax);
319                }
320            } catch (Exception x) {
321                throw (RuntimeException)x;
322            }
323            try {
324                this.attrs = Files.readAttributes(Paths.get(name), BasicFileAttributes.class);
325                this.lastModified = new File(name).lastModified();
326            } catch (Exception x) {
327                throw (RuntimeException)x;
328            }
329        }
330
331        private void writeEntry(ZipOutputStream zos, CRC32 crc,
332                                ZipEntry ze, int method, int szMax)
333            throws IOException
334        {
335            ze.setMethod(method);
336            byte[] data = new byte[r.nextInt(szMax + 1)];
337            r.nextBytes(data);
338            if (method == ZipEntry.STORED) {  // must set size/csize/crc
339                ze.setSize(data.length);
340                ze.setCompressedSize(data.length);
341                crc.reset();
342                crc.update(data);
343                ze.setCrc(crc.getValue());
344            }
345            ze.setTime(System.currentTimeMillis());
346            ze.setComment(ze.getName());
347            zos.putNextEntry(ze);
348            zos.write(data);
349            zos.closeEntry();
350            entries.put(ze, data);
351        }
352    }
353
354    //--------------------- Infrastructure ---------------------------
355    static volatile int passed = 0, failed = 0;
356    static void pass() {passed++;}
357    static void pass(String msg) {System.out.println(msg); passed++;}
358    static void fail() {failed++; Thread.dumpStack();}
359    static void fail(String msg) {System.out.println(msg); fail();}
360    static void unexpected(Throwable t) {failed++; t.printStackTrace();}
361    static void unexpected(Throwable t, String msg) {
362        System.out.println(msg); failed++; t.printStackTrace();}
363    static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;}
364
365    public static void main(String[] args) throws Throwable {
366        try {realMain(args);} catch (Throwable t) {unexpected(t);}
367        System.out.println("\nPassed = " + passed + " failed = " + failed);
368        if (failed > 0) throw new AssertionError("Some tests failed");}
369}
370