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