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