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 import libcore.io.IoUtils;
     37 
     38 public final class ZipFileTest extends TestCase {
     39     /**
     40      * Exercise Inflater's ability to refill the zlib's input buffer. As of this
     41      * writing, this buffer's max size is 64KiB compressed bytes. We'll write a
     42      * full megabyte of uncompressed data, which should be sufficient to exhaust
     43      * the buffer. http://b/issue?id=2734751
     44      */
     45     public void testInflatingFilesRequiringZipRefill() throws IOException {
     46         int originalSize = 1024 * 1024;
     47         byte[] readBuffer = new byte[8192];
     48         ZipFile zipFile = new ZipFile(createZipFile(1, originalSize));
     49         for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
     50             ZipEntry zipEntry = e.nextElement();
     51             assertTrue("This test needs >64 KiB of compressed data to exercise Inflater",
     52                     zipEntry.getCompressedSize() > (64 * 1024));
     53             InputStream is = zipFile.getInputStream(zipEntry);
     54             while (is.read(readBuffer, 0, readBuffer.length) != -1) {}
     55             is.close();
     56         }
     57         zipFile.close();
     58     }
     59 
     60     private static void replaceBytes(byte[] original, byte[] replacement, byte[] buffer) {
     61         // Gotcha here: original and replacement must be the same length
     62         assertEquals(original.length, replacement.length);
     63         boolean found;
     64         for(int i=0; i < buffer.length - original.length; i++) {
     65             found = false;
     66             if (buffer[i] == original[0]) {
     67                 found = true;
     68                 for (int j=0; j < original.length; j++) {
     69                     if (buffer[i+j] != original[j]) {
     70                         found = false;
     71                         break;
     72                     }
     73                 }
     74             }
     75             if (found) {
     76                 for (int j=0; j < original.length; j++) {
     77                     buffer[i+j] = replacement[j];
     78                 }
     79             }
     80         }
     81     }
     82 
     83     /**
     84      * Make sure we don't fail silently for duplicate entries.
     85      * b/8219321
     86      */
     87     public void testDuplicateEntries() throws IOException {
     88         String entryName = "test_file_name1";
     89         String tmpName = "test_file_name2";
     90 
     91         // create the template data
     92         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
     93         ZipOutputStream out = new ZipOutputStream(bytesOut);
     94         ZipEntry ze1 = new ZipEntry(tmpName);
     95         out.putNextEntry(ze1);
     96         out.closeEntry();
     97         ZipEntry ze2 = new ZipEntry(entryName);
     98         out.putNextEntry(ze2);
     99         out.closeEntry();
    100         out.close();
    101 
    102         // replace the bytes we don't like
    103         byte[] buf = bytesOut.toByteArray();
    104         replaceBytes(tmpName.getBytes(), entryName.getBytes(), buf);
    105 
    106         // write the result to a file
    107         File badZip = File.createTempFile("badzip", "zip");
    108         badZip.deleteOnExit();
    109         FileOutputStream outstream = new FileOutputStream(badZip);
    110         outstream.write(buf);
    111         outstream.close();
    112 
    113         // see if we can still handle it
    114         try {
    115             ZipFile bad = new ZipFile(badZip);
    116             fail();
    117         } catch (ZipException expected) {
    118         }
    119     }
    120 
    121     public void testInflatingStreamsRequiringZipRefill() throws IOException {
    122         int originalSize = 1024 * 1024;
    123         byte[] readBuffer = new byte[8192];
    124         ZipInputStream in = new ZipInputStream(new FileInputStream(createZipFile(1, originalSize)));
    125         while (in.getNextEntry() != null) {
    126             while (in.read(readBuffer, 0, readBuffer.length) != -1) {}
    127         }
    128         in.close();
    129     }
    130 
    131     public void testZipFileWithLotsOfEntries() throws IOException {
    132         int expectedEntryCount = 64*1024 - 1;
    133         File f = createZipFile(expectedEntryCount, 0);
    134         ZipFile zipFile = new ZipFile(f);
    135         int entryCount = 0;
    136         for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
    137             ZipEntry zipEntry = e.nextElement();
    138             ++entryCount;
    139         }
    140         assertEquals(expectedEntryCount, entryCount);
    141         zipFile.close();
    142     }
    143 
    144     // http://code.google.com/p/android/issues/detail?id=36187
    145     public void testZipFileLargerThan2GiB() throws IOException {
    146         if (false) { // TODO: this test requires too much time and too much disk space!
    147             File f = createZipFile(1024, 3*1024*1024);
    148             ZipFile zipFile = new ZipFile(f);
    149             int entryCount = 0;
    150             for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
    151                 ZipEntry zipEntry = e.nextElement();
    152                 ++entryCount;
    153             }
    154             assertEquals(1024, entryCount);
    155             zipFile.close();
    156         }
    157     }
    158 
    159     public void testZip64Support() throws IOException {
    160         try {
    161             createZipFile(64*1024, 0);
    162             fail(); // Make this test more like testHugeZipFile when we have Zip64 support.
    163         } catch (ZipException expected) {
    164         }
    165     }
    166 
    167     /**
    168      * Compresses the given number of files, each of the given size, into a .zip archive.
    169      */
    170     private File createZipFile(int entryCount, int entrySize) throws IOException {
    171         File result = createTemporaryZipFile();
    172 
    173         byte[] writeBuffer = new byte[8192];
    174         Random random = new Random();
    175 
    176         ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(result)));
    177         for (int entry = 0; entry < entryCount; ++entry) {
    178             ZipEntry ze = new ZipEntry(Integer.toHexString(entry));
    179             out.putNextEntry(ze);
    180 
    181             for (int i = 0; i < entrySize; i += writeBuffer.length) {
    182                 random.nextBytes(writeBuffer);
    183                 int byteCount = Math.min(writeBuffer.length, entrySize - i);
    184                 out.write(writeBuffer, 0, byteCount);
    185             }
    186 
    187             out.closeEntry();
    188         }
    189 
    190         out.close();
    191         return result;
    192     }
    193 
    194     private File createTemporaryZipFile() throws IOException {
    195         File result = File.createTempFile("ZipFileTest", "zip");
    196         result.deleteOnExit();
    197         return result;
    198     }
    199 
    200     private ZipOutputStream createZipOutputStream(File f) throws IOException {
    201         return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
    202     }
    203 
    204     public void testSTORED() throws IOException {
    205         ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
    206         CRC32 crc = new CRC32();
    207 
    208         // Missing CRC, size, and compressed size => failure.
    209         try {
    210             ZipEntry ze = new ZipEntry("a");
    211             ze.setMethod(ZipEntry.STORED);
    212             out.putNextEntry(ze);
    213             fail();
    214         } catch (ZipException expected) {
    215         }
    216 
    217         // Missing CRC and compressed size => failure.
    218         try {
    219             ZipEntry ze = new ZipEntry("a");
    220             ze.setMethod(ZipEntry.STORED);
    221             ze.setSize(0);
    222             out.putNextEntry(ze);
    223             fail();
    224         } catch (ZipException expected) {
    225         }
    226 
    227         // Missing CRC and size => failure.
    228         try {
    229             ZipEntry ze = new ZipEntry("a");
    230             ze.setMethod(ZipEntry.STORED);
    231             ze.setSize(0);
    232             ze.setCompressedSize(0);
    233             out.putNextEntry(ze);
    234             fail();
    235         } catch (ZipException expected) {
    236         }
    237 
    238         // Missing size and compressed size => failure.
    239         try {
    240             ZipEntry ze = new ZipEntry("a");
    241             ze.setMethod(ZipEntry.STORED);
    242             ze.setCrc(crc.getValue());
    243             out.putNextEntry(ze);
    244             fail();
    245         } catch (ZipException expected) {
    246         }
    247 
    248         // Missing size is copied from compressed size.
    249         {
    250             ZipEntry ze = new ZipEntry("okay1");
    251             ze.setMethod(ZipEntry.STORED);
    252             ze.setCrc(crc.getValue());
    253 
    254             assertEquals(-1, ze.getSize());
    255             assertEquals(-1, ze.getCompressedSize());
    256 
    257             ze.setCompressedSize(0);
    258 
    259             assertEquals(-1, ze.getSize());
    260             assertEquals(0, ze.getCompressedSize());
    261 
    262             out.putNextEntry(ze);
    263 
    264             assertEquals(0, ze.getSize());
    265             assertEquals(0, ze.getCompressedSize());
    266         }
    267 
    268         // Missing compressed size is copied from size.
    269         {
    270             ZipEntry ze = new ZipEntry("okay2");
    271             ze.setMethod(ZipEntry.STORED);
    272             ze.setCrc(crc.getValue());
    273 
    274             assertEquals(-1, ze.getSize());
    275             assertEquals(-1, ze.getCompressedSize());
    276 
    277             ze.setSize(0);
    278 
    279             assertEquals(0, ze.getSize());
    280             assertEquals(-1, ze.getCompressedSize());
    281 
    282             out.putNextEntry(ze);
    283 
    284             assertEquals(0, ze.getSize());
    285             assertEquals(0, ze.getCompressedSize());
    286         }
    287 
    288         // Mismatched size and compressed size => failure.
    289         try {
    290             ZipEntry ze = new ZipEntry("a");
    291             ze.setMethod(ZipEntry.STORED);
    292             ze.setCrc(crc.getValue());
    293             ze.setCompressedSize(1);
    294             ze.setSize(0);
    295             out.putNextEntry(ze);
    296             fail();
    297         } catch (ZipException expected) {
    298         }
    299 
    300         // Everything present => success.
    301         ZipEntry ze = new ZipEntry("okay");
    302         ze.setMethod(ZipEntry.STORED);
    303         ze.setCrc(crc.getValue());
    304         ze.setSize(0);
    305         ze.setCompressedSize(0);
    306         out.putNextEntry(ze);
    307 
    308         out.close();
    309     }
    310 
    311     private String makeString(int count, String ch) {
    312         StringBuilder sb = new StringBuilder();
    313         for (int i = 0; i < count; ++i) {
    314             sb.append(ch);
    315         }
    316         return sb.toString();
    317     }
    318 
    319     public void testComment() throws Exception {
    320         String expectedFileComment = "1 \u0666 2";
    321         String expectedEntryComment = "a \u0666 b";
    322 
    323         File file = createTemporaryZipFile();
    324         ZipOutputStream out = createZipOutputStream(file);
    325 
    326         // Is file comment length checking done on bytes or characters? (Should be bytes.)
    327         out.setComment(null);
    328         out.setComment(makeString(0xffff, "a"));
    329         try {
    330             out.setComment(makeString(0xffff + 1, "a"));
    331             fail();
    332         } catch (IllegalArgumentException expected) {
    333         }
    334         try {
    335             out.setComment(makeString(0xffff, "\u0666"));
    336             fail();
    337         } catch (IllegalArgumentException expected) {
    338         }
    339 
    340         ZipEntry ze = new ZipEntry("a");
    341 
    342         // Is entry comment length checking done on bytes or characters? (Should be bytes.)
    343         ze.setComment(null);
    344         ze.setComment(makeString(0xffff, "a"));
    345         try {
    346             ze.setComment(makeString(0xffff + 1, "a"));
    347             fail();
    348         } catch (IllegalArgumentException expected) {
    349         }
    350         try {
    351             ze.setComment(makeString(0xffff, "\u0666"));
    352             fail();
    353         } catch (IllegalArgumentException expected) {
    354         }
    355 
    356         ze.setComment(expectedEntryComment);
    357         out.putNextEntry(ze);
    358         out.closeEntry();
    359 
    360         out.setComment(expectedFileComment);
    361         out.close();
    362 
    363         ZipFile zipFile = new ZipFile(file);
    364         // TODO: there's currently no API for reading the file comment --- strings(1) the file?
    365         assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment());
    366         zipFile.close();
    367     }
    368 
    369     public void testNameLengthChecks() throws IOException {
    370         // Is entry name length checking done on bytes or characters?
    371         // Really it should be bytes, but the RI only checks characters at construction time.
    372         // Android does the same, because it's cheap...
    373         try {
    374             new ZipEntry((String) null);
    375             fail();
    376         } catch (NullPointerException expected) {
    377         }
    378         new ZipEntry(makeString(0xffff, "a"));
    379         try {
    380             new ZipEntry(makeString(0xffff + 1, "a"));
    381             fail();
    382         } catch (IllegalArgumentException expected) {
    383         }
    384 
    385         // ...but Android won't let you create a zip file with a truncated name.
    386         ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
    387         ZipEntry ze = new ZipEntry(makeString(0xffff, "\u0666"));
    388         try {
    389             out.putNextEntry(ze);
    390             fail(); // The RI fails this test; it just checks the character count at construction time.
    391         } catch (IllegalArgumentException expected) {
    392         }
    393         out.closeEntry();
    394         out.putNextEntry(new ZipEntry("okay")); // ZipOutputStream.close throws if you add nothing!
    395         out.close();
    396     }
    397 
    398     public void testCrc() throws IOException {
    399         ZipEntry ze = new ZipEntry("test");
    400         ze.setMethod(ZipEntry.STORED);
    401         ze.setSize(4);
    402 
    403         // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits).
    404         try {
    405             ze.setCrc(-1);
    406         } catch (IllegalArgumentException expected) {
    407         }
    408 
    409         // You can set the CRC32 to 0xffffffff if you're slightly more careful though...
    410         ze.setCrc(0xffffffffL);
    411         assertEquals(0xffffffffL, ze.getCrc());
    412 
    413         // And it actually works, even though we use -1L to mean "no CRC set"...
    414         ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
    415         out.putNextEntry(ze);
    416         out.write(-1);
    417         out.write(-1);
    418         out.write(-1);
    419         out.write(-1);
    420         out.closeEntry();
    421         out.close();
    422     }
    423 }
    424