Home | History | Annotate | Download | only in zip
      1 /*
      2  * Copyright (C) 2010 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 libcore.java.util.zip;
     18 
     19 import java.io.BufferedOutputStream;
     20 import java.io.ByteArrayOutputStream;
     21 import java.io.File;
     22 import java.io.FileInputStream;
     23 import java.io.FileOutputStream;
     24 import java.io.IOException;
     25 import java.io.InputStream;
     26 import java.io.OutputStream;
     27 import java.util.Enumeration;
     28 import java.util.HashSet;
     29 import java.util.Random;
     30 import java.util.Set;
     31 import java.util.zip.CRC32;
     32 import java.util.zip.ZipEntry;
     33 import java.util.zip.ZipException;
     34 import java.util.zip.ZipFile;
     35 import java.util.zip.ZipInputStream;
     36 import java.util.zip.ZipOutputStream;
     37 import libcore.junit.junit3.TestCaseWithRules;
     38 import libcore.junit.util.ResourceLeakageDetector;
     39 import org.junit.Rule;
     40 import org.junit.rules.TestRule;
     41 import tests.support.resource.Support_Resources;
     42 
     43 public abstract class AbstractZipFileTest extends TestCaseWithRules {
     44     @Rule
     45     public TestRule resourceLeakageDetectorRule = ResourceLeakageDetector.getRule();
     46 
     47     /**
     48      * Exercise Inflater's ability to refill the zlib's input buffer. As of this
     49      * writing, this buffer's max size is 64KiB compressed bytes. We'll write a
     50      * full megabyte of uncompressed data, which should be sufficient to exhaust
     51      * the buffer. http://b/issue?id=2734751
     52      */
     53     public void testInflatingFilesRequiringZipRefill() throws IOException {
     54         int originalSize = 1024 * 1024;
     55         byte[] readBuffer = new byte[8192];
     56         final File f = createTemporaryZipFile();
     57         writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */);
     58         ZipFile zipFile = new ZipFile(f);
     59         for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
     60             ZipEntry zipEntry = e.nextElement();
     61             assertTrue("This test needs >64 KiB of compressed data to exercise Inflater",
     62                     zipEntry.getCompressedSize() > (64 * 1024));
     63             InputStream is = zipFile.getInputStream(zipEntry);
     64             while (is.read(readBuffer, 0, readBuffer.length) != -1) {}
     65             is.close();
     66         }
     67         zipFile.close();
     68     }
     69 
     70     private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) {
     71         // Gotcha here: original and replacement must be the same length
     72         assertEquals(original.length, replacement.length);
     73         boolean found;
     74         for(int i=0; i < buffer.length - original.length; i++) {
     75             found = false;
     76             if (buffer[i] == original[0]) {
     77                 found = true;
     78                 for (int j=0; j < original.length; j++) {
     79                     if (buffer[i+j] != original[j]) {
     80                         found = false;
     81                         break;
     82                     }
     83                 }
     84             }
     85             if (found) {
     86                 for (int j=0; j < original.length; j++) {
     87                     buffer[i+j] = replacement[j];
     88                 }
     89             }
     90         }
     91     }
     92 
     93     private static void writeBytes(File f, byte[] bytes) throws IOException {
     94         FileOutputStream out = new FileOutputStream(f);
     95         out.write(bytes);
     96         out.close();
     97     }
     98 
     99     /**
    100      * Make sure we don't fail silently for duplicate entries.
    101      * b/8219321
    102      */
    103     public void testDuplicateEntries() throws Exception {
    104         String name1 = "test_file_name1";
    105         String name2 = "test_file_name2";
    106 
    107         // Create the good zip file.
    108         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    109         ZipOutputStream out = createZipOutputStream(baos);
    110         out.putNextEntry(new ZipEntry(name2));
    111         out.closeEntry();
    112         out.putNextEntry(new ZipEntry(name1));
    113         out.closeEntry();
    114         out.close();
    115 
    116         // Rewrite one of the filenames.
    117         byte[] buffer = baos.toByteArray();
    118         replaceBytes(buffer, name2.getBytes(), name1.getBytes());
    119 
    120         // Write the result to a file.
    121         File badZip = createTemporaryZipFile();
    122         writeBytes(badZip, buffer);
    123 
    124         // Check that we refuse to load the modified file.
    125         try {
    126             ZipFile bad = new ZipFile(badZip);
    127             fail();
    128         } catch (ZipException expected) {
    129         }
    130     }
    131 
    132     /**
    133      * Make sure the size used for stored zip entires is the uncompressed size.
    134      * b/10227498
    135      */
    136     public void testStoredEntrySize() throws Exception {
    137         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    138         ZipOutputStream out = createZipOutputStream(baos);
    139 
    140         // Set up a single stored entry.
    141         String name = "test_file";
    142         int expectedLength = 5;
    143         ZipEntry outEntry = new ZipEntry(name);
    144         byte[] buffer = new byte[expectedLength];
    145         outEntry.setMethod(ZipEntry.STORED);
    146         CRC32 crc = new CRC32();
    147         crc.update(buffer);
    148         outEntry.setCrc(crc.getValue());
    149         outEntry.setSize(buffer.length);
    150 
    151         out.putNextEntry(outEntry);
    152         out.write(buffer);
    153         out.closeEntry();
    154         out.close();
    155 
    156         // Write the result to a file.
    157         byte[] outBuffer = baos.toByteArray();
    158         File zipFile = createTemporaryZipFile();
    159         writeBytes(zipFile, outBuffer);
    160 
    161         ZipFile zip = new ZipFile(zipFile);
    162         // Set up the zip entry to have different compressed/uncompressed sizes.
    163         ZipEntry ze = zip.getEntry(name);
    164         ze.setCompressedSize(expectedLength - 1);
    165         // Read the contents of the stream and verify uncompressed size was used.
    166         InputStream stream = zip.getInputStream(ze);
    167         int count = 0;
    168         int read;
    169         while ((read = stream.read(buffer)) != -1) {
    170             count += read;
    171         }
    172 
    173         assertEquals(expectedLength, count);
    174         zip.close();
    175     }
    176 
    177     public void testInflatingStreamsRequiringZipRefill() throws IOException {
    178         int originalSize = 1024 * 1024;
    179         byte[] readBuffer = new byte[8192];
    180         final File f = createTemporaryZipFile();
    181         writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */);
    182 
    183         ZipInputStream in = new ZipInputStream(new FileInputStream(f));
    184         while (in.getNextEntry() != null) {
    185             while (in.read(readBuffer, 0, readBuffer.length) != -1) {}
    186         }
    187         in.close();
    188     }
    189 
    190     public void testZipFileWithLotsOfEntries() throws IOException {
    191         int expectedEntryCount = 64*1024 - 1;
    192         final File f = createTemporaryZipFile();
    193         writeEntries(createZipOutputStream(f), expectedEntryCount, 0, false /* setEntrySize */);
    194         ZipFile zipFile = new ZipFile(f);
    195         int entryCount = 0;
    196         for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
    197             ZipEntry zipEntry = e.nextElement();
    198             ++entryCount;
    199         }
    200         assertEquals(expectedEntryCount, entryCount);
    201         zipFile.close();
    202     }
    203 
    204     // http://code.google.com/p/android/issues/detail?id=36187
    205     public void testZipFileLargerThan2GiB() throws IOException {
    206         if (false) { // TODO: this test requires too much time and too much disk space!
    207             final File f = createTemporaryZipFile();
    208             writeEntries(createZipOutputStream(f), 1024, 3*1024*1024, false /* setEntrySize */);
    209             ZipFile zipFile = new ZipFile(f);
    210             int entryCount = 0;
    211             for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
    212                 e.nextElement();
    213                 ++entryCount;
    214             }
    215             assertEquals(1024, entryCount);
    216             zipFile.close();
    217         }
    218     }
    219 
    220     /**
    221      * Compresses the given number of files, each of the given size, into a .zip archive.
    222      */
    223     protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize,
    224                                 boolean setEntrySize)
    225             throws IOException {
    226         byte[] writeBuffer = new byte[8192];
    227         Random random = new Random();
    228         try {
    229             for (int entry = 0; entry < entryCount; ++entry) {
    230                 ZipEntry ze = new ZipEntry(Integer.toHexString(entry));
    231                 if (setEntrySize) {
    232                     ze.setSize(entrySize);
    233                 }
    234                 out.putNextEntry(ze);
    235 
    236                 for (long i = 0; i < entrySize; i += writeBuffer.length) {
    237                     random.nextBytes(writeBuffer);
    238                     int byteCount = (int) Math.min(writeBuffer.length, entrySize - i);
    239                     out.write(writeBuffer, 0, byteCount);
    240                 }
    241 
    242                 out.closeEntry();
    243             }
    244         } finally {
    245             out.close();
    246         }
    247     }
    248 
    249     static File createTemporaryZipFile() throws IOException {
    250         File result = File.createTempFile("ZipFileTest", ".zip");
    251         result.deleteOnExit();
    252         return result;
    253     }
    254 
    255     private ZipOutputStream createZipOutputStream(File f) throws IOException {
    256         return createZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
    257     }
    258 
    259     protected abstract ZipOutputStream createZipOutputStream(OutputStream wrapped);
    260 
    261     public void testSTORED() throws IOException {
    262         ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
    263         CRC32 crc = new CRC32();
    264 
    265         // Missing CRC, size, and compressed size => failure.
    266         try {
    267             ZipEntry ze = new ZipEntry("a");
    268             ze.setMethod(ZipEntry.STORED);
    269             out.putNextEntry(ze);
    270             fail();
    271         } catch (ZipException expected) {
    272         }
    273 
    274         // Missing CRC and compressed size => failure.
    275         try {
    276             ZipEntry ze = new ZipEntry("a");
    277             ze.setMethod(ZipEntry.STORED);
    278             ze.setSize(0);
    279             out.putNextEntry(ze);
    280             fail();
    281         } catch (ZipException expected) {
    282         }
    283 
    284         // Missing CRC and size => failure.
    285         try {
    286             ZipEntry ze = new ZipEntry("a");
    287             ze.setMethod(ZipEntry.STORED);
    288             ze.setSize(0);
    289             ze.setCompressedSize(0);
    290             out.putNextEntry(ze);
    291             fail();
    292         } catch (ZipException expected) {
    293         }
    294 
    295         // Missing size and compressed size => failure.
    296         try {
    297             ZipEntry ze = new ZipEntry("a");
    298             ze.setMethod(ZipEntry.STORED);
    299             ze.setCrc(crc.getValue());
    300             out.putNextEntry(ze);
    301             fail();
    302         } catch (ZipException expected) {
    303         }
    304 
    305         // Missing size is copied from compressed size.
    306         {
    307             ZipEntry ze = new ZipEntry("okay1");
    308             ze.setMethod(ZipEntry.STORED);
    309             ze.setCrc(crc.getValue());
    310 
    311             assertEquals(-1, ze.getSize());
    312             assertEquals(-1, ze.getCompressedSize());
    313 
    314             ze.setCompressedSize(0);
    315 
    316             assertEquals(-1, ze.getSize());
    317             assertEquals(0, ze.getCompressedSize());
    318 
    319             out.putNextEntry(ze);
    320 
    321             assertEquals(0, ze.getSize());
    322             assertEquals(0, ze.getCompressedSize());
    323         }
    324 
    325         // Missing compressed size is copied from size.
    326         {
    327             ZipEntry ze = new ZipEntry("okay2");
    328             ze.setMethod(ZipEntry.STORED);
    329             ze.setCrc(crc.getValue());
    330 
    331             assertEquals(-1, ze.getSize());
    332             assertEquals(-1, ze.getCompressedSize());
    333 
    334             ze.setSize(0);
    335 
    336             assertEquals(0, ze.getSize());
    337             assertEquals(-1, ze.getCompressedSize());
    338 
    339             out.putNextEntry(ze);
    340 
    341             assertEquals(0, ze.getSize());
    342             assertEquals(0, ze.getCompressedSize());
    343         }
    344 
    345         // Mismatched size and compressed size => failure.
    346         try {
    347             ZipEntry ze = new ZipEntry("a");
    348             ze.setMethod(ZipEntry.STORED);
    349             ze.setCrc(crc.getValue());
    350             ze.setCompressedSize(1);
    351             ze.setSize(0);
    352             out.putNextEntry(ze);
    353             fail();
    354         } catch (ZipException expected) {
    355         }
    356 
    357         // Everything present => success.
    358         ZipEntry ze = new ZipEntry("okay");
    359         ze.setMethod(ZipEntry.STORED);
    360         ze.setCrc(crc.getValue());
    361         ze.setSize(0);
    362         ze.setCompressedSize(0);
    363         out.putNextEntry(ze);
    364 
    365         out.close();
    366     }
    367 
    368     private String makeString(int count, String ch) {
    369         StringBuilder sb = new StringBuilder();
    370         for (int i = 0; i < count; ++i) {
    371             sb.append(ch);
    372         }
    373         return sb.toString();
    374     }
    375 
    376     public void testComments() throws Exception {
    377         String expectedFileComment = "1 \u0666 2";
    378         String expectedEntryComment = "a \u0666 b";
    379 
    380         File file = createTemporaryZipFile();
    381         ZipOutputStream out = createZipOutputStream(file);
    382 
    383         // Is file comment length checking done on bytes or characters? (Should be bytes.)
    384         out.setComment(null);
    385         out.setComment(makeString(0xffff, "a"));
    386         try {
    387             out.setComment(makeString(0xffff + 1, "a"));
    388             fail();
    389         } catch (IllegalArgumentException expected) {
    390         }
    391         try {
    392             out.setComment(makeString(0xffff, "\u0666"));
    393             fail();
    394         } catch (IllegalArgumentException expected) {
    395         }
    396 
    397         ZipEntry ze = new ZipEntry("a");
    398 
    399         // Is entry comment length checking done on bytes or characters? (Should be bytes.)
    400         ze.setComment(null);
    401         ze.setComment(makeString(0xffff, "a"));
    402         try {
    403             ze.setComment(makeString(0xffff + 1, "a"));
    404             fail();
    405         } catch (IllegalArgumentException expected) {
    406         }
    407         try {
    408             ze.setComment(makeString(0xffff, "\u0666"));
    409             fail();
    410         } catch (IllegalArgumentException expected) {
    411         }
    412 
    413         ze.setComment(expectedEntryComment);
    414         out.putNextEntry(ze);
    415         out.closeEntry();
    416 
    417         out.setComment(expectedFileComment);
    418         out.close();
    419 
    420         ZipFile zipFile = new ZipFile(file);
    421         assertEquals(expectedFileComment, zipFile.getComment());
    422         assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment());
    423         zipFile.close();
    424     }
    425 
    426     public void test_getComment_unset() throws Exception {
    427         File file = createTemporaryZipFile();
    428         ZipOutputStream out = createZipOutputStream(file);
    429         ZipEntry ze = new ZipEntry("test entry");
    430         ze.setComment("per-entry comment");
    431         out.putNextEntry(ze);
    432         out.close();
    433 
    434         try (ZipFile zipFile = new ZipFile(file)) {
    435             assertEquals(null, zipFile.getComment());
    436         }
    437     }
    438 
    439     // https://code.google.com/p/android/issues/detail?id=58465
    440     public void test_NUL_in_filename() throws Exception {
    441         File file = createTemporaryZipFile();
    442 
    443         // We allow creation of a ZipEntry whose name contains a NUL byte,
    444         // mainly because it's not likely to happen by accident and it's useful for testing.
    445         ZipOutputStream out = createZipOutputStream(file);
    446         out.putNextEntry(new ZipEntry("hello"));
    447         out.putNextEntry(new ZipEntry("hello\u0000"));
    448         out.close();
    449 
    450         // But you can't open a ZIP file containing such an entry, because we reject it
    451         // when we find it in the central directory.
    452         try {
    453             ZipFile zipFile = new ZipFile(file);
    454             fail();
    455         } catch (ZipException expected) {
    456         }
    457     }
    458 
    459     public void testCrc() throws IOException {
    460         ZipEntry ze = new ZipEntry("test");
    461         ze.setMethod(ZipEntry.STORED);
    462         ze.setSize(4);
    463 
    464         // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits).
    465         try {
    466             ze.setCrc(-1);
    467             fail();
    468         } catch (IllegalArgumentException expected) {
    469         }
    470 
    471         // You can set the CRC32 to 0xffffffff if you're slightly more careful though...
    472         ze.setCrc(0xffffffffL);
    473         assertEquals(0xffffffffL, ze.getCrc());
    474 
    475         // And it actually works, even though we use -1L to mean "no CRC set"...
    476         ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
    477         out.putNextEntry(ze);
    478         out.write(-1);
    479         out.write(-1);
    480         out.write(-1);
    481         out.write(-1);
    482         out.closeEntry();
    483         out.close();
    484     }
    485 
    486     /**
    487      * RI does not allow reading of an empty zip using a {@link ZipFile}.
    488      */
    489     public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException {
    490 
    491         File resources = Support_Resources.createTempFolder();
    492         File emptyZip = Support_Resources.copyFile(
    493                 resources, "java/util/zip", "EmptyArchive.zip");
    494 
    495         try {
    496             // The following should fail with an exception but if it doesn't then we need to clean
    497             // up the resource so we need a reference to it.
    498             ZipFile zipFile = new ZipFile(emptyZip);
    499 
    500             // Clean up the resource.
    501             try {
    502                 zipFile.close();
    503             } catch (Exception e) {
    504                 // Ignore
    505             }
    506             fail();
    507         } catch (ZipException expected) {
    508             // expected
    509         }
    510     }
    511 
    512     // Demonstrates http://b/18644314 : Zip entry names are relative to the point of
    513     // extraction and can contain relative paths "../" and "./".
    514     //
    515     // It is left to callers of the API to perform any validation / santization to
    516     // ensure that files are not written outside of the destination directory, where that
    517     // is a concern.
    518     public void testArchivesWithRelativePaths() throws IOException {
    519         String[] entryNames = {
    520                 "../",
    521                 "../foo.bar",
    522                 "foo/../../",
    523                 "foo/../../bar.baz"
    524         };
    525 
    526         File zip = createTemporaryZipFile();
    527         ZipOutputStream out = createZipOutputStream(zip);
    528 
    529         try {
    530             byte[] entryData = new byte[1024];
    531             for (String entryName : entryNames) {
    532                 ZipEntry ze = new ZipEntry(entryName);
    533                 out.putNextEntry(ze);
    534                 out.write(entryData);
    535                 out.closeEntry();
    536             }
    537         } finally {
    538             out.close();
    539         }
    540 
    541         ZipFile zf = new ZipFile(zip, ZipFile.OPEN_READ);
    542         Enumeration<? extends ZipEntry> entries = zf.entries();
    543         Set<String> entryNamesFromFile = new HashSet<>();
    544         while (entries.hasMoreElements()) {
    545             ZipEntry ze = entries.nextElement();
    546             entryNamesFromFile.add(ze.getName());
    547         }
    548 
    549         zf.close();
    550 
    551         for (String entryName : entryNames) {
    552             assertTrue(entryNamesFromFile.contains(entryName));
    553         }
    554     }
    555 }
    556