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