View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers;
20  
21  import static org.junit.Assert.*;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.OutputStream;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Enumeration;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.zip.ZipEntry;
37  import java.util.zip.ZipException;
38  
39  import org.apache.commons.compress.AbstractTestCase;
40  import org.apache.commons.compress.archivers.zip.Zip64Mode;
41  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
42  import org.apache.commons.compress.archivers.zip.ZipArchiveEntryPredicate;
43  import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
44  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
45  import org.apache.commons.compress.archivers.zip.ZipFile;
46  import org.apache.commons.compress.archivers.zip.ZipMethod;
47  import org.apache.commons.compress.utils.IOUtils;
48  import org.apache.commons.compress.utils.InputStreamStatistics;
49  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
50  import org.junit.Assert;
51  import org.junit.Test;
52  
53  public final class ZipTestCase extends AbstractTestCase {
54      /**
55       * Archives 2 files and unarchives it again. If the file length of result
56       * and source is the same, it looks like the operations have worked
57       * @throws Exception
58       */
59      @Test
60      public void testZipArchiveCreation() throws Exception {
61          // Archive
62          final File output = new File(dir, "bla.zip");
63          final File file1 = getFile("test1.xml");
64          final File file2 = getFile("test2.xml");
65  
66          final OutputStream out = new FileOutputStream(output);
67          ArchiveOutputStream os = null;
68          try {
69              os = new ArchiveStreamFactory()
70                  .createArchiveOutputStream("zip", out);
71              os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
72              IOUtils.copy(new FileInputStream(file1), os);
73              os.closeArchiveEntry();
74  
75              os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
76              IOUtils.copy(new FileInputStream(file2), os);
77              os.closeArchiveEntry();
78          } finally {
79              if (os != null) {
80                  os.close();
81              }
82          }
83          out.close();
84  
85          // Unarchive the same
86          final List<File> results = new ArrayList<>();
87  
88          final InputStream is = new FileInputStream(output);
89          ArchiveInputStream in = null;
90          try {
91              in = new ArchiveStreamFactory()
92                  .createArchiveInputStream("zip", is);
93  
94              ZipArchiveEntry entry = null;
95              while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) {
96                  final File outfile = new File(resultDir.getCanonicalPath() + "/result/" + entry.getName());
97                  outfile.getParentFile().mkdirs();
98                  try (OutputStream o = new FileOutputStream(outfile)) {
99                      IOUtils.copy(in, o);
100                 }
101                 results.add(outfile);
102             }
103         } finally {
104             if (in != null) {
105                 in.close();
106             }
107         }
108         is.close();
109 
110         assertEquals(results.size(), 2);
111         File result = results.get(0);
112         assertEquals(file1.length(), result.length());
113         result = results.get(1);
114         assertEquals(file2.length(), result.length());
115     }
116 
117     /**
118      * Archives 2 files and unarchives it again. If the file contents of result
119      * and source is the same, it looks like the operations have worked
120      * @throws Exception
121      */
122     @Test
123     public void testZipArchiveCreationInMemory() throws Exception {
124         final File file1 = getFile("test1.xml");
125         final File file2 = getFile("test2.xml");
126         final byte[] file1Contents = new byte[(int) file1.length()];
127         final byte[] file2Contents = new byte[(int) file2.length()];
128         IOUtils.readFully(new FileInputStream(file1), file1Contents);
129         IOUtils.readFully(new FileInputStream(file2), file2Contents);
130 
131         SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel();
132         try (ZipArchiveOutputStream os = new ZipArchiveOutputStream(channel)) {
133             os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
134             os.write(file1Contents);
135             os.closeArchiveEntry();
136 
137             os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
138             os.write(file2Contents);
139             os.closeArchiveEntry();
140         }
141 
142         // Unarchive the same
143         final List<byte[]> results = new ArrayList<>();
144 
145         try (ArchiveInputStream in = new ArchiveStreamFactory()
146              .createArchiveInputStream("zip", new ByteArrayInputStream(channel.array()))) {
147 
148             ZipArchiveEntry entry;
149             while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) {
150                 byte[] result = new byte[(int) entry.getSize()];
151                 IOUtils.readFully(in, result);
152                 results.add(result);
153             }
154         }
155 
156         assertArrayEquals(results.get(0), file1Contents);
157         assertArrayEquals(results.get(1), file2Contents);
158     }
159 
160     /**
161      * Simple unarchive test. Asserts nothing.
162      * @throws Exception
163      */
164     @Test
165     public void testZipUnarchive() throws Exception {
166         final File input = getFile("bla.zip");
167         try (final InputStream is = new FileInputStream(input);
168                 final ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream("zip", is)) {
169             final ZipArchiveEntry entry = (ZipArchiveEntry) in.getNextEntry();
170             try (final OutputStream out = new FileOutputStream(new File(dir, entry.getName()))) {
171                 IOUtils.copy(in, out);
172             }
173         }
174     }
175 
176     /**
177      * Test case for
178      * <a href="https://issues.apache.org/jira/browse/COMPRESS-208"
179      * >COMPRESS-208</a>.
180      */
181     @Test
182     public void testSkipsPK00Prefix() throws Exception {
183         final File input = getFile("COMPRESS-208.zip");
184         final ArrayList<String> al = new ArrayList<>();
185         al.add("test1.xml");
186         al.add("test2.xml");
187         try (InputStream is = new FileInputStream(input)) {
188             checkArchiveContent(new ZipArchiveInputStream(is), al);
189         }
190     }
191 
192     /**
193      * Test case for
194      * <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
195      * >COMPRESS-93</a>.
196      */
197     @Test
198     public void testSupportedCompressionMethod() throws IOException {
199         /*
200         ZipFile bla = new ZipFile(getFile("bla.zip"));
201         assertTrue(bla.canReadEntryData(bla.getEntry("test1.xml")));
202         bla.close();
203         */
204 
205         final ZipFile moby = new ZipFile(getFile("moby.zip"));
206         final ZipArchiveEntry entry = moby.getEntry("README");
207         assertEquals("method", ZipMethod.TOKENIZATION.getCode(), entry.getMethod());
208         assertFalse(moby.canReadEntryData(entry));
209         moby.close();
210     }
211 
212     /**
213      * Test case for being able to skip an entry in an
214      * {@link ZipArchiveInputStream} even if the compression method of that
215      * entry is unsupported.
216      *
217      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
218      *        >COMPRESS-93</a>
219      */
220     @Test
221     public void testSkipEntryWithUnsupportedCompressionMethod()
222             throws IOException {
223         try (ZipArchiveInputStream zip = new ZipArchiveInputStream(new FileInputStream(getFile("moby.zip")))) {
224             final ZipArchiveEntry entry = zip.getNextZipEntry();
225             assertEquals("method", ZipMethod.TOKENIZATION.getCode(), entry.getMethod());
226             assertEquals("README", entry.getName());
227             assertFalse(zip.canReadEntryData(entry));
228             try {
229                 assertNull(zip.getNextZipEntry());
230             } catch (final IOException e) {
231                 e.printStackTrace();
232                 fail("COMPRESS-93: Unable to skip an unsupported zip entry");
233             }
234         }
235     }
236 
237     /**
238      * Checks if all entries from a nested archive can be read.
239      * The archive: OSX_ArchiveWithNestedArchive.zip contains:
240      * NestedArchiv.zip and test.xml3.
241      *
242      * The nested archive:  NestedArchive.zip contains test1.xml and test2.xml
243      *
244      * @throws Exception
245      */
246     @Test
247     public void testListAllFilesWithNestedArchive() throws Exception {
248         final File input = getFile("OSX_ArchiveWithNestedArchive.zip");
249 
250         final List<String> results = new ArrayList<>();
251         final List<ZipException> expectedExceptions = new ArrayList<>();
252 
253         final InputStream is = new FileInputStream(input);
254         ArchiveInputStream in = null;
255         try {
256             in = new ArchiveStreamFactory().createArchiveInputStream("zip", is);
257 
258             ZipArchiveEntry entry = null;
259             while ((entry = (ZipArchiveEntry) in.getNextEntry()) != null) {
260                 results.add(entry.getName());
261 
262                 final ArchiveInputStream nestedIn = new ArchiveStreamFactory().createArchiveInputStream("zip", in);
263                 try {
264                     ZipArchiveEntry nestedEntry = null;
265                     while ((nestedEntry = (ZipArchiveEntry) nestedIn.getNextEntry()) != null) {
266                         results.add(nestedEntry.getName());
267                     }
268                 } catch (ZipException ex) {
269                     // expected since you cannot create a final ArchiveInputStream from test3.xml
270                     expectedExceptions.add(ex);
271                 }
272                 // nested stream must not be closed here
273             }
274         } finally {
275             if (in != null) {
276                 in.close();
277             }
278         }
279         is.close();
280 
281         assertTrue(results.contains("NestedArchiv.zip"));
282         assertTrue(results.contains("test1.xml"));
283         assertTrue(results.contains("test2.xml"));
284         assertTrue(results.contains("test3.xml"));
285         assertEquals(1, expectedExceptions.size());
286     }
287 
288     @Test
289     public void testDirectoryEntryFromFile() throws Exception {
290         final File[] tmp = createTempDirAndFile();
291         File archive = null;
292         ZipArchiveOutputStream zos = null;
293         ZipFile zf = null;
294         try {
295             archive = File.createTempFile("test.", ".zip", tmp[0]);
296             archive.deleteOnExit();
297             zos = new ZipArchiveOutputStream(archive);
298             final long beforeArchiveWrite = tmp[0].lastModified();
299             final ZipArchiveEntry in = new ZipArchiveEntry(tmp[0], "foo");
300             zos.putArchiveEntry(in);
301             zos.closeArchiveEntry();
302             zos.close();
303             zos = null;
304             zf = new ZipFile(archive);
305             final ZipArchiveEntry out = zf.getEntry("foo/");
306             assertNotNull(out);
307             assertEquals("foo/", out.getName());
308             assertEquals(0, out.getSize());
309             // ZIP stores time with a granularity of 2 seconds
310             assertEquals(beforeArchiveWrite / 2000,
311                          out.getLastModifiedDate().getTime() / 2000);
312             assertTrue(out.isDirectory());
313         } finally {
314             ZipFile.closeQuietly(zf);
315             if (zos != null) {
316                 zos.close();
317             }
318             tryHardToDelete(archive);
319             tryHardToDelete(tmp[1]);
320             rmdir(tmp[0]);
321         }
322     }
323 
324     @Test
325     public void testExplicitDirectoryEntry() throws Exception {
326         final File[] tmp = createTempDirAndFile();
327         File archive = null;
328         ZipArchiveOutputStream zos = null;
329         ZipFile zf = null;
330         try {
331             archive = File.createTempFile("test.", ".zip", tmp[0]);
332             archive.deleteOnExit();
333             zos = new ZipArchiveOutputStream(archive);
334             final long beforeArchiveWrite = tmp[0].lastModified();
335             final ZipArchiveEntry in = new ZipArchiveEntry("foo/");
336             in.setTime(beforeArchiveWrite);
337             zos.putArchiveEntry(in);
338             zos.closeArchiveEntry();
339             zos.close();
340             zos = null;
341             zf = new ZipFile(archive);
342             final ZipArchiveEntry out = zf.getEntry("foo/");
343             assertNotNull(out);
344             assertEquals("foo/", out.getName());
345             assertEquals(0, out.getSize());
346             assertEquals(beforeArchiveWrite / 2000,
347                          out.getLastModifiedDate().getTime() / 2000);
348             assertTrue(out.isDirectory());
349         } finally {
350             ZipFile.closeQuietly(zf);
351             if (zos != null) {
352                 zos.close();
353             }
354             tryHardToDelete(archive);
355             tryHardToDelete(tmp[1]);
356             rmdir(tmp[0]);
357         }
358     }
359     String first_payload = "ABBA";
360     String second_payload = "AAAAAAAAAAAA";
361     ZipArchiveEntryPredicate allFilesPredicate = new ZipArchiveEntryPredicate() {
362         @Override
363         public boolean test(final ZipArchiveEntry zipArchiveEntry) {
364             return true;
365         }
366     };
367 
368     @Test
369     public void testCopyRawEntriesFromFile()
370             throws IOException {
371 
372         final File[] tmp = createTempDirAndFile();
373         final File reference = createReferenceFile(tmp[0], Zip64Mode.Never, "expected.");
374 
375         final File a1 = File.createTempFile("src1.", ".zip", tmp[0]);
376         try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1)) {
377             zos.setUseZip64(Zip64Mode.Never);
378             createFirstEntry(zos).close();
379         }
380 
381         final File a2 = File.createTempFile("src2.", ".zip", tmp[0]);
382         try (final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(a2)) {
383             zos1.setUseZip64(Zip64Mode.Never);
384             createSecondEntry(zos1).close();
385         }
386 
387         try (final ZipFile zf1 = new ZipFile(a1); final ZipFile zf2 = new ZipFile(a2)) {
388             final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]);
389             try (final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult)) {
390                 zf1.copyRawEntries(zos2, allFilesPredicate);
391                 zf2.copyRawEntries(zos2, allFilesPredicate);
392             }
393             // copyRawEntries does not add superfluous zip64 header like regular zip output stream
394             // does when using Zip64Mode.AsNeeded so all the source material has to be Zip64Mode.Never,
395             // if exact binary equality is to be achieved
396             assertSameFileContents(reference, fileResult);
397         }
398     }
399 
400     @Test
401     public void testCopyRawZip64EntryFromFile()
402             throws IOException {
403 
404         final File[] tmp = createTempDirAndFile();
405         final File reference = File.createTempFile("z64reference.", ".zip", tmp[0]);
406         try (final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(reference)) {
407             zos1.setUseZip64(Zip64Mode.Always);
408             createFirstEntry(zos1);
409         }
410 
411         final File a1 = File.createTempFile("zip64src.", ".zip", tmp[0]);
412         try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1)) {
413             zos.setUseZip64(Zip64Mode.Always);
414             createFirstEntry(zos).close();
415         }
416 
417         final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]);
418         try (final ZipFile zf1 = new ZipFile(a1)) {
419             try (final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult)) {
420                 zos2.setUseZip64(Zip64Mode.Always);
421                 zf1.copyRawEntries(zos2, allFilesPredicate);
422             }
423             assertSameFileContents(reference, fileResult);
424         }
425     }
426 
427     @Test
428     public void testUnixModeInAddRaw() throws IOException {
429 
430         final File[] tmp = createTempDirAndFile();
431 
432         final File a1 = File.createTempFile("unixModeBits.", ".zip", tmp[0]);
433         try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1)) {
434 
435             final ZipArchiveEntry archiveEntry = new ZipArchiveEntry("fred");
436             archiveEntry.setUnixMode(0664);
437             archiveEntry.setMethod(ZipEntry.DEFLATED);
438             zos.addRawArchiveEntry(archiveEntry, new ByteArrayInputStream("fud".getBytes()));
439         }
440 
441         try (final ZipFile zf1 = new ZipFile(a1)) {
442             final ZipArchiveEntry fred = zf1.getEntry("fred");
443             assertEquals(0664, fred.getUnixMode());
444         }
445     }
446 
447     private File createReferenceFile(final File directory, final Zip64Mode zipMode, final String prefix)
448             throws IOException {
449         final File reference = File.createTempFile(prefix, ".zip", directory);
450         try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(reference)) {
451             zos.setUseZip64(zipMode);
452             createFirstEntry(zos);
453             createSecondEntry(zos);
454         }
455         return reference;
456     }
457 
458     private ZipArchiveOutputStream createFirstEntry(final ZipArchiveOutputStream zos) throws IOException {
459         createArchiveEntry(first_payload, zos, "file1.txt");
460         return zos;
461     }
462 
463     private ZipArchiveOutputStream createSecondEntry(final ZipArchiveOutputStream zos) throws IOException {
464         createArchiveEntry(second_payload, zos, "file2.txt");
465         return zos;
466     }
467 
468 
469     private void assertSameFileContents(final File expectedFile, final File actualFile) throws IOException {
470         final int size = (int) Math.max(expectedFile.length(), actualFile.length());
471         try (final ZipFile expected = new ZipFile(expectedFile); final ZipFile actual = new ZipFile(actualFile)) {
472             final byte[] expectedBuf = new byte[size];
473             final byte[] actualBuf = new byte[size];
474 
475             final Enumeration<ZipArchiveEntry> actualInOrder = actual.getEntriesInPhysicalOrder();
476             final Enumeration<ZipArchiveEntry> expectedInOrder = expected.getEntriesInPhysicalOrder();
477 
478             while (actualInOrder.hasMoreElements()) {
479                 final ZipArchiveEntry actualElement = actualInOrder.nextElement();
480                 final ZipArchiveEntry expectedElement = expectedInOrder.nextElement();
481                 assertEquals(expectedElement.getName(), actualElement.getName());
482                 // Don't compare timestamps since they may vary;
483                 // there's no support for stubbed out clock (TimeSource) in ZipArchiveOutputStream
484                 assertEquals(expectedElement.getMethod(), actualElement.getMethod());
485                 assertEquals(expectedElement.getGeneralPurposeBit(), actualElement.getGeneralPurposeBit());
486                 assertEquals(expectedElement.getCrc(), actualElement.getCrc());
487                 assertEquals(expectedElement.getCompressedSize(), actualElement.getCompressedSize());
488                 assertEquals(expectedElement.getSize(), actualElement.getSize());
489                 assertEquals(expectedElement.getExternalAttributes(), actualElement.getExternalAttributes());
490                 assertEquals(expectedElement.getInternalAttributes(), actualElement.getInternalAttributes());
491 
492                 final InputStream actualIs = actual.getInputStream(actualElement);
493                 final InputStream expectedIs = expected.getInputStream(expectedElement);
494                 IOUtils.readFully(expectedIs, expectedBuf);
495                 IOUtils.readFully(actualIs, actualBuf);
496                 expectedIs.close();
497                 actualIs.close();
498                 Assert.assertArrayEquals(expectedBuf, actualBuf); // Buffers are larger than payload. dont care
499             }
500 
501         }
502     }
503 
504 
505     private void createArchiveEntry(final String payload, final ZipArchiveOutputStream zos, final String name)
506             throws IOException {
507         final ZipArchiveEntry in = new ZipArchiveEntry(name);
508         zos.putArchiveEntry(in);
509 
510         zos.write(payload.getBytes());
511         zos.closeArchiveEntry();
512     }
513 
514     @Test
515     public void testFileEntryFromFile() throws Exception {
516         final File[] tmp = createTempDirAndFile();
517         File archive = null;
518         ZipArchiveOutputStream zos = null;
519         ZipFile zf = null;
520         FileInputStream fis = null;
521         try {
522             archive = File.createTempFile("test.", ".zip", tmp[0]);
523             archive.deleteOnExit();
524             zos = new ZipArchiveOutputStream(archive);
525             final ZipArchiveEntry in = new ZipArchiveEntry(tmp[1], "foo");
526             zos.putArchiveEntry(in);
527             final byte[] b = new byte[(int) tmp[1].length()];
528             fis = new FileInputStream(tmp[1]);
529             while (fis.read(b) > 0) {
530                 zos.write(b);
531             }
532             fis.close();
533             fis = null;
534             zos.closeArchiveEntry();
535             zos.close();
536             zos = null;
537             zf = new ZipFile(archive);
538             final ZipArchiveEntry out = zf.getEntry("foo");
539             assertNotNull(out);
540             assertEquals("foo", out.getName());
541             assertEquals(tmp[1].length(), out.getSize());
542             assertEquals(tmp[1].lastModified() / 2000,
543                          out.getLastModifiedDate().getTime() / 2000);
544             assertFalse(out.isDirectory());
545         } finally {
546             ZipFile.closeQuietly(zf);
547             if (zos != null) {
548                 zos.close();
549             }
550             tryHardToDelete(archive);
551             if (fis != null) {
552                 fis.close();
553             }
554             tryHardToDelete(tmp[1]);
555             rmdir(tmp[0]);
556         }
557     }
558 
559     @Test
560     public void testExplicitFileEntry() throws Exception {
561         final File[] tmp = createTempDirAndFile();
562         File archive = null;
563         ZipArchiveOutputStream zos = null;
564         ZipFile zf = null;
565         FileInputStream fis = null;
566         try {
567             archive = File.createTempFile("test.", ".zip", tmp[0]);
568             archive.deleteOnExit();
569             zos = new ZipArchiveOutputStream(archive);
570             final ZipArchiveEntry in = new ZipArchiveEntry("foo");
571             in.setTime(tmp[1].lastModified());
572             in.setSize(tmp[1].length());
573             zos.putArchiveEntry(in);
574             final byte[] b = new byte[(int) tmp[1].length()];
575             fis = new FileInputStream(tmp[1]);
576             while (fis.read(b) > 0) {
577                 zos.write(b);
578             }
579             fis.close();
580             fis = null;
581             zos.closeArchiveEntry();
582             zos.close();
583             zos = null;
584             zf = new ZipFile(archive);
585             final ZipArchiveEntry out = zf.getEntry("foo");
586             assertNotNull(out);
587             assertEquals("foo", out.getName());
588             assertEquals(tmp[1].length(), out.getSize());
589             assertEquals(tmp[1].lastModified() / 2000,
590                          out.getLastModifiedDate().getTime() / 2000);
591             assertFalse(out.isDirectory());
592         } finally {
593             ZipFile.closeQuietly(zf);
594             if (zos != null) {
595                 zos.close();
596             }
597             tryHardToDelete(archive);
598             if (fis != null) {
599                 fis.close();
600             }
601             tryHardToDelete(tmp[1]);
602             rmdir(tmp[0]);
603         }
604     }
605 
606     @Test
607     public void inputStreamStatisticsOfZipBombExcel() throws IOException, ArchiveException {
608         Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
609             put("[Content_Types].xml", Arrays.asList(8390036L, 8600L));
610             put("xl/worksheets/sheet1.xml", Arrays.asList(1348L, 508L));
611         }};
612         testInputStreamStatistics("zipbomb.xlsx", expected);
613     }
614 
615     @Test
616     public void inputStreamStatisticsForImplodedEntry() throws IOException, ArchiveException {
617         Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
618             put("LICENSE.TXT", Arrays.asList(11560L, 4131L));
619         }};
620         testInputStreamStatistics("imploding-8Kdict-3trees.zip", expected);
621     }
622 
623     @Test
624     public void inputStreamStatisticsForShrunkEntry() throws IOException, ArchiveException {
625         Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
626             put("TEST1.XML", Arrays.asList(76L, 66L));
627             put("TEST2.XML", Arrays.asList(81L, 76L));
628         }};
629         testInputStreamStatistics("SHRUNK.ZIP", expected);
630     }
631 
632     @Test
633     public void inputStreamStatisticsForStoredEntry() throws IOException, ArchiveException {
634         Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
635             put("test.txt", Arrays.asList(5L, 5L));
636         }};
637         testInputStreamStatistics("COMPRESS-264.zip", expected);
638     }
639 
640     @Test
641     public void inputStreamStatisticsForBzip2Entry() throws IOException, ArchiveException {
642         Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
643             put("lots-of-as", Arrays.asList(42L, 39L));
644         }};
645         testInputStreamStatistics("bzip2-zip.zip", expected);
646     }
647 
648     @Test
649     public void inputStreamStatisticsForDeflate64Entry() throws IOException, ArchiveException {
650         Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
651             put("input2", Arrays.asList(3072L, 2111L));
652         }};
653         testInputStreamStatistics("COMPRESS-380/COMPRESS-380.zip", expected);
654     }
655 
656     private void testInputStreamStatistics(String fileName, Map<String, List<Long>> expectedStatistics)
657         throws IOException, ArchiveException {
658         final File input = getFile(fileName);
659 
660         final Map<String,List<List<Long>>> actualStatistics = new HashMap<>();
661 
662         // stream access
663         try (final FileInputStream fis = new FileInputStream(input);
664             final ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream("zip", fis)) {
665             for (ArchiveEntry entry; (entry = in.getNextEntry()) != null; ) {
666                 readStream(in, entry, actualStatistics);
667             }
668         }
669 
670         // file access
671         try (final ZipFile zf = new ZipFile(input)) {
672             final Enumeration<ZipArchiveEntry> entries = zf.getEntries();
673             while (entries.hasMoreElements()) {
674                 final ZipArchiveEntry zae = entries.nextElement();
675                 try (InputStream in = zf.getInputStream(zae)) {
676                     readStream(in, zae, actualStatistics);
677                 }
678             }
679         }
680 
681         // compare statistics of stream / file access
682         for (Map.Entry<String,List<List<Long>>> me : actualStatistics.entrySet()) {
683             assertEquals("Mismatch of stats for: " + me.getKey(),
684                          me.getValue().get(0), me.getValue().get(1));
685         }
686 
687         for (Map.Entry<String, List<Long>> me : expectedStatistics.entrySet()) {
688             assertEquals("Mismatch of stats with expected value for: " + me.getKey(),
689                 me.getValue(), actualStatistics.get(me.getKey()).get(0));
690         }
691     }
692 
693     private void readStream(final InputStream in, final ArchiveEntry entry, final Map<String,List<List<Long>>> map) throws IOException {
694         final byte[] buf = new byte[4096];
695         final InputStreamStatistics stats = (InputStreamStatistics) in;
696         while (in.read(buf) != -1);
697 
698         final String name = entry.getName();
699         final List<List<Long>> l;
700         if (map.containsKey(name)) {
701             l = map.get(name);
702         } else {
703             map.put(name, l = new ArrayList<>());
704         }
705 
706         final long t = stats.getUncompressedCount();
707         final long b = stats.getCompressedCount();
708         l.add(Arrays.asList(t, b));
709     }
710 }