• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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