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 
32 import java.io.*;
33 import java.lang.reflect.Method;
34 import java.nio.*;
35 import java.nio.file.*;
36 import java.nio.file.attribute.*;
37 import java.util.*;
38 import java.util.concurrent.*;
39 import java.util.zip.*;
40 
41 public 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 
realMain(String[] args)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 
test(int numEntry, int szMax, boolean addPrefix, boolean cleanOld)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
testDelete()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.
testCachedDelete()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()
testCachedOverwrite()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.
verifyNoCrash(Zip zip)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 
checkEqual(ZipEntry x, ZipEntry y)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 
doTest(Zip zip)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 
doTest0(Zip zip, ZipFile zf)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 
Zip(String name, int num, int szMax, boolean prefix, boolean clean)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 
writeEntry(ZipOutputStream zos, CRC32 crc, ZipEntry ze, int method, int szMax)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;
pass()356     static void pass() {passed++;}
pass(String msg)357     static void pass(String msg) {System.out.println(msg); passed++;}
fail()358     static void fail() {failed++; Thread.dumpStack();}
fail(String msg)359     static void fail(String msg) {System.out.println(msg); fail();}
unexpected(Throwable t)360     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
unexpected(Throwable t, String msg)361     static void unexpected(Throwable t, String msg) {
362         System.out.println(msg); failed++; t.printStackTrace();}
check(boolean cond)363     static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;}
364 
main(String[] args)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