1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.build.apkzlib.zip;
18 
19 import static com.android.tools.build.apkzlib.utils.ApkZFileTestUtils.readSegment;
20 import static org.junit.Assert.assertArrayEquals;
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNotEquals;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNotSame;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.assertSame;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 
31 import com.android.tools.build.apkzlib.zip.compress.DeflateExecutionCompressor;
32 import com.android.tools.build.apkzlib.zip.utils.CloseableByteSource;
33 import com.android.tools.build.apkzlib.zip.utils.RandomAccessFileUtils;
34 import com.google.common.base.Charsets;
35 import com.google.common.base.Strings;
36 import com.google.common.base.Throwables;
37 import com.google.common.hash.Hashing;
38 import com.google.common.io.ByteStreams;
39 import com.google.common.io.Closer;
40 import com.google.common.io.Files;
41 import java.io.ByteArrayInputStream;
42 import java.io.ByteArrayOutputStream;
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.RandomAccessFile;
49 import java.util.Locale;
50 import java.util.Random;
51 import java.util.concurrent.ExecutorService;
52 import java.util.concurrent.Executors;
53 import java.util.zip.Deflater;
54 import java.util.zip.ZipEntry;
55 import java.util.zip.ZipFile;
56 import java.util.zip.ZipInputStream;
57 import java.util.zip.ZipOutputStream;
58 import javax.annotation.Nonnull;
59 import org.junit.Rule;
60 import org.junit.Test;
61 import org.junit.rules.TemporaryFolder;
62 
63 public class ZFileTest {
64     @Rule
65     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
66 
67     @Test
getZipPath()68     public void getZipPath() throws Exception {
69         File temporaryDir = mTemporaryFolder.getRoot();
70         File zpath = new File(temporaryDir, "a");
71         try (ZFile zf = new ZFile(zpath)) {
72             assertEquals(zpath, zf.getFile());
73         }
74     }
75 
76     @Test
readNonExistingFile()77     public void readNonExistingFile() throws Exception {
78         File temporaryDir = mTemporaryFolder.getRoot();
79         File zf = new File(temporaryDir, "a");
80         try (ZFile azf = new ZFile(zf)) {
81             azf.touch();
82         }
83 
84         assertTrue(zf.exists());
85     }
86 
87     @Test(expected = IOException.class)
readExistingEmptyFile()88     public void readExistingEmptyFile() throws Exception {
89         File temporaryDir = mTemporaryFolder.getRoot();
90         File zf = new File(temporaryDir, "a");
91         Files.write(new byte[0], zf);
92         try (ZFile azf = new ZFile(zf)) {
93             /*
94              * Just open and close.
95              */
96         }
97     }
98 
99     @Test
readAlmostEmptyZip()100     public void readAlmostEmptyZip() throws Exception {
101         File zf = ZipTestUtils.cloneRsrc("empty-zip.zip", mTemporaryFolder);
102 
103         try (ZFile azf = new ZFile(zf)) {
104             assertEquals(1, azf.entries().size());
105 
106             StoredEntry z = azf.get("z/");
107             assertNotNull(z);
108             assertSame(StoredEntryType.DIRECTORY, z.getType());
109         }
110     }
111 
112     @Test
readZipWithTwoFilesOneDirectory()113     public void readZipWithTwoFilesOneDirectory() throws Exception {
114         File zf = ZipTestUtils.cloneRsrc("simple-zip.zip", mTemporaryFolder);
115 
116         try (ZFile azf = new ZFile(zf)) {
117             assertEquals(3, azf.entries().size());
118 
119             StoredEntry e0 = azf.get("dir/");
120             assertNotNull(e0);
121             assertSame(StoredEntryType.DIRECTORY, e0.getType());
122 
123             StoredEntry e1 = azf.get("dir/inside");
124             assertNotNull(e1);
125             assertSame(StoredEntryType.FILE, e1.getType());
126             ByteArrayOutputStream e1BytesOut = new ByteArrayOutputStream();
127             ByteStreams.copy(e1.open(), e1BytesOut);
128             byte e1Bytes[] = e1BytesOut.toByteArray();
129             String e1Txt = new String(e1Bytes, Charsets.US_ASCII);
130             assertEquals("inside", e1Txt);
131 
132             StoredEntry e2 = azf.get("file.txt");
133             assertNotNull(e2);
134             assertSame(StoredEntryType.FILE, e2.getType());
135             ByteArrayOutputStream e2BytesOut = new ByteArrayOutputStream();
136             ByteStreams.copy(e2.open(), e2BytesOut);
137             byte e2Bytes[] = e2BytesOut.toByteArray();
138             String e2Txt = new String(e2Bytes, Charsets.US_ASCII);
139             assertEquals("file with more text to allow deflating to be useful", e2Txt);
140         }
141     }
142 
143     @Test
readOnlyZipSupport()144     public void readOnlyZipSupport() throws Exception {
145         File testZip = ZipTestUtils.cloneRsrc("empty-zip.zip", mTemporaryFolder);
146 
147         assertTrue(testZip.setWritable(false));
148 
149         try (ZFile zf = new ZFile(testZip)) {
150             assertEquals(1, zf.entries().size());
151             assertTrue(zf.getCentralDirectoryOffset() > 0);
152             assertTrue(zf.getEocdOffset() > 0);
153         }
154     }
155 
156     @Test
readOnlyV2SignedApkSupport()157     public void readOnlyV2SignedApkSupport() throws Exception {
158         File testZip = ZipTestUtils.cloneRsrc("v2-signed.apk", mTemporaryFolder);
159 
160         assertTrue(testZip.setWritable(false));
161 
162         try (ZFile zf = new ZFile(testZip)) {
163             assertEquals(416, zf.entries().size());
164             assertTrue(zf.getCentralDirectoryOffset() > 0);
165             assertTrue(zf.getEocdOffset() > 0);
166         }
167     }
168 
169     @Test
compressedFilesReadableByJavaZip()170     public void compressedFilesReadableByJavaZip() throws Exception {
171         File testZip = new File(mTemporaryFolder.getRoot(), "t.zip");
172 
173         File wiki = ZipTestUtils
174                 .cloneRsrc("text-files/wikipedia.html", mTemporaryFolder, "wiki");
175         File rfc = ZipTestUtils.cloneRsrc("text-files/rfc2460.txt", mTemporaryFolder, "rfc");
176         File lena = ZipTestUtils.cloneRsrc("images/lena.png", mTemporaryFolder, "lena");
177         byte[] wikiData = Files.toByteArray(wiki);
178         byte[] rfcData = Files.toByteArray(rfc);
179         byte[] lenaData = Files.toByteArray(lena);
180 
181         try (ZFile zf = new ZFile(testZip)) {
182             zf.add("wiki", new ByteArrayInputStream(wikiData));
183             zf.add("rfc", new ByteArrayInputStream(rfcData));
184             zf.add("lena", new ByteArrayInputStream(lenaData));
185         }
186 
187         try(ZipFile jz = new ZipFile(testZip)) {
188             ZipEntry ze = jz.getEntry("wiki");
189             assertNotNull(ze);
190             assertEquals(ZipEntry.DEFLATED, ze.getMethod());
191             assertTrue(ze.getCompressedSize() < wikiData.length);
192             InputStream zeis = jz.getInputStream(ze);
193             assertArrayEquals(wikiData, ByteStreams.toByteArray(zeis));
194             zeis.close();
195 
196             ze = jz.getEntry("rfc");
197             assertNotNull(ze);
198             assertEquals(ZipEntry.DEFLATED, ze.getMethod());
199             assertTrue(ze.getCompressedSize() < rfcData.length);
200             zeis = jz.getInputStream(ze);
201             assertArrayEquals(rfcData, ByteStreams.toByteArray(zeis));
202             zeis.close();
203 
204             ze = jz.getEntry("lena");
205             assertNotNull(ze);
206             assertEquals(ZipEntry.STORED, ze.getMethod());
207             assertTrue(ze.getCompressedSize() == lenaData.length);
208             zeis = jz.getInputStream(ze);
209             assertArrayEquals(lenaData, ByteStreams.toByteArray(zeis));
210             zeis.close();
211         }
212     }
213 
214     @Test
215     public void removeFileFromZip() throws Exception {
216         File zipFile = mTemporaryFolder.newFile("test.zip");
217 
218         try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
219             ZipEntry entry = new ZipEntry("foo/");
220             entry.setMethod(ZipEntry.STORED);
221             entry.setSize(0);
222             entry.setCompressedSize(0);
223             entry.setCrc(0);
224             zos.putNextEntry(entry);
225             zos.putNextEntry(new ZipEntry("foo/bar"));
226             zos.write(new byte[] { 1, 2, 3, 4 });
227             zos.closeEntry();
228         }
229 
230         try (ZFile zf = new ZFile(zipFile)) {
231             assertEquals(2, zf.entries().size());
232             for (StoredEntry e : zf.entries()) {
233                 if (e.getType() == StoredEntryType.FILE) {
234                     e.delete();
235                 }
236             }
237 
238             zf.update();
239 
240             try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
241                 ZipEntry e1 = zis.getNextEntry();
242                 assertNotNull(e1);
243 
244                 assertEquals("foo/", e1.getName());
245 
246                 ZipEntry e2 = zis.getNextEntry();
247                 assertNull(e2);
248             }
249         }
250     }
251 
252     @Test
253     public void addFileToZip() throws Exception {
254         File zipFile = mTemporaryFolder.newFile("test.zip");
255 
256         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
257             ZipEntry fooDir = new ZipEntry("foo/");
258             fooDir.setCrc(0);
259             fooDir.setCompressedSize(0);
260             fooDir.setSize(0);
261             fooDir.setMethod(ZipEntry.STORED);
262             zos.putNextEntry(fooDir);
263             zos.closeEntry();
264         }
265 
266         ZFile zf = new ZFile(zipFile);
267         assertEquals(1, zf.entries().size());
268 
269         zf.update();
270 
271         try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
272             ZipEntry e1 = zis.getNextEntry();
273             assertNotNull(e1);
274 
275             assertEquals("foo/", e1.getName());
276 
277             ZipEntry e2 = zis.getNextEntry();
278             assertNull(e2);
279         }
280     }
281 
282     @Test
283     public void createNewZip() throws Exception {
284         File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
285 
286         ZFile zf = new ZFile(zipFile);
287         zf.add("foo", new ByteArrayInputStream(new byte[] { 0, 1 }));
288         zf.close();
289 
290         try (ZipFile jzf = new ZipFile(zipFile)) {
291             assertEquals(1, jzf.size());
292 
293             ZipEntry fooEntry = jzf.getEntry("foo");
294             assertNotNull(fooEntry);
295             assertEquals("foo", fooEntry.getName());
296             assertEquals(2, fooEntry.getSize());
297 
298             InputStream is = jzf.getInputStream(fooEntry);
299             assertEquals(0, is.read());
300             assertEquals(1, is.read());
301             assertEquals(-1, is.read());
302 
303             is.close();
304         }
305     }
306 
307     @Test
308     public void replaceFileWithSmallerInMiddle() throws Exception {
309         File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
310 
311         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
312             zos.putNextEntry(new ZipEntry("file1"));
313             zos.write(new byte[] { 1, 2, 3, 4, 5 });
314             zos.putNextEntry(new ZipEntry("file2"));
315             zos.write(new byte[] { 6, 7, 8 });
316             zos.putNextEntry(new ZipEntry("file3"));
317             zos.write(new byte[] { 9, 0, 1, 2, 3, 4 });
318         }
319 
320         int totalSize = (int) zipFile.length();
321 
322         try (ZFile zf = new ZFile(zipFile)) {
323             assertEquals(3, zf.entries().size());
324 
325             StoredEntry file2 = zf.get("file2");
326             assertNotNull(file2);
327             assertEquals(3, file2.getCentralDirectoryHeader().getUncompressedSize());
328 
329             assertArrayEquals(new byte[] { 6, 7, 8 }, file2.read());
330 
331             zf.add("file2", new ByteArrayInputStream(new byte[] { 11, 12 }));
332 
333             int newTotalSize = (int) zipFile.length();
334             assertTrue(newTotalSize + " == " + totalSize, newTotalSize == totalSize);
335 
336             file2 = zf.get("file2");
337             assertNotNull(file2);
338             assertArrayEquals(new byte[] { 11, 12 }, file2.read());
339         }
340 
341         try (ZFile zf2 = new ZFile(zipFile)) {
342             StoredEntry file2 = zf2.get("file2");
343             assertNotNull(file2);
344             assertArrayEquals(new byte[] { 11, 12 }, file2.read());
345         }
346     }
347 
348     @Test
349     public void replaceFileWithSmallerAtEnd() throws Exception {
350         File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
351         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
352             zos.putNextEntry(new ZipEntry("file1"));
353             zos.write(new byte[]{1, 2, 3, 4, 5});
354             zos.putNextEntry(new ZipEntry("file2"));
355             zos.write(new byte[]{6, 7, 8});
356             zos.putNextEntry(new ZipEntry("file3"));
357             zos.write(new byte[]{9, 0, 1, 2, 3, 4});
358         }
359 
360         int totalSize = (int) zipFile.length();
361 
362         try (ZFile zf = new ZFile(zipFile)) {
363             assertEquals(3, zf.entries().size());
364 
365             StoredEntry file3 = zf.get("file3");
366             assertNotNull(file3);
367             assertEquals(6, file3.getCentralDirectoryHeader().getUncompressedSize());
368 
369             assertArrayEquals(new byte[]{9, 0, 1, 2, 3, 4}, file3.read());
370 
371             zf.add("file3", new ByteArrayInputStream(new byte[]{11, 12}));
372             zf.close();
373 
374             int newTotalSize = (int) zipFile.length();
375             assertTrue(newTotalSize + " < " + totalSize, newTotalSize < totalSize);
376 
377             file3 = zf.get("file3");
378             assertNotNull(file3);
379             assertArrayEquals(new byte[]{11, 12,}, file3.read());
380         }
381 
382         try (ZFile zf2 = new ZFile(zipFile)) {
383             StoredEntry file3 = zf2.get("file3");
384             assertNotNull(file3);
385             assertArrayEquals(new byte[]{11, 12,}, file3.read());
386         }
387     }
388 
389     @Test
390     public void replaceFileWithBiggerAtBegin() throws Exception {
391         File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
392 
393         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
394             zos.putNextEntry(new ZipEntry("file1"));
395             zos.write(new byte[]{1, 2, 3, 4, 5});
396             zos.putNextEntry(new ZipEntry("file2"));
397             zos.write(new byte[]{6, 7, 8});
398             zos.putNextEntry(new ZipEntry("file3"));
399             zos.write(new byte[]{9, 0, 1, 2, 3, 4});
400         }
401 
402         int totalSize = (int) zipFile.length();
403         byte[] newData = new byte[100];
404 
405         try (ZFile zf = new ZFile(zipFile)) {
406             assertEquals(3, zf.entries().size());
407 
408             StoredEntry file1 = zf.get("file1");
409             assertNotNull(file1);
410             assertEquals(5, file1.getCentralDirectoryHeader().getUncompressedSize());
411 
412             assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, file1.read());
413 
414             /*
415              * Need some data because java zip API uses data descriptors which we don't and makes
416              * the entries bigger (meaning just adding a couple of bytes would still fit in the
417              * same place).
418              */
419             Random r = new Random();
420             r.nextBytes(newData);
421 
422             zf.add("file1", new ByteArrayInputStream(newData));
423             zf.close();
424 
425             int newTotalSize = (int) zipFile.length();
426             assertTrue(newTotalSize + " > " + totalSize, newTotalSize > totalSize);
427 
428             file1 = zf.get("file1");
429             assertNotNull(file1);
430             assertArrayEquals(newData, file1.read());
431         }
432 
433         try (ZFile zf2 = new ZFile(zipFile)) {
434             StoredEntry file1 = zf2.get("file1");
435             assertNotNull(file1);
436             assertArrayEquals(newData, file1.read());
437         }
438     }
439 
440     @Test
441     public void replaceFileWithBiggerAtEnd() throws Exception {
442         File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
443 
444         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
445             zos.putNextEntry(new ZipEntry("file1"));
446             zos.write(new byte[]{1, 2, 3, 4, 5});
447             zos.putNextEntry(new ZipEntry("file2"));
448             zos.write(new byte[]{6, 7, 8});
449             zos.putNextEntry(new ZipEntry("file3"));
450             zos.write(new byte[]{9, 0, 1, 2, 3, 4});
451         }
452 
453         int totalSize = (int) zipFile.length();
454         byte[] newData = new byte[100];
455 
456         try (ZFile zf = new ZFile(zipFile)) {
457             assertEquals(3, zf.entries().size());
458 
459             StoredEntry file3 = zf.get("file3");
460             assertNotNull(file3);
461             assertEquals(6, file3.getCentralDirectoryHeader().getUncompressedSize());
462 
463             assertArrayEquals(new byte[]{9, 0, 1, 2, 3, 4}, file3.read());
464 
465             /*
466              * Need some data because java zip API uses data descriptors which we don't and makes
467              * the entries bigger (meaning just adding a couple of bytes would still fit in the
468              * same place).
469              */
470             Random r = new Random();
471             r.nextBytes(newData);
472 
473             zf.add("file3", new ByteArrayInputStream(newData));
474             zf.close();
475 
476             int newTotalSize = (int) zipFile.length();
477             assertTrue(newTotalSize + " > " + totalSize, newTotalSize > totalSize);
478 
479             file3 = zf.get("file3");
480             assertNotNull(file3);
481             assertArrayEquals(newData, file3.read());
482         }
483 
484         try (ZFile zf2 = new ZFile(zipFile)) {
485             StoredEntry file3 = zf2.get("file3");
486             assertNotNull(file3);
487             assertArrayEquals(newData, file3.read());
488         }
489     }
490 
491     @Test
492     public void ignoredFilesDuringMerge() throws Exception {
493         File zip1 = mTemporaryFolder.newFile("t1.zip");
494 
495         try (ZipOutputStream zos1 = new ZipOutputStream(new FileOutputStream(zip1))) {
496             zos1.putNextEntry(new ZipEntry("only_in_1"));
497             zos1.write(new byte[] { 1, 2 });
498             zos1.putNextEntry(new ZipEntry("overridden_by_2"));
499             zos1.write(new byte[] { 2, 3 });
500             zos1.putNextEntry(new ZipEntry("not_overridden_by_2"));
501             zos1.write(new byte[] { 3, 4 });
502         }
503 
504         File zip2 = mTemporaryFolder.newFile("t2.zip");
505         try (ZipOutputStream zos2 = new ZipOutputStream(new FileOutputStream(zip2))) {
506             zos2.putNextEntry(new ZipEntry("only_in_2"));
507             zos2.write(new byte[] { 4, 5 });
508             zos2.putNextEntry(new ZipEntry("overridden_by_2"));
509             zos2.write(new byte[] { 5, 6 });
510             zos2.putNextEntry(new ZipEntry("not_overridden_by_2"));
511             zos2.write(new byte[] { 6, 7 });
512             zos2.putNextEntry(new ZipEntry("ignored_in_2"));
513             zos2.write(new byte[] { 7, 8 });
514         }
515 
516         try (
517                 ZFile zf1 = new ZFile(zip1);
518                 ZFile zf2 = new ZFile(zip2)) {
519             zf1.mergeFrom(zf2, (input) -> input.matches("not.*") || input.matches(".*gnored.*"));
520 
521             StoredEntry only_in_1 = zf1.get("only_in_1");
522             assertNotNull(only_in_1);
523             assertArrayEquals(new byte[]{1, 2}, only_in_1.read());
524 
525             StoredEntry overridden_by_2 = zf1.get("overridden_by_2");
526             assertNotNull(overridden_by_2);
527             assertArrayEquals(new byte[]{5, 6}, overridden_by_2.read());
528 
529             StoredEntry not_overridden_by_2 = zf1.get("not_overridden_by_2");
530             assertNotNull(not_overridden_by_2);
531             assertArrayEquals(new byte[]{3, 4}, not_overridden_by_2.read());
532 
533             StoredEntry only_in_2 = zf1.get("only_in_2");
534             assertNotNull(only_in_2);
535             assertArrayEquals(new byte[]{4, 5}, only_in_2.read());
536 
537             StoredEntry ignored_in_2 = zf1.get("ignored_in_2");
538             assertNull(ignored_in_2);
539         }
540     }
541 
542     @Test
addingFileDoesNotAddDirectoriesAutomatically()543     public void addingFileDoesNotAddDirectoriesAutomatically() throws Exception {
544         File zip = new File(mTemporaryFolder.getRoot(), "z.zip");
545         try (ZFile zf = new ZFile(zip)) {
546             zf.add("a/b/c", new ByteArrayInputStream(new byte[]{1, 2, 3}));
547             zf.update();
548             assertEquals(1, zf.entries().size());
549 
550             StoredEntry c = zf.get("a/b/c");
551             assertNotNull(c);
552             assertEquals(3, c.read().length);
553         }
554     }
555 
556     @Test
zipFileWithEocdSignatureInComment()557     public void zipFileWithEocdSignatureInComment() throws Exception {
558         File zip = mTemporaryFolder.newFile("f.zip");
559         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip))) {
560             zos.putNextEntry(new ZipEntry("a"));
561             zos.write(new byte[] { 1, 2, 3 });
562             zos.setComment("Random comment with XXXX weird characters. There must be enough "
563                     + "characters to survive skipping back the EOCD size.");
564         }
565 
566         byte zipBytes[] = Files.toByteArray(zip);
567         boolean didX4 = false;
568         for (int i = 0; i < zipBytes.length - 3; i++) {
569             boolean x4 = true;
570             for (int j = 0; j < 4; j++) {
571                 if (zipBytes[i + j] != 'X') {
572                     x4 = false;
573                     break;
574                 }
575             }
576 
577             if (x4) {
578                 zipBytes[i] = (byte) 0x50;
579                 zipBytes[i + 1] = (byte) 0x4b;
580                 zipBytes[i + 2] = (byte) 0x05;
581                 zipBytes[i + 3] = (byte) 0x06;
582                 didX4 = true;
583                 break;
584             }
585         }
586 
587         assertTrue(didX4);
588 
589         Files.write(zipBytes, zip);
590 
591         try (ZFile zf = new ZFile(zip)) {
592             assertEquals(1, zf.entries().size());
593             StoredEntry a = zf.get("a");
594             assertNotNull(a);
595             assertArrayEquals(new byte[]{1, 2, 3}, a.read());
596         }
597     }
598 
599     @Test
addFileRecursively()600     public void addFileRecursively() throws Exception {
601         File tdir = mTemporaryFolder.newFolder();
602         File tfile = new File(tdir, "blah-blah");
603         Files.write("blah", tfile, Charsets.US_ASCII);
604 
605         File zip = new File(tdir, "f.zip");
606         try (ZFile zf = new ZFile(zip)) {
607             zf.addAllRecursively(tfile);
608 
609             StoredEntry blahEntry = zf.get("blah-blah");
610             assertNotNull(blahEntry);
611             String contents = new String(blahEntry.read(), Charsets.US_ASCII);
612             assertEquals("blah", contents);
613         }
614     }
615 
616     @Test
addDirectoryRecursively()617     public void addDirectoryRecursively() throws Exception {
618         File tdir = mTemporaryFolder.newFolder();
619 
620         String boom = Strings.repeat("BOOM!", 100);
621         String kaboom = Strings.repeat("KABOOM!", 100);
622 
623         Files.write(boom, new File(tdir, "danger"), Charsets.US_ASCII);
624         Files.write(kaboom, new File(tdir, "do not touch"), Charsets.US_ASCII);
625         File safeDir = new File(tdir, "safe");
626         assertTrue(safeDir.mkdir());
627 
628         String iLoveChocolate = Strings.repeat("I love chocolate! ", 200);
629         String iLoveOrange = Strings.repeat("I love orange! ", 50);
630         String loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean vitae "
631                 + "turpis quis justo scelerisque vulputate in et magna. Suspendisse eleifend "
632                 + "ultricies nisi, placerat consequat risus accumsan et. Pellentesque habitant "
633                 + "morbi tristique senectus et netus et malesuada fames ac turpis egestas. "
634                 + "Integer vitae leo purus. Nulla facilisi. Duis ligula libero, lacinia a "
635                 + "malesuada a, viverra tempor sapien. Donec eget consequat sapien, ultrices"
636                 + "interdum diam. Maecenas ipsum erat, suscipit at iaculis a, mollis nec risus. "
637                 + "Quisque tristique ac velit sed auctor. Nulla lacus diam, tristique id sem non, "
638                 + "pellentesque commodo mauris.";
639 
640         Files.write(iLoveChocolate, new File(safeDir, "eat.sweet"), Charsets.US_ASCII);
641         Files.write(iLoveOrange, new File(safeDir, "eat.fruit"), Charsets.US_ASCII);
642         Files.write(loremIpsum, new File(safeDir, "bedtime.reading.txt"), Charsets.US_ASCII);
643 
644         File zip = new File(tdir, "f.zip");
645         try (ZFile zf = new ZFile(zip)) {
646             zf.addAllRecursively(tdir, (f) -> !f.getName().startsWith("eat."));
647 
648             assertEquals(6, zf.entries().size());
649 
650             StoredEntry boomEntry = zf.get("danger");
651             assertNotNull(boomEntry);
652             assertEquals(CompressionMethod.DEFLATE,
653                     boomEntry.getCentralDirectoryHeader().getCompressionInfoWithWait().getMethod());
654             assertEquals(boom, new String(boomEntry.read(), Charsets.US_ASCII));
655 
656             StoredEntry kaboomEntry = zf.get("do not touch");
657             assertNotNull(kaboomEntry);
658             assertEquals(CompressionMethod.DEFLATE,
659                     kaboomEntry
660                             .getCentralDirectoryHeader()
661                             .getCompressionInfoWithWait()
662                             .getMethod());
663             assertEquals(kaboom, new String(kaboomEntry.read(), Charsets.US_ASCII));
664 
665             StoredEntry safeEntry = zf.get("safe/");
666             assertNotNull(safeEntry);
667             assertEquals(0, safeEntry.read().length);
668 
669             StoredEntry choc = zf.get("safe/eat.sweet");
670             assertNotNull(choc);
671             assertEquals(CompressionMethod.STORE,
672                     choc.getCentralDirectoryHeader().getCompressionInfoWithWait().getMethod());
673             assertEquals(iLoveChocolate, new String(choc.read(), Charsets.US_ASCII));
674 
675             StoredEntry orangeEntry = zf.get("safe/eat.fruit");
676             assertNotNull(orangeEntry);
677             assertEquals(CompressionMethod.STORE,
678                     orangeEntry
679                             .getCentralDirectoryHeader()
680                             .getCompressionInfoWithWait()
681                             .getMethod());
682             assertEquals(iLoveOrange, new String(orangeEntry.read(), Charsets.US_ASCII));
683 
684             StoredEntry loremEntry = zf.get("safe/bedtime.reading.txt");
685             assertNotNull(loremEntry);
686             assertEquals(CompressionMethod.DEFLATE,
687                     loremEntry
688                             .getCentralDirectoryHeader()
689                             .getCompressionInfoWithWait()
690                             .getMethod());
691             assertEquals(loremIpsum, new String(loremEntry.read(), Charsets.US_ASCII));
692         }
693     }
694 
695     @Test
extraDirectoryOffsetEmptyFile()696     public void extraDirectoryOffsetEmptyFile() throws Exception {
697         File zipNoOffsetFile = new File(mTemporaryFolder.getRoot(), "a.zip");
698         File zipWithOffsetFile = new File(mTemporaryFolder.getRoot(), "b.zip");
699 
700         int offset = 31;
701 
702         long zipNoOffsetSize;
703         try (
704                 ZFile zipNoOffset = new ZFile(zipNoOffsetFile);
705                 ZFile zipWithOffset = new ZFile(zipWithOffsetFile)) {
706             zipWithOffset.setExtraDirectoryOffset(offset);
707 
708             zipNoOffset.close();
709             zipWithOffset.close();
710 
711             zipNoOffsetSize = zipNoOffsetFile.length();
712             long zipWithOffsetSize = zipWithOffsetFile.length();
713 
714             assertEquals(zipNoOffsetSize + offset, zipWithOffsetSize);
715 
716             /*
717              * EOCD with no comment has 22 bytes.
718              */
719             assertEquals(0, zipNoOffset.getCentralDirectoryOffset());
720             assertEquals(0, zipNoOffset.getCentralDirectorySize());
721             assertEquals(0, zipNoOffset.getEocdOffset());
722             assertEquals(ZFileTestConstants.EOCD_SIZE, zipNoOffset.getEocdSize());
723             assertEquals(offset, zipWithOffset.getCentralDirectoryOffset());
724             assertEquals(0, zipWithOffset.getCentralDirectorySize());
725             assertEquals(offset, zipWithOffset.getEocdOffset());
726             assertEquals(ZFileTestConstants.EOCD_SIZE, zipWithOffset.getEocdSize());
727         }
728 
729         /*
730          * The EOCDs should not differ up until the end of the Central Directory size and should
731          * not differ after the offset
732          */
733         int p1Start = 0;
734         int p1Size = Eocd.F_CD_SIZE.endOffset();
735         int p2Start = Eocd.F_CD_OFFSET.endOffset();
736         int p2Size = (int) zipNoOffsetSize - p2Start;
737 
738         byte[] noOffsetData1 = readSegment(zipNoOffsetFile, p1Start, p1Size);
739         byte[] noOffsetData2 = readSegment(zipNoOffsetFile, p2Start, p2Size);
740         byte[] withOffsetData1 = readSegment(zipWithOffsetFile, offset, p1Size);
741         byte[] withOffsetData2 = readSegment(zipWithOffsetFile, offset + p2Start, p2Size);
742 
743         assertArrayEquals(noOffsetData1, withOffsetData1);
744         assertArrayEquals(noOffsetData2, withOffsetData2);
745 
746         try (ZFile readWithOffset = new ZFile(zipWithOffsetFile)) {
747             assertEquals(0, readWithOffset.entries().size());
748         }
749     }
750 
751     @Test
extraDirectoryOffsetNonEmptyFile()752     public void extraDirectoryOffsetNonEmptyFile() throws Exception {
753         File zipNoOffsetFile = new File(mTemporaryFolder.getRoot(), "a.zip");
754         File zipWithOffsetFile = new File(mTemporaryFolder.getRoot(), "b.zip");
755 
756         int cdSize;
757 
758         // The byte arrays below are larger when compressed, so we end up storing them uncompressed,
759         // which would normally cause them to be 4-aligned. Disable that, to make calculations
760         // easier.
761         ZFileOptions options = new ZFileOptions();
762         options.setAlignmentRule(AlignmentRules.constant(AlignmentRule.NO_ALIGNMENT));
763 
764         try (ZFile zipNoOffset = new ZFile(zipNoOffsetFile, options);
765                 ZFile zipWithOffset = new ZFile(zipWithOffsetFile, options)) {
766             zipWithOffset.setExtraDirectoryOffset(37);
767 
768             zipNoOffset.add("x", new ByteArrayInputStream(new byte[]{1, 2}));
769             zipWithOffset.add("x", new ByteArrayInputStream(new byte[]{1, 2}));
770 
771             zipNoOffset.close();
772             zipWithOffset.close();
773 
774             long zipNoOffsetSize = zipNoOffsetFile.length();
775             long zipWithOffsetSize = zipWithOffsetFile.length();
776 
777             assertEquals(zipNoOffsetSize + 37, zipWithOffsetSize);
778 
779             /*
780              * Local file header has 30 bytes + name.
781              * Central directory entry has 46 bytes + name
782              * EOCD with no comment has 22 bytes.
783              */
784             assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2,
785                     zipNoOffset.getCentralDirectoryOffset());
786             cdSize = (int) zipNoOffset.getCentralDirectorySize();
787             assertEquals(ZFileTestConstants.CENTRAL_DIRECTORY_ENTRY_SIZE + 1, cdSize);
788             assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2 + cdSize,
789                     zipNoOffset.getEocdOffset());
790             assertEquals(ZFileTestConstants.EOCD_SIZE, zipNoOffset.getEocdSize());
791             assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2 + 37,
792                     zipWithOffset.getCentralDirectoryOffset());
793             assertEquals(cdSize, zipWithOffset.getCentralDirectorySize());
794             assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2 + 37 + cdSize,
795                     zipWithOffset.getEocdOffset());
796             assertEquals(ZFileTestConstants.EOCD_SIZE, zipWithOffset.getEocdSize());
797         }
798 
799         /*
800          * The files should be equal: until the end of the first entry, from the beginning of the
801          * central directory until the offset field in the EOCD and after the offset field.
802          */
803         int p1Start = 0;
804         int p1Size = ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2;
805         int p2Start = ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2;
806         int p2Size = cdSize + Eocd.F_CD_SIZE.endOffset();
807         int p3Start = p2Start + cdSize + Eocd.F_CD_OFFSET.endOffset();
808         int p3Size = ZFileTestConstants.EOCD_SIZE - Eocd.F_CD_OFFSET.endOffset();
809 
810         byte[] noOffsetData1 = readSegment(zipNoOffsetFile, p1Start, p1Size);
811         byte[] noOffsetData2 = readSegment(zipNoOffsetFile, p2Start, p2Size);
812         byte[] noOffsetData3 = readSegment(zipNoOffsetFile, p3Start, p3Size);
813         byte[] withOffsetData1 = readSegment(zipWithOffsetFile, p1Start, p1Size);
814         byte[] withOffsetData2 = readSegment(zipWithOffsetFile, 37 + p2Start, p2Size);
815         byte[] withOffsetData3 = readSegment(zipWithOffsetFile, 37 + p3Start, p3Size);
816 
817         assertArrayEquals(noOffsetData1, withOffsetData1);
818         assertArrayEquals(noOffsetData2, withOffsetData2);
819         assertArrayEquals(noOffsetData3, withOffsetData3);
820 
821         try (ZFile readWithOffset = new ZFile(zipWithOffsetFile)) {
822             assertEquals(1, readWithOffset.entries().size());
823         }
824     }
825 
826     @Test
changeExtraDirectoryOffset()827     public void changeExtraDirectoryOffset() throws Exception {
828         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
829 
830         try (ZFile zip = new ZFile(zipFile)) {
831             zip.add("x", new ByteArrayInputStream(new byte[]{1, 2}));
832             zip.close();
833 
834             long noOffsetSize = zipFile.length();
835 
836             zip.setExtraDirectoryOffset(177);
837             zip.close();
838 
839             long withOffsetSize = zipFile.length();
840 
841             assertEquals(noOffsetSize + 177, withOffsetSize);
842         }
843     }
844 
845     @Test
computeOffsetWhenReadingEmptyFile()846     public void computeOffsetWhenReadingEmptyFile() throws Exception {
847         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
848 
849         try (ZFile zip = new ZFile(zipFile)) {
850             zip.setExtraDirectoryOffset(18);
851         }
852 
853         try (ZFile zip = new ZFile(zipFile)) {
854             assertEquals(18, zip.getExtraDirectoryOffset());
855         }
856     }
857 
858     @Test
computeOffsetWhenReadingNonEmptyFile()859     public void computeOffsetWhenReadingNonEmptyFile() throws Exception {
860         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
861 
862         try (ZFile zip = new ZFile(zipFile)) {
863             zip.setExtraDirectoryOffset(287);
864             zip.add("x", new ByteArrayInputStream(new byte[]{1, 2}));
865         }
866 
867         try (ZFile zip = new ZFile(zipFile)) {
868             assertEquals(287, zip.getExtraDirectoryOffset());
869         }
870     }
871 
872     @Test
obtainingCDAndEocdWhenEntriesWrittenOnEmptyZip()873     public void obtainingCDAndEocdWhenEntriesWrittenOnEmptyZip() throws Exception {
874         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
875 
876         byte[][] cd = new byte[1][];
877         byte[][] eocd = new byte[1][];
878 
879         try (ZFile zip = new ZFile(zipFile)) {
880             zip.addZFileExtension(new ZFileExtension() {
881                 @Override
882                 public void entriesWritten() throws IOException {
883                     cd[0] = zip.getCentralDirectoryBytes();
884                     eocd[0] = zip.getEocdBytes();
885                 }
886             });
887         }
888 
889         assertNotNull(cd[0]);
890         assertEquals(0, cd[0].length);
891         assertNotNull(eocd[0]);
892         assertEquals(22, eocd[0].length);
893     }
894 
895     @Test
obtainingCDAndEocdWhenEntriesWrittenOnNonEmptyZip()896     public void obtainingCDAndEocdWhenEntriesWrittenOnNonEmptyZip() throws Exception {
897         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
898 
899         byte[][] cd = new byte[1][];
900         byte[][] eocd = new byte[1][];
901 
902         try (ZFile zip = new ZFile(zipFile)) {
903             zip.add("foo", new ByteArrayInputStream(new byte[0]));
904             zip.addZFileExtension(new ZFileExtension() {
905                 @Override
906                 public void entriesWritten() throws IOException {
907                     cd[0] = zip.getCentralDirectoryBytes();
908                     eocd[0] = zip.getEocdBytes();
909                 }
910             });
911         }
912 
913         /*
914          * Central directory entry has 46 bytes + name
915          * EOCD with no comment has 22 bytes.
916          */
917         assertNotNull(cd[0]);
918         assertEquals(46 + 3, cd[0].length);
919         assertNotNull(eocd[0]);
920         assertEquals(22, eocd[0].length);
921     }
922 
923     @Test
java7JarSupported()924     public void java7JarSupported() throws Exception {
925         File jar = ZipTestUtils.cloneRsrc("j7.jar", mTemporaryFolder);
926 
927         try (ZFile j = new ZFile(jar)) {
928             assertEquals(8, j.entries().size());
929         }
930     }
931 
932     @Test
java8JarSupported()933     public void java8JarSupported() throws Exception {
934         File jar = ZipTestUtils.cloneRsrc("j8.jar", mTemporaryFolder);
935 
936         try (ZFile j = new ZFile(jar)) {
937             assertEquals(8, j.entries().size());
938         }
939     }
940 
941     @Test
utf8NamesSupportedOnReading()942     public void utf8NamesSupportedOnReading() throws Exception {
943         File zip = ZipTestUtils.cloneRsrc("zip-with-utf8-filename.zip", mTemporaryFolder);
944 
945         try (ZFile f = new ZFile(zip)) {
946             assertEquals(1, f.entries().size());
947 
948             StoredEntry entry = f.entries().iterator().next();
949             String filetMignonKorean = "\uc548\uc2eC \uc694\ub9ac";
950             String isGoodJapanese = "\u3068\u3066\u3082\u826f\u3044";
951 
952             assertEquals(
953                     filetMignonKorean + " " + isGoodJapanese,
954                     entry.getCentralDirectoryHeader().getName());
955             assertArrayEquals(
956                     "Stuff about food is good.\n".getBytes(Charsets.US_ASCII), entry.read());
957         }
958     }
959 
960     @Test
utf8NamesSupportedOnReadingWithoutUtf8Flag()961     public void utf8NamesSupportedOnReadingWithoutUtf8Flag() throws Exception {
962         File zip = ZipTestUtils.cloneRsrc("zip-with-utf8-filename.zip", mTemporaryFolder);
963 
964         // Reset bytes 7 and 122 that have the flag in the local header and central directory.
965         byte[] data = Files.toByteArray(zip);
966         data[7] = 0;
967         data[122] = 0;
968         Files.write(data, zip);
969 
970         try (ZFile f = new ZFile(zip)) {
971             assertEquals(1, f.entries().size());
972 
973             StoredEntry entry = f.entries().iterator().next();
974             String filetMignonKorean = "\uc548\uc2eC \uc694\ub9ac";
975             String isGoodJapanese = "\u3068\u3066\u3082\u826f\u3044";
976 
977             assertEquals(
978                     filetMignonKorean + " " + isGoodJapanese,
979                     entry.getCentralDirectoryHeader().getName());
980             assertArrayEquals(
981                     "Stuff about food is good.\n".getBytes(Charsets.US_ASCII),
982                     entry.read());
983         }
984     }
985 
986     @Test
utf8NamesSupportedOnWriting()987     public void utf8NamesSupportedOnWriting() throws Exception {
988         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
989         String lettuceIsHealthyArmenian = "\u0533\u0561\u0566\u0561\u0580\u0020\u0561\u057C"
990                 + "\u0578\u0572\u057B";
991 
992         try (ZFile zip = new ZFile(zipFile)) {
993             zip.add(lettuceIsHealthyArmenian, new ByteArrayInputStream(new byte[]{0}));
994         }
995 
996         try (ZFile zip2 = new ZFile(zipFile)) {
997             assertEquals(1, zip2.entries().size());
998             StoredEntry entry = zip2.entries().iterator().next();
999             assertEquals(lettuceIsHealthyArmenian, entry.getCentralDirectoryHeader().getName());
1000         }
1001     }
1002 
1003     @Test
zipMemoryUsageIsZeroAfterClose()1004     public void zipMemoryUsageIsZeroAfterClose() throws Exception {
1005         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1006 
1007         ZFileOptions options = new ZFileOptions();
1008         long used;
1009         try (ZFile zip = new ZFile(zipFile, options)) {
1010 
1011             assertEquals(0, options.getTracker().getBytesUsed());
1012             assertEquals(0, options.getTracker().getMaxBytesUsed());
1013 
1014             zip.add("Blah", new ByteArrayInputStream(new byte[500]));
1015             used = options.getTracker().getBytesUsed();
1016             assertTrue(used > 500);
1017             assertEquals(used, options.getTracker().getMaxBytesUsed());
1018         }
1019 
1020         assertEquals(0, options.getTracker().getBytesUsed());
1021         assertEquals(used, options.getTracker().getMaxBytesUsed());
1022     }
1023 
1024     @Test
unusedZipAreasAreClearedOnWrite()1025     public void unusedZipAreasAreClearedOnWrite() throws Exception {
1026         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1027         ZFileOptions options = new ZFileOptions();
1028         options.setAlignmentRule(AlignmentRules.constantForSuffix(".txt", 1000));
1029         try (ZFile zf = new ZFile(zipFile, options)) {
1030             zf.add("test1.txt", new ByteArrayInputStream(new byte[]{1}), false);
1031         }
1032 
1033         /*
1034          * Write dummy data in some unused portion of the file.
1035          */
1036         try (RandomAccessFile raf = new RandomAccessFile(zipFile, "rw")) {
1037 
1038             raf.seek(500);
1039             byte[] dummyData = "Dummy".getBytes(Charsets.US_ASCII);
1040             raf.write(dummyData);
1041         }
1042 
1043         try (ZFile zf = new ZFile(zipFile)) {
1044             zf.touch();
1045         }
1046 
1047         try (RandomAccessFile raf = new RandomAccessFile(zipFile, "r")) {
1048 
1049             /*
1050              * test1.txt won't take more than 200 bytes. Additionally, the header for
1051              */
1052             byte[] data = new byte[900];
1053             RandomAccessFileUtils.fullyRead(raf, data);
1054 
1055             byte[] zeroData = new byte[data.length];
1056             assertArrayEquals(zeroData, data);
1057         }
1058     }
1059 
1060     @Test
deferredCompression()1061     public void deferredCompression() throws Exception {
1062         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1063 
1064         ExecutorService executor = Executors.newSingleThreadExecutor();
1065 
1066         ZFileOptions options = new ZFileOptions();
1067         boolean[] done = new boolean[1];
1068         options.setCompressor(new DeflateExecutionCompressor(executor, options.getTracker(),
1069                 Deflater.BEST_COMPRESSION) {
1070             @Nonnull
1071             @Override
1072             protected CompressionResult immediateCompress(@Nonnull CloseableByteSource source)
1073                     throws Exception {
1074                 Thread.sleep(500);
1075                 CompressionResult cr = super.immediateCompress(source);
1076                 done[0] = true;
1077                 return cr;
1078             }
1079         });
1080 
1081         try (ZFile zip = new ZFile(zipFile, options)) {
1082             byte sequences = 100;
1083             int seqCount = 1000;
1084             byte[] compressableData = new byte[sequences * seqCount];
1085             for (byte i = 0; i < sequences; i++) {
1086                 for (int j = 0; j < seqCount; j++) {
1087                     compressableData[i * seqCount + j] = i;
1088                 }
1089             }
1090 
1091             zip.add("compressedFile", new ByteArrayInputStream(compressableData));
1092             assertFalse(done[0]);
1093 
1094             /*
1095              * Even before closing, eventually all the stream will be read.
1096              */
1097             long tooLong = System.currentTimeMillis() + 10000;
1098             while (!done[0] && System.currentTimeMillis() < tooLong) {
1099                 Thread.sleep(10);
1100             }
1101 
1102             assertTrue(done[0]);
1103         }
1104 
1105         executor.shutdownNow();
1106     }
1107 
1108     @Test
zipFileWithEocdMarkerInComment()1109     public void zipFileWithEocdMarkerInComment() throws Exception {
1110         File zipFile = new File(mTemporaryFolder.getRoot(), "x");
1111 
1112         try (Closer closer = Closer.create()) {
1113             ZipOutputStream zos = closer.register(
1114                     new ZipOutputStream(new FileOutputStream(zipFile)));
1115             zos.setComment("\u0065\u4b50");
1116             zos.putNextEntry(new ZipEntry("foo"));
1117             zos.write(new byte[] { 1, 2, 3, 4 });
1118             zos.close();
1119 
1120             ZFile zf = closer.register(new ZFile(zipFile));
1121             StoredEntry entry = zf.get("foo");
1122             assertNotNull(entry);
1123             assertEquals(4, entry.getCentralDirectoryHeader().getUncompressedSize());
1124         }
1125     }
1126 
1127     @Test
zipFileWithEocdMarkerInFileName()1128     public void zipFileWithEocdMarkerInFileName() throws Exception {
1129         File zipFile = new File(mTemporaryFolder.getRoot(), "x");
1130 
1131         String fname = "tricky-\u0050\u004b\u0005\u0006";
1132         byte[] bytes = new byte[] { 1, 2, 3, 4 };
1133 
1134         try (Closer closer = Closer.create()) {
1135             ZipOutputStream zos = closer.register(
1136                     new ZipOutputStream(new FileOutputStream(zipFile)));
1137             zos.putNextEntry(new ZipEntry(fname));
1138             zos.write(bytes);
1139             zos.close();
1140 
1141             ZFile zf = closer.register(new ZFile(zipFile));
1142             StoredEntry entry = zf.get(fname);
1143             assertNotNull(entry);
1144             assertEquals(4, entry.getCentralDirectoryHeader().getUncompressedSize());
1145         }
1146     }
1147 
1148     @Test
zipFileWithEocdMarkerInFileContents()1149     public void zipFileWithEocdMarkerInFileContents() throws Exception {
1150         File zipFile = new File(mTemporaryFolder.getRoot(), "x");
1151 
1152         byte[] bytes = new byte[] { 0x50, 0x4b, 0x05, 0x06 };
1153 
1154         try (Closer closer = Closer.create()) {
1155             ZipOutputStream zos = closer.register(
1156                     new ZipOutputStream(new FileOutputStream(zipFile)));
1157             ZipEntry zipEntry = new ZipEntry("file");
1158             zipEntry.setMethod(ZipEntry.STORED);
1159             zipEntry.setCompressedSize(4);
1160             zipEntry.setSize(4);
1161             zipEntry.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
1162             zos.putNextEntry(zipEntry);
1163             zos.write(bytes);
1164             zos.close();
1165 
1166             ZFile zf = closer.register(new ZFile(zipFile));
1167             StoredEntry entry = zf.get("file");
1168             assertNotNull(entry);
1169             assertEquals(4, entry.getCentralDirectoryHeader().getUncompressedSize());
1170         }
1171     }
1172 
1173     @Test
replaceVeryLargeFileWithBiggerInMiddleOfZip()1174     public void replaceVeryLargeFileWithBiggerInMiddleOfZip() throws Exception {
1175         File zipFile = new File(mTemporaryFolder.getRoot(), "x");
1176 
1177         long small1Offset;
1178         long small2Offset;
1179         ZFileOptions coverOptions = new ZFileOptions();
1180         coverOptions.setCoverEmptySpaceUsingExtraField(true);
1181         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1182             zf.add("small1", new ByteArrayInputStream(new byte[] { 0, 1 }));
1183         }
1184 
1185         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1186             zf.add("verybig", new ByteArrayInputStream(new byte[100_000]), false);
1187         }
1188 
1189         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1190             zf.add("small2", new ByteArrayInputStream(new byte[] { 0, 1 }));
1191         }
1192 
1193         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1194             StoredEntry se = zf.get("small1");
1195             assertNotNull(se);
1196             small1Offset = se.getCentralDirectoryHeader().getOffset();
1197 
1198             se = zf.get("small2");
1199             assertNotNull(se);
1200             small2Offset = se.getCentralDirectoryHeader().getOffset();
1201 
1202             se = zf.get("verybig");
1203             assertNotNull(se);
1204             se.delete();
1205 
1206             zf.add("evenbigger", new ByteArrayInputStream(new byte[110_000]), false);
1207         }
1208 
1209         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1210             StoredEntry se = zf.get("small1");
1211             assertNotNull(se);
1212             assertEquals(se.getCentralDirectoryHeader().getOffset(), small1Offset);
1213 
1214             se = zf.get("small2");
1215             assertNotNull(se);
1216             assertNotEquals(se.getCentralDirectoryHeader().getOffset(), small2Offset);
1217         }
1218     }
1219 
1220     @Test
regressionRepackingDoesNotFail()1221     public void regressionRepackingDoesNotFail() throws Exception {
1222         File zipFile = new File(mTemporaryFolder.getRoot(), "x");
1223 
1224         ZFileOptions coverOptions = new ZFileOptions();
1225         coverOptions.setCoverEmptySpaceUsingExtraField(true);
1226         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1227             zf.add("small_1", new ByteArrayInputStream(new byte[] { 0, 1 }));
1228             zf.add("very_big", new ByteArrayInputStream(new byte[100_000]), false);
1229             zf.add("small_2", new ByteArrayInputStream(new byte[] { 0, 1 }));
1230             zf.add("big", new ByteArrayInputStream(new byte[10_000]), false);
1231             zf.add("small_3", new ByteArrayInputStream(new byte[] { 0, 1 }));
1232         }
1233 
1234         /*
1235          * Regression we're covering is that small_2 cannot be extended to cover up for the space
1236          * taken by very_big and needs to be repositioned. However, the algorithm to reposition
1237          * will put it in the best-fitting block, which is the one in "big", failing to actually
1238          * move it backwards in the file.
1239          */
1240         try (ZFile zf = new ZFile(zipFile, coverOptions)) {
1241             StoredEntry se = zf.get("big");
1242             assertNotNull(se);
1243             se.delete();
1244 
1245             se = zf.get("very_big");
1246             assertNotNull(se);
1247             se.delete();
1248         }
1249     }
1250 
1251     @Test
cannotAddMoreThan0x7fffExtraField()1252     public void cannotAddMoreThan0x7fffExtraField() throws Exception {
1253         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1254 
1255         ZFileOptions zfo = new ZFileOptions();
1256         zfo.setCoverEmptySpaceUsingExtraField(true);
1257 
1258         /*
1259          * Create a zip file with:
1260          *
1261          * [small file][large file with exactly 0x8000 bytes][small file 2]
1262          */
1263         long smallFile1Offset;
1264         long smallFile2Offset;
1265         long largeFileOffset;
1266         String largeFileName = "Large file";
1267         try (ZFile zf = new ZFile(zipFile, zfo)) {
1268             zf.add("Small file", new ByteArrayInputStream(new byte[] { 0, 1 }));
1269 
1270             int largeFileTotalSize = 0x8000;
1271             int largeFileContentsSize =
1272                     largeFileTotalSize
1273                             - ZFileTestConstants.LOCAL_HEADER_SIZE
1274                             - largeFileName.length();
1275 
1276             zf.add(largeFileName, new ByteArrayInputStream(new byte[largeFileContentsSize]), false);
1277             zf.add("Small file 2", new ByteArrayInputStream(new byte[] { 0, 1 }));
1278 
1279             zf.update();
1280 
1281             StoredEntry sfEntry = zf.get("Small file");
1282             assertNotNull(sfEntry);
1283             smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset();
1284             assertEquals(0, smallFile1Offset);
1285 
1286             StoredEntry lfEntry = zf.get(largeFileName);
1287             assertNotNull(lfEntry);
1288             largeFileOffset = lfEntry.getCentralDirectoryHeader().getOffset();
1289 
1290             StoredEntry sf2Entry = zf.get("Small file 2");
1291             assertNotNull(sf2Entry);
1292             smallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset();
1293 
1294             assertEquals(largeFileTotalSize, smallFile2Offset - largeFileOffset);
1295         }
1296 
1297         /*
1298          * Remove the large file from the zip file and check that small file 2 has been moved, but
1299          * no extra field has been added.
1300          */
1301         try (ZFile zf = new ZFile(zipFile, zfo)) {
1302             StoredEntry lfEntry = zf.get(largeFileName);
1303             assertNotNull(lfEntry);
1304             lfEntry.delete();
1305 
1306             zf.update();
1307 
1308             StoredEntry sfEntry = zf.get("Small file");
1309             assertNotNull(sfEntry);
1310             smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset();
1311             assertEquals(0, smallFile1Offset);
1312 
1313             StoredEntry sf2Entry = zf.get("Small file 2");
1314             assertNotNull(sf2Entry);
1315             long newSmallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset();
1316             assertEquals(largeFileOffset, newSmallFile2Offset);
1317 
1318             assertEquals(0, sf2Entry.getLocalExtra().size());
1319         }
1320     }
1321 
1322     @Test
canAddMoreThan0x7fffExtraField()1323     public void canAddMoreThan0x7fffExtraField() throws Exception {
1324         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1325 
1326         ZFileOptions zfo = new ZFileOptions();
1327         zfo.setCoverEmptySpaceUsingExtraField(true);
1328 
1329         /*
1330          * Create a zip file with:
1331          *
1332          * [small file][large file with exactly 0x7fff bytes][small file 2]
1333          */
1334         long smallFile1Offset;
1335         long smallFile2Offset;
1336         long largeFileOffset;
1337         String largeFileName = "Large file";
1338         int largeFileTotalSize = 0x7fff;
1339         try (ZFile zf = new ZFile(zipFile, zfo)) {
1340             zf.add("Small file", new ByteArrayInputStream(new byte[] { 0, 1 }));
1341 
1342             int largeFileContentsSize =
1343                     largeFileTotalSize
1344                             - ZFileTestConstants.LOCAL_HEADER_SIZE
1345                             - largeFileName.length();
1346 
1347             zf.add(largeFileName, new ByteArrayInputStream(new byte[largeFileContentsSize]), false);
1348             zf.add("Small file 2", new ByteArrayInputStream(new byte[] { 0, 1 }));
1349 
1350             zf.update();
1351 
1352             StoredEntry sfEntry = zf.get("Small file");
1353             assertNotNull(sfEntry);
1354             smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset();
1355             assertEquals(0, smallFile1Offset);
1356 
1357             StoredEntry lfEntry = zf.get(largeFileName);
1358             assertNotNull(lfEntry);
1359             largeFileOffset = lfEntry.getCentralDirectoryHeader().getOffset();
1360 
1361             StoredEntry sf2Entry = zf.get("Small file 2");
1362             assertNotNull(sf2Entry);
1363             smallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset();
1364 
1365             assertEquals(largeFileTotalSize, smallFile2Offset - largeFileOffset);
1366         }
1367 
1368         /*
1369          * Remove the large file from the zip file and check that small file 2 has been moved back
1370          * but with 0x7fff extra space added.
1371          */
1372         try (ZFile zf = new ZFile(zipFile, zfo)) {
1373             StoredEntry lfEntry = zf.get(largeFileName);
1374             assertNotNull(lfEntry);
1375             lfEntry.delete();
1376 
1377             zf.update();
1378 
1379             StoredEntry sfEntry = zf.get("Small file");
1380             assertNotNull(sfEntry);
1381             smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset();
1382             assertEquals(0, smallFile1Offset);
1383 
1384             StoredEntry sf2Entry = zf.get("Small file 2");
1385             assertNotNull(sf2Entry);
1386             long newSmallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset();
1387 
1388             assertEquals(largeFileOffset, newSmallFile2Offset);
1389             assertEquals(largeFileTotalSize, sf2Entry.getLocalExtra().size());
1390         }
1391     }
1392 
1393     @Test
detectIncorrectCRC32InLocalHeader()1394     public void detectIncorrectCRC32InLocalHeader() throws Exception {
1395         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1396 
1397         /*
1398          * Zip files created by ZFile never have data descriptors so we need to create one using
1399          * java's zip.
1400          */
1401         try (
1402                 FileOutputStream fos = new FileOutputStream(zipFile);
1403                 ZipOutputStream zos = new ZipOutputStream(fos)) {
1404             ZipEntry ze = new ZipEntry("foo");
1405             zos.putNextEntry(ze);
1406             byte[] randomBytes = new byte[512];
1407             new Random().nextBytes(randomBytes);
1408             zos.write(randomBytes);
1409         }
1410 
1411         /*
1412          * Open the zip file and compute where the local header CRC32 is.
1413          */
1414         long crcOffset;
1415         try (ZFile zf = new ZFile(zipFile)) {
1416             StoredEntry se = zf.get("foo");
1417             assertNotNull(se);
1418             long cdOffset = zf.getCentralDirectoryOffset();
1419 
1420             /*
1421              * Twelve bytes from the CD offset, we have the start of the CRC32 of the zip entry.
1422              */
1423             crcOffset = cdOffset - 12;
1424         }
1425 
1426         /*
1427          * Corrupt the CRC32.
1428          */
1429         byte[] crc = readSegment(zipFile, crcOffset, 4);
1430         crc[0]++;
1431         try (RandomAccessFile raf = new RandomAccessFile(zipFile, "rw")) {
1432             raf.seek(crcOffset);
1433             raf.write(crc);
1434         }
1435 
1436         /*
1437          * Now open the zip file and it should write a message in the log.
1438          */
1439         ZFileOptions options = new ZFileOptions();
1440         options.setVerifyLogFactory(VerifyLogs::unlimited);
1441         try (ZFile zf = new ZFile(zipFile, options)) {
1442             VerifyLog vl = zf.getVerifyLog();
1443             assertTrue(vl.getLogs().isEmpty());
1444             StoredEntry fooEntry = zf.get("foo");
1445             vl = fooEntry.getVerifyLog();
1446             assertEquals(1, vl.getLogs().size());
1447             assertTrue(vl.getLogs().get(0).contains("CRC32"));
1448         }
1449     }
1450 
1451     @Test
detectIncorrectVersionToExtractInCentralDirectory()1452     public void detectIncorrectVersionToExtractInCentralDirectory() throws Exception {
1453         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1454 
1455         /*
1456          * Create a valid zip file.
1457          */
1458         try (ZFile zf = new ZFile(zipFile)) {
1459             zf.add("foo", new ByteArrayInputStream(new byte[0]));
1460         }
1461 
1462         /*
1463          * Change the "version to extract" in the central directory to 0x7777.
1464          */
1465         int versionToExtractOffset =
1466                 ZFileTestConstants.LOCAL_HEADER_SIZE
1467                         + 3
1468                         + CentralDirectory.F_VERSION_EXTRACT.offset();
1469         byte[] allZipBytes = Files.toByteArray(zipFile);
1470         allZipBytes[versionToExtractOffset] = 0x77;
1471         allZipBytes[versionToExtractOffset + 1] = 0x77;
1472         Files.write(allZipBytes, zipFile);
1473 
1474         /*
1475          * Opening the file and it should write a message in the log. The entry has the right
1476          * version to extract (20), but it issues a warning because it is not equal to the one
1477          * in the central directory.
1478          */
1479         ZFileOptions options = new ZFileOptions();
1480         options.setVerifyLogFactory(VerifyLogs::unlimited);
1481         try (ZFile zf = new ZFile(zipFile, options)) {
1482             VerifyLog vl = zf.getVerifyLog();
1483             assertEquals(1, vl.getLogs().size());
1484             assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("version"));
1485             assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("extract"));
1486             StoredEntry fooEntry = zf.get("foo");
1487             vl = fooEntry.getVerifyLog();
1488             assertEquals(1, vl.getLogs().size());
1489             assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("version"));
1490             assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("extract"));
1491         }
1492     }
1493 
1494     @Test
detectIncorrectVersionToExtractInLocalHeader()1495     public void detectIncorrectVersionToExtractInLocalHeader() throws Exception {
1496         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1497 
1498         /*
1499          * Create a valid zip file.
1500          */
1501         try (ZFile zf = new ZFile(zipFile)) {
1502             zf.add("foo", new ByteArrayInputStream(new byte[0]));
1503         }
1504 
1505         /*
1506          * Change the "version to extract" in the local header to 0x7777.
1507          */
1508         int versionToExtractOffset = StoredEntry.F_VERSION_EXTRACT.offset();
1509         byte[] allZipBytes = Files.toByteArray(zipFile);
1510         allZipBytes[versionToExtractOffset] = 0x77;
1511         allZipBytes[versionToExtractOffset + 1] = 0x77;
1512         Files.write(allZipBytes, zipFile);
1513 
1514         /*
1515          * Opening the file should log an error message.
1516          */
1517         ZFileOptions options = new ZFileOptions();
1518         options.setVerifyLogFactory(VerifyLogs::unlimited);
1519         try (ZFile zf = new ZFile(zipFile, options)) {
1520             VerifyLog vl = zf.getVerifyLog();
1521             assertTrue(vl.getLogs().isEmpty());
1522             StoredEntry fooEntry = zf.get("foo");
1523             vl = fooEntry.getVerifyLog();
1524             assertEquals(1, vl.getLogs().size());
1525             assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("version"));
1526             assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("extract"));
1527         }
1528     }
1529 
1530     @Test
sortZipContentsWithDeferredCrc32()1531     public void sortZipContentsWithDeferredCrc32() throws Exception {
1532         /*
1533          * Create a zip file with deferred CRC32 and files in non-alphabetical order.
1534          * ZipOutputStream always creates deferred CRC32 entries.
1535          */
1536         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1537         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
1538             zos.putNextEntry(new ZipEntry("b"));
1539             zos.write(new byte[1000]);
1540             zos.putNextEntry(new ZipEntry("a"));
1541             zos.write(new byte[1000]);
1542         }
1543 
1544         /*
1545          * Now open the zip using a ZFile and sort the contents and check that the deferred CRC32
1546          * bits were reset.
1547          */
1548         try (ZFile zf = new ZFile(zipFile)) {
1549             StoredEntry a = zf.get("a");
1550             assertNotNull(a);
1551             assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, a.getDataDescriptorType());
1552             StoredEntry b = zf.get("b");
1553             assertNotNull(b);
1554             assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, b.getDataDescriptorType());
1555             assertTrue(
1556                     a.getCentralDirectoryHeader().getOffset()
1557                             > b.getCentralDirectoryHeader().getOffset());
1558 
1559             zf.sortZipContents();
1560             zf.update();
1561 
1562             a = zf.get("a");
1563             assertNotNull(a);
1564             assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, a.getDataDescriptorType());
1565             b = zf.get("b");
1566             assertNotNull(b);
1567             assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, b.getDataDescriptorType());
1568 
1569             assertTrue(
1570                     a.getCentralDirectoryHeader().getOffset()
1571                             < b.getCentralDirectoryHeader().getOffset());
1572         }
1573 
1574         /*
1575          * Open the file again and check there are no warnings.
1576          */
1577         try (ZFile zf = new ZFile(zipFile)) {
1578             VerifyLog vl = zf.getVerifyLog();
1579             assertEquals(0, vl.getLogs().size());
1580 
1581             StoredEntry a = zf.get("a");
1582             assertNotNull(a);
1583             vl = a.getVerifyLog();
1584             assertEquals(0, vl.getLogs().size());
1585 
1586             StoredEntry b = zf.get("b");
1587             assertNotNull(b);
1588             vl = b.getVerifyLog();
1589             assertEquals(0, vl.getLogs().size());
1590         }
1591     }
1592 
1593     @Test
1594     public void alignZipContentsWithDeferredCrc32() throws Exception {
1595         /*
1596          * Create an unaligned zip file with deferred CRC32 and files in non-alphabetical order.
1597          * We need an uncompressed file to make realigning have any effect.
1598          */
1599         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1600         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
1601             zos.putNextEntry(new ZipEntry("x"));
1602             zos.write(new byte[1000]);
1603             zos.putNextEntry(new ZipEntry("y"));
1604             zos.write(new byte[1000]);
1605             ZipEntry zEntry = new ZipEntry("z");
1606             zEntry.setSize(1000);
1607             zEntry.setMethod(ZipEntry.STORED);
1608             zEntry.setCrc(Hashing.crc32().hashBytes(new byte[1000]).asInt());
1609             zos.putNextEntry(zEntry);
1610             zos.write(new byte[1000]);
1611         }
1612 
1613         /*
1614          * Now open the zip using a ZFile and realign the contents and check that the deferred CRC32
1615          * bits were reset.
1616          */
1617         ZFileOptions options = new ZFileOptions();
1618         options.setAlignmentRule(AlignmentRules.constant(2000));
1619         try (ZFile zf = new ZFile(zipFile, options)) {
1620             StoredEntry x = zf.get("x");
1621             assertNotNull(x);
1622             assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, x.getDataDescriptorType());
1623             StoredEntry y = zf.get("y");
1624             assertNotNull(y);
1625             assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, y.getDataDescriptorType());
1626             StoredEntry z = zf.get("z");
1627             assertNotNull(z);
1628             assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, z.getDataDescriptorType());
1629 
1630             zf.realign();
1631             zf.update();
1632 
1633             x = zf.get("x");
1634             assertNotNull(x);
1635             assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, x.getDataDescriptorType());
1636             y = zf.get("y");
1637             assertNotNull(y);
1638             assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, y.getDataDescriptorType());
1639             z = zf.get("z");
1640             assertNotNull(z);
1641             assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, z.getDataDescriptorType());
1642         }
1643     }
1644 
1645     @Test
1646     public void openingZFileDoesNotRemoveDataDescriptors() throws Exception {
1647         /*
1648          * Create a zip file with deferred CRC32.
1649          */
1650         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
1651         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
1652             zos.putNextEntry(new ZipEntry("a"));
1653             zos.write(new byte[1000]);
1654         }
1655 
1656         /*
1657          * Open using ZFile and check that the deferred CRC32 is there.
1658          */
1659         try (ZFile zf = new ZFile(zipFile)) {
1660             StoredEntry se = zf.get("a");
1661             assertNotNull(se);
1662             assertNotEquals(DataDescriptorType.NO_DATA_DESCRIPTOR, se.getDataDescriptorType());
1663         }
1664 
1665         /*
1666          * Open using ZFile (again) and check that the deferred CRC32 is there.
1667          */
1668         try (ZFile zf = new ZFile(zipFile)) {
1669             StoredEntry se = zf.get("a");
1670             assertNotNull(se);
1671             assertNotEquals(DataDescriptorType.NO_DATA_DESCRIPTOR, se.getDataDescriptorType());
1672         }
1673     }
1674 
1675     @Test
1676     public void zipCommentsAreSaved() throws Exception {
1677         File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip");
1678         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFileWithComments))) {
1679             zos.setComment("foo");
1680         }
1681 
1682         /*
1683          * Open the zip file and check the comment is there.
1684          */
1685         try (ZFile zf = new ZFile(zipFileWithComments)) {
1686             byte[] comment = zf.getEocdComment();
1687             assertArrayEquals(new byte[] { 'f', 'o', 'o' }, comment);
1688 
1689             /*
1690              * Modify the comment and write the file.
1691              */
1692             zf.setEocdComment(new byte[] { 'b', 'a', 'r', 'r' });
1693         }
1694 
1695         /*
1696          * Open the file and see that the comment is there (both with java and zfile).
1697          */
1698         try (ZipFile zf2 = new ZipFile(zipFileWithComments)) {
1699             assertEquals("barr", zf2.getComment());
1700         }
1701 
1702         try (ZFile zf3 = new ZFile(zipFileWithComments)) {
1703             assertArrayEquals(new byte[] { 'b', 'a', 'r', 'r' }, zf3.getEocdComment());
1704         }
1705     }
1706 
1707     @Test
1708     public void eocdCommentsWithMoreThan64kNotAllowed() throws Exception {
1709         File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip");
1710         try (ZFile zf = new ZFile(zipFileWithComments)) {
1711             try {
1712                 zf.setEocdComment(new byte[65536]);
1713                 fail();
1714             } catch (IllegalArgumentException e) {
1715                 // Expected.
1716             }
1717 
1718             zf.setEocdComment(new byte[65535]);
1719         }
1720     }
1721 
1722     @Test
1723     public void eocdCommentsWithTheEocdMarkerAreAllowed() throws Exception {
1724         File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip");
1725         byte[] data = new byte[100];
1726         data[50] = 0x50; // Signature
1727         data[51] = 0x4b;
1728         data[52] = 0x05;
1729         data[53] = 0x06;
1730         data[54] = 0x00; // Number of disk
1731         data[55] = 0x00;
1732         data[56] = 0x00; // Disk CD start
1733         data[57] = 0x00;
1734         data[54] = 0x01; // Total records 1
1735         data[55] = 0x00;
1736         data[56] = 0x02; // Total records 2, must be = to total records 1
1737         data[57] = 0x00;
1738 
1739         try (ZFile zf = new ZFile(zipFileWithComments)) {
1740             zf.setEocdComment(data);
1741         }
1742 
1743         try (ZFile zf = new ZFile(zipFileWithComments)) {
1744             assertArrayEquals(data, zf.getEocdComment());
1745         }
1746     }
1747 
1748     @Test
1749     public void eocdCommentsWithTheEocdMarkerThatAreInvalidAreNotAllowed() throws Exception {
1750         File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip");
1751         byte[] data = new byte[100];
1752         data[50] = 0x50;
1753         data[51] = 0x4b;
1754         data[52] = 0x05;
1755         data[53] = 0x06;
1756         data[67] = 0x00;
1757 
1758         try (ZFile zf = new ZFile(zipFileWithComments)) {
1759             try {
1760                 zf.setEocdComment(data);
1761                 fail();
1762             } catch (IllegalArgumentException e) {
1763                 // Expected.
1764             }
1765         }
1766     }
1767 
1768     @Test
1769     public void zipCommentsArePreservedWithFileChanges() throws Exception {
1770         File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip");
1771         byte[] comment = new byte[] { 1, 3, 4 };
1772         try (ZFile zf = new ZFile(zipFileWithComments)) {
1773             zf.add("foo", new ByteArrayInputStream(new byte[50]));
1774             zf.setEocdComment(comment);
1775         }
1776 
1777         try (ZFile zf = new ZFile(zipFileWithComments)) {
1778             assertArrayEquals(comment, zf.getEocdComment());
1779             zf.add("bar", new ByteArrayInputStream(new byte[100]));
1780         }
1781 
1782         try (ZFile zf = new ZFile(zipFileWithComments)) {
1783             assertArrayEquals(comment, zf.getEocdComment());
1784         }
1785     }
1786 
1787     @Test
1788     public void overlappingZipEntries() throws Exception {
1789         File myZip = ZipTestUtils.cloneRsrc("overlapping.zip", mTemporaryFolder);
1790         try (ZFile zf = new ZFile(myZip)) {
1791             fail();
1792         } catch (IOException e) {
1793             assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/bbb"));
1794             assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/ddd"));
1795             assertFalse(Throwables.getStackTraceAsString(e).contains("Central Directory"));
1796         }
1797     }
1798 
1799     @Test
1800     public void overlappingZipEntryWithCentralDirectory() throws Exception {
1801         File myZip = ZipTestUtils.cloneRsrc("overlapping2.zip", mTemporaryFolder);
1802         try (ZFile zf = new ZFile(myZip)) {
1803             fail();
1804         } catch (IOException e) {
1805             assertFalse(Throwables.getStackTraceAsString(e).contains("overlapping/bbb"));
1806             assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/ddd"));
1807             assertTrue(Throwables.getStackTraceAsString(e).contains("Central Directory"));
1808         }
1809     }
1810 
1811     @Test
1812     public void readFileWithOffsetBeyondFileEnd() throws Exception {
1813         File myZip = ZipTestUtils.cloneRsrc("entry-outside-file.zip", mTemporaryFolder);
1814         try (ZFile zf = new ZFile(myZip)) {
1815             fail();
1816         } catch (IOException e) {
1817             assertTrue(Throwables.getStackTraceAsString(e).contains("entry-outside-file/foo"));
1818             assertTrue(Throwables.getStackTraceAsString(e).contains("EOF"));
1819         }
1820     }
1821 }
1822