Home | History | Annotate | Download | only in zip
      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
     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
     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)
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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