View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.compress.archivers.zip;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.BufferedInputStream;
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.EOFException;
32  import java.io.File;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.OutputStream;
36  import java.nio.channels.Channels;
37  import java.nio.channels.SeekableByteChannel;
38  import java.nio.charset.StandardCharsets;
39  import java.nio.file.Files;
40  import java.nio.file.Path;
41  import java.nio.file.Paths;
42  import java.time.Instant;
43  import java.util.zip.ZipEntry;
44  import java.util.zip.ZipException;
45  
46  import org.apache.commons.compress.AbstractTest;
47  import org.apache.commons.compress.archivers.ArchiveEntry;
48  import org.apache.commons.compress.archivers.ArchiveInputStream;
49  import org.apache.commons.compress.archivers.ArchiveStreamFactory;
50  import org.apache.commons.compress.utils.ByteUtils;
51  import org.apache.commons.io.IOUtils;
52  import org.apache.commons.lang3.ArrayFill;
53  import org.junit.jupiter.api.Test;
54  import org.junit.jupiter.params.ParameterizedTest;
55  import org.junit.jupiter.params.provider.ValueSource;
56  
57  public class ZipArchiveInputStreamTest extends AbstractTest {
58  
59      private static void nameSource(final String archive, final String entry, int entryNo, final ZipArchiveEntry.NameSource expected) throws Exception {
60          try (ZipArchiveInputStream zis = new ZipArchiveInputStream(Files.newInputStream(getFile(archive).toPath()))) {
61              ZipArchiveEntry ze;
62              do {
63                  ze = zis.getNextZipEntry();
64              } while (--entryNo > 0);
65              assertEquals(entry, ze.getName());
66              assertEquals(expected, ze.getNameSource());
67          }
68      }
69  
70      private static void nameSource(final String archive, final String entry, final ZipArchiveEntry.NameSource expected) throws Exception {
71          nameSource(archive, entry, 1, expected);
72      }
73  
74      private static byte[] readEntry(final ZipArchiveInputStream zip, final ZipArchiveEntry zae) throws IOException {
75          final int len = (int) zae.getSize();
76          final byte[] buff = new byte[len];
77          zip.read(buff, 0, len);
78  
79          return buff;
80      }
81  
82      private void extractZipInputStream(final ZipArchiveInputStream inputStream) throws IOException {
83          ZipArchiveEntry zae = inputStream.getNextZipEntry();
84          while (zae != null) {
85              if (zae.getName().endsWith(".zip")) {
86                  try (ZipArchiveInputStream innerInputStream = new ZipArchiveInputStream(inputStream)) {
87                      extractZipInputStream(innerInputStream);
88                  }
89              }
90              zae = inputStream.getNextZipEntry();
91          }
92      }
93  
94      /**
95       * Forge a ZIP archive in memory, using STORED and Data Descriptor, and without signature of Data Descriptor.
96       *
97       * @return the input stream of the generated zip
98       * @throws IOException there are problems
99       */
100     private InputStream forgeZipInputStream() throws IOException {
101         try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
102                 ZipArchiveOutputStream zo = new ZipArchiveOutputStream(byteArrayOutputStream)) {
103 
104             final ZipArchiveEntry entryA = new ZipArchiveEntry("foo");
105             entryA.setMethod(ZipEntry.STORED);
106             entryA.setSize(4);
107             entryA.setCrc(0xb63cfbcdL);
108             // Ensure we won't write extra fields. They are not compatible with the manual edits below.
109             entryA.setTime(Instant.parse("2022-12-26T17:01:00Z").toEpochMilli());
110             zo.putArchiveEntry(entryA);
111             zo.write(new byte[] { 1, 2, 3, 4 });
112             zo.closeArchiveEntry();
113             zo.close();
114 
115             final byte[] zipContent = byteArrayOutputStream.toByteArray();
116             final byte[] zipContentWithDataDescriptor = new byte[zipContent.length + 12];
117             System.arraycopy(zipContent, 0, zipContentWithDataDescriptor, 0, 37);
118             // modify the general purpose bit flag
119             zipContentWithDataDescriptor[6] = 8;
120 
121             // copy the crc-32, compressed size and uncompressed size to the data descriptor
122             System.arraycopy(zipContent, 14, zipContentWithDataDescriptor, 37, 12);
123 
124             // and copy the rest of the ZIP content
125             System.arraycopy(zipContent, 37, zipContentWithDataDescriptor, 49, zipContent.length - 37);
126 
127             return new ByteArrayInputStream(zipContentWithDataDescriptor);
128         }
129     }
130 
131     private void fuzzingTest(final int[] bytes) throws Exception {
132         final int len = bytes.length;
133         final byte[] input = new byte[len];
134         for (int i = 0; i < len; i++) {
135             input[i] = (byte) bytes[i];
136         }
137         try (ArchiveInputStream<?> ais = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", new ByteArrayInputStream(input))) {
138             ais.getNextEntry();
139             IOUtils.toByteArray(ais);
140         }
141     }
142 
143     private void getAllZipEntries(final ZipArchiveInputStream zipInputStream) throws IOException {
144         while (zipInputStream.getNextZipEntry() != null) {
145             // noop
146         }
147     }
148 
149     private void multiByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
150         final byte[] buf = new byte[2];
151         try (InputStream in = newInputStream("bla.zip");
152                 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
153             assertNotNull(archive.getNextEntry());
154             IOUtils.toByteArray(archive);
155             assertEquals(-1, archive.read(buf));
156             assertEquals(-1, archive.read(buf));
157         }
158     }
159 
160     private void singleByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
161         try (InputStream in = Files.newInputStream(file.toPath());
162                 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
163             assertNotNull(archive.getNextEntry());
164             IOUtils.toByteArray(archive);
165             assertEquals(-1, archive.read());
166             assertEquals(-1, archive.read());
167         }
168     }
169 
170     @Test
171     public void testGetFirstEntryEmptyZip() throws IOException {
172         try (ZipArchiveInputStream zin = new ZipArchiveInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY))) {
173             final ZipArchiveEntry entry = zin.getNextEntry();
174             assertNull(entry);
175         }
176     }
177 
178     /**
179      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-351" >COMPRESS-351</a>.
180      */
181     @Test
182     public void testMessageWithCorruptFileName() throws Exception {
183         try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("COMPRESS-351.zip"))) {
184             final EOFException ex = assertThrows(EOFException.class, () -> {
185                 ZipArchiveEntry ze = in.getNextZipEntry();
186                 while (ze != null) {
187                     ze = in.getNextZipEntry();
188                 }
189             }, "expected EOFException");
190             final String m = ex.getMessage();
191             assertTrue(m.startsWith("Truncated ZIP entry: ?2016")); // the first character is not printable
192         }
193     }
194 
195     @Test
196     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingBzip2() throws Exception {
197         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("bzip2-zip.zip"));
198     }
199 
200     @Test
201     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate() throws Exception {
202         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("bla.zip"));
203     }
204 
205     @Test
206     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate64() throws Exception {
207         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-380/COMPRESS-380.zip"));
208     }
209 
210     @Test
211     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingExplode() throws Exception {
212         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("imploding-8Kdict-3trees.zip"));
213     }
214 
215     @Test
216     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingStore() throws Exception {
217         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-264.zip"));
218     }
219 
220     @Test
221     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Exception {
222         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("SHRUNK.ZIP"));
223     }
224 
225     @Test
226     public void testMultiByteReadThrowsAtEofForCorruptedStoredEntry() throws Exception {
227         final byte[] content = readAllBytes("COMPRESS-264.zip");
228         // make size much bigger than entry's real size
229         for (int i = 17; i < 26; i++) {
230             content[i] = (byte) 0xff;
231         }
232         final byte[] buf = new byte[2];
233         try (ByteArrayInputStream in = new ByteArrayInputStream(content);
234                 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
235             assertNotNull(archive.getNextEntry());
236             final IOException ex1 = assertThrows(IOException.class, () -> IOUtils.toByteArray(archive), "expected exception");
237             assertEquals("Truncated ZIP file", ex1.getMessage());
238             final IOException ex2 = assertThrows(IOException.class, () -> archive.read(buf), "expected exception");
239             assertEquals("Truncated ZIP file", ex2.getMessage());
240             final IOException ex3 = assertThrows(IOException.class, () -> archive.read(buf), "expected exception");
241             assertEquals("Truncated ZIP file", ex3.getMessage());
242         }
243     }
244 
245     @Test
246     public void testNameSourceDefaultsToName() throws Exception {
247         nameSource("bla.zip", "test1.xml", ZipArchiveEntry.NameSource.NAME);
248     }
249 
250     @Test
251     public void testNameSourceIsSetToEFS() throws Exception {
252         nameSource("utf8-7zip-test.zip", "\u20AC_for_Dollar.txt", 3, ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
253     }
254 
255     @Test
256     public void testNameSourceIsSetToUnicodeExtraField() throws Exception {
257         nameSource("utf8-winzip-test.zip", "\u20AC_for_Dollar.txt", ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
258     }
259 
260     /**
261      * Test correct population of header and data offsets.
262      */
263     @Test
264     public void testOffsets() throws Exception {
265         // mixed.zip contains both inflated and stored files
266         try (InputStream archiveStream = ZipArchiveInputStream.class.getResourceAsStream("/mixed.zip");
267                 ZipArchiveInputStream zipStream = new ZipArchiveInputStream(archiveStream)) {
268             final ZipArchiveEntry inflatedEntry = zipStream.getNextZipEntry();
269             assertEquals("inflated.txt", inflatedEntry.getName());
270             assertEquals(0x0000, inflatedEntry.getLocalHeaderOffset());
271             assertEquals(0x0046, inflatedEntry.getDataOffset());
272             final ZipArchiveEntry storedEntry = zipStream.getNextZipEntry();
273             assertEquals("stored.txt", storedEntry.getName());
274             assertEquals(0x5892, storedEntry.getLocalHeaderOffset());
275             assertEquals(0x58d6, storedEntry.getDataOffset());
276             assertNull(zipStream.getNextZipEntry());
277         }
278     }
279 
280     @Test
281     public void testProperlyMarksEntriesAsUnreadableIfUncompressedSizeIsUnknown() throws Exception {
282         // we never read any data
283         try (ZipArchiveInputStream zis = new ZipArchiveInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY))) {
284             final ZipArchiveEntry e = new ZipArchiveEntry("test");
285             e.setMethod(ZipMethod.DEFLATED.getCode());
286             assertTrue(zis.canReadEntryData(e));
287             e.setMethod(ZipMethod.ENHANCED_DEFLATED.getCode());
288             assertTrue(zis.canReadEntryData(e));
289             e.setMethod(ZipMethod.BZIP2.getCode());
290             assertFalse(zis.canReadEntryData(e));
291         }
292     }
293 
294     @Test
295     public void testProperlyReadsStoredEntries() throws IOException {
296         try (InputStream fs = newInputStream("bla-stored.zip");
297                 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs)) {
298             ZipArchiveEntry e = archive.getNextZipEntry();
299             assertNotNull(e);
300             assertEquals("test1.xml", e.getName());
301             assertEquals(610, e.getCompressedSize());
302             assertEquals(610, e.getSize());
303             byte[] data = IOUtils.toByteArray(archive);
304             assertEquals(610, data.length);
305             e = archive.getNextZipEntry();
306             assertNotNull(e);
307             assertEquals("test2.xml", e.getName());
308             assertEquals(82, e.getCompressedSize());
309             assertEquals(82, e.getSize());
310             data = IOUtils.toByteArray(archive);
311             assertEquals(82, data.length);
312             assertNull(archive.getNextEntry());
313         }
314     }
315 
316     @Test
317     public void testProperlyReadsStoredEntryWithDataDescriptorWithoutSignature() throws IOException {
318         try (InputStream fs = newInputStream("bla-stored-dd-nosig.zip");
319                 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
320             final ZipArchiveEntry e = archive.getNextZipEntry();
321             assertNotNull(e);
322             assertEquals("test1.xml", e.getName());
323             assertEquals(-1, e.getCompressedSize());
324             assertEquals(-1, e.getSize());
325             final byte[] data = IOUtils.toByteArray(archive);
326             assertEquals(610, data.length);
327             assertEquals(610, e.getCompressedSize());
328             assertEquals(610, e.getSize());
329         }
330     }
331 
332     @Test
333     public void testProperlyReadsStoredEntryWithDataDescriptorWithSignature() throws IOException {
334         try (InputStream fs = newInputStream("bla-stored-dd.zip");
335                 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
336             final ZipArchiveEntry e = archive.getNextZipEntry();
337             assertNotNull(e);
338             assertEquals("test1.xml", e.getName());
339             assertEquals(-1, e.getCompressedSize());
340             assertEquals(-1, e.getSize());
341             final byte[] data = IOUtils.toByteArray(archive);
342             assertEquals(610, data.length);
343             assertEquals(610, e.getCompressedSize());
344             assertEquals(610, e.getSize());
345         }
346     }
347 
348     /**
349      * @see "https://issues.apache.org/jira/browse/COMPRESS-189"
350      */
351     @Test
352     public void testProperUseOfInflater() throws Exception {
353         try (ZipFile zf = ZipFile.builder().setFile(getFile("COMPRESS-189.zip")).get()) {
354             final ZipArchiveEntry zae = zf.getEntry("USD0558682-20080101.ZIP");
355             try (ZipArchiveInputStream in = new ZipArchiveInputStream(new BufferedInputStream(zf.getInputStream(zae)))) {
356                 ZipArchiveEntry innerEntry;
357                 while ((innerEntry = in.getNextZipEntry()) != null) {
358                     if (innerEntry.getName().endsWith("XML")) {
359                         assertTrue(0 < in.read());
360                     }
361                 }
362             }
363         }
364     }
365 
366     /**
367      * @see "https://issues.apache.org/jira/browse/COMPRESS-380"
368      */
369     @Test
370     public void testReadDeflate64CompressedStream() throws Exception {
371         final byte[] orig = readAllBytes("COMPRESS-380/COMPRESS-380-input");
372         final File archive = getFile("COMPRESS-380/COMPRESS-380.zip");
373         try (ZipArchiveInputStream zin = new ZipArchiveInputStream(Files.newInputStream(archive.toPath()))) {
374             assertNotNull(zin.getNextZipEntry());
375             final byte[] fromZip = IOUtils.toByteArray(zin);
376             assertArrayEquals(orig, fromZip);
377         }
378     }
379 
380     @Test
381     public void testReadDeflate64CompressedStreamWithDataDescriptor() throws Exception {
382         // this is a copy of bla.jar with META-INF/MANIFEST.MF's method manually changed to ENHANCED_DEFLATED
383         final File archive = getFile("COMPRESS-380/COMPRESS-380-dd.zip");
384         try (ZipArchiveInputStream zin = new ZipArchiveInputStream(Files.newInputStream(archive.toPath()))) {
385             final ZipArchiveEntry e = zin.getNextZipEntry();
386             assertEquals(-1, e.getSize());
387             assertEquals(ZipMethod.ENHANCED_DEFLATED.getCode(), e.getMethod());
388             final byte[] fromZip = IOUtils.toByteArray(zin);
389             final byte[] expected = { 'M', 'a', 'n', 'i', 'f', 'e', 's', 't', '-', 'V', 'e', 'r', 's', 'i', 'o', 'n', ':', ' ', '1', '.', '0', '\r', '\n', '\r',
390                     '\n' };
391             assertArrayEquals(expected, fromZip);
392             zin.getNextZipEntry();
393             assertEquals(25, e.getSize());
394         }
395     }
396 
397     /**
398      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-264" >COMPRESS-264</a>.
399      */
400     @Test
401     public void testReadingOfFirstStoredEntry() throws Exception {
402 
403         try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("COMPRESS-264.zip"))) {
404             final ZipArchiveEntry ze = in.getNextZipEntry();
405             assertEquals(5, ze.getSize());
406             assertArrayEquals(new byte[] { 'd', 'a', 't', 'a', '\n' }, IOUtils.toByteArray(in));
407         }
408     }
409 
410     @Test
411     public void testRejectsStoredEntriesWithDataDescriptorByDefault() throws IOException {
412         try (InputStream fs = newInputStream("bla-stored-dd.zip");
413                 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs)) {
414             final ZipArchiveEntry e = archive.getNextZipEntry();
415             assertNotNull(e);
416             assertEquals("test1.xml", e.getName());
417             assertEquals(-1, e.getCompressedSize());
418             assertEquals(-1, e.getSize());
419             assertThrows(UnsupportedZipFeatureException.class, () -> IOUtils.toByteArray(archive));
420         }
421     }
422 
423     @Test
424     public void testShouldConsumeArchiveCompletely() throws Exception {
425         try (InputStream is = ZipArchiveInputStreamTest.class.getResourceAsStream("/archive_with_trailer.zip");
426                 ZipArchiveInputStream zip = new ZipArchiveInputStream(is)) {
427             getAllZipEntries(zip);
428             final byte[] expected = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n' };
429             final byte[] actual = new byte[expected.length];
430             is.read(actual);
431             assertArrayEquals(expected, actual);
432         }
433     }
434 
435     /**
436      * @see "https://issues.apache.org/jira/browse/COMPRESS-219"
437      */
438     @Test
439     public void testShouldReadNestedZip() throws IOException {
440         try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("COMPRESS-219.zip"))) {
441             extractZipInputStream(in);
442         }
443     }
444 
445     @Test
446     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingBzip2() throws Exception {
447         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("bzip2-zip.zip"));
448     }
449 
450     @Test
451     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate() throws Exception {
452         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("bla.zip"));
453     }
454 
455     @Test
456     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate64() throws Exception {
457         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-380/COMPRESS-380.zip"));
458     }
459 
460     @Test
461     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingExplode() throws Exception {
462         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("imploding-8Kdict-3trees.zip"));
463     }
464 
465     @Test
466     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingStore() throws Exception {
467         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-264.zip"));
468     }
469 
470     @Test
471     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Exception {
472         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("SHRUNK.ZIP"));
473     }
474 
475     @Test
476     public void testSingleByteReadThrowsAtEofForCorruptedStoredEntry() throws Exception {
477         final byte[] content = readAllBytes("COMPRESS-264.zip");
478         // make size much bigger than entry's real size
479         for (int i = 17; i < 26; i++) {
480             content[i] = (byte) 0xff;
481         }
482         try (ByteArrayInputStream in = new ByteArrayInputStream(content);
483                 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
484             assertNotNull(archive.getNextEntry());
485             final IOException ex1 = assertThrows(IOException.class, () -> IOUtils.toByteArray(archive), "expected exception");
486             assertEquals("Truncated ZIP file", ex1.getMessage());
487             final IOException ex2 = assertThrows(IOException.class, archive::read, "expected exception");
488             assertEquals("Truncated ZIP file", ex2.getMessage());
489             final IOException ex3 = assertThrows(IOException.class, archive::read, "expected exception");
490             assertEquals("Truncated ZIP file", ex3.getMessage());
491         }
492     }
493 
494     @Test
495     public void testSplitZipCreatedByWinrar() throws IOException {
496         final File lastFile = getFile("COMPRESS-477/split_zip_created_by_winrar/split_zip_created_by_winrar.zip");
497         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
498                 InputStream inputStream = Channels.newInputStream(channel);
499                 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, false, true)) {
500 
501             final File fileToCompare = getFile("COMPRESS-477/split_zip_created_by_winrar/zip_to_compare_created_by_winrar.zip");
502             try (ZipArchiveInputStream inputStreamToCompare = new ZipArchiveInputStream(Files.newInputStream(fileToCompare.toPath()),
503                     StandardCharsets.UTF_8.name(), true, false, true)) {
504 
505                 ArchiveEntry entry;
506                 while ((entry = splitInputStream.getNextEntry()) != null && inputStreamToCompare.getNextEntry() != null) {
507                     if (entry.isDirectory()) {
508                         continue;
509                     }
510                     assertArrayEquals(IOUtils.toByteArray(splitInputStream), IOUtils.toByteArray(inputStreamToCompare));
511                 }
512             }
513         }
514     }
515 
516     @Test
517     public void testSplitZipCreatedByZip() throws IOException {
518         final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip.zip");
519         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
520                 InputStream inputStream = Channels.newInputStream(channel);
521                 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, false, true)) {
522 
523             final Path fileToCompare = getPath("COMPRESS-477/split_zip_created_by_zip/zip_to_compare_created_by_zip.zip");
524             try (ZipArchiveInputStream inputStreamToCompare = new ZipArchiveInputStream(Files.newInputStream(fileToCompare), StandardCharsets.UTF_8.name(),
525                     true, false, true)) {
526 
527                 ArchiveEntry entry;
528                 while ((entry = splitInputStream.getNextEntry()) != null && inputStreamToCompare.getNextEntry() != null) {
529                     if (entry.isDirectory()) {
530                         continue;
531                     }
532                     assertArrayEquals(IOUtils.toByteArray(splitInputStream), IOUtils.toByteArray(inputStreamToCompare));
533                 }
534             }
535         }
536     }
537 
538     @Test
539     public void testSplitZipCreatedByZipOfZip64() throws IOException {
540         final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip_zip64.zip");
541         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
542                 InputStream inputStream = Channels.newInputStream(channel);
543                 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, false, true)) {
544 
545             final Path fileToCompare = getPath("COMPRESS-477/split_zip_created_by_zip/zip_to_compare_created_by_zip_zip64.zip");
546             try (ZipArchiveInputStream inputStreamToCompare = new ZipArchiveInputStream(Files.newInputStream(fileToCompare), StandardCharsets.UTF_8.name(),
547                     true, false, true)) {
548 
549                 ArchiveEntry entry;
550                 while ((entry = splitInputStream.getNextEntry()) != null && inputStreamToCompare.getNextEntry() != null) {
551                     if (entry.isDirectory()) {
552                         continue;
553                     }
554                     assertArrayEquals(IOUtils.toByteArray(splitInputStream), IOUtils.toByteArray(inputStreamToCompare));
555                 }
556             }
557         }
558     }
559 
560     @Test
561     public void testSplitZipCreatedByZipThrowsException() throws IOException {
562         final File zipSplitFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip.z01");
563         try (ZipArchiveInputStream inputStream = new ZipArchiveInputStream(Files.newInputStream(zipSplitFile.toPath()), StandardCharsets.UTF_8.name(), true,
564                 false, true)) {
565 
566             assertThrows(EOFException.class, () -> {
567                 ArchiveEntry entry = inputStream.getNextEntry();
568                 while (entry != null) {
569                     entry = inputStream.getNextEntry();
570                 }
571             });
572         }
573     }
574 
575     /**
576      * {@code getNextZipEntry()} should throw a {@code ZipException} rather than return {@code null} when an unexpected structure is encountered.
577      */
578     @Test
579     public void testThrowOnInvalidEntry() throws Exception {
580         try (ZipArchiveInputStream zip = new ZipArchiveInputStream(ZipArchiveInputStreamTest.class.getResourceAsStream("/invalid-zip.zip"))) {
581             final ZipException expected = assertThrows(ZipException.class, zip::getNextZipEntry, "IOException expected");
582             assertTrue(expected.getMessage().contains("Cannot find zip signature"));
583         }
584     }
585 
586     @Test
587     public void testThrowsIfStoredDDIsDifferentFromLengthRead() throws IOException {
588         try (InputStream fs = newInputStream("bla-stored-dd-contradicts-actualsize.zip");
589                 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
590             final ZipArchiveEntry e = archive.getNextZipEntry();
591             assertNotNull(e);
592             assertEquals("test1.xml", e.getName());
593             assertEquals(-1, e.getCompressedSize());
594             assertEquals(-1, e.getSize());
595             assertThrows(ZipException.class, () -> IOUtils.toByteArray(archive));
596         }
597     }
598 
599     @Test
600     public void testThrowsIfStoredDDIsInconsistent() throws IOException {
601         try (InputStream fs = newInputStream("bla-stored-dd-sizes-differ.zip");
602                 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
603             final ZipArchiveEntry e = archive.getNextZipEntry();
604             assertNotNull(e);
605             assertEquals("test1.xml", e.getName());
606             assertEquals(-1, e.getCompressedSize());
607             assertEquals(-1, e.getSize());
608             assertThrows(ZipException.class, () -> IOUtils.toByteArray(archive));
609         }
610     }
611 
612     /**
613      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-523">COMPRESS-523</a>
614      */
615     @Test
616     public void testThrowsIfThereIsNoEocd() {
617         assertThrows(IOException.class, () -> fuzzingTest(new int[] { 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
618                 0x00, 0x43, 0xbe, 0x00, 0x00, 0x00, 0xb7, 0xe8, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 }));
619     }
620 
621     /**
622      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-518">COMPRESS-518</a>
623      */
624     @Test
625     public void testThrowsIfZip64ExtraCouldNotBeUnderstood() {
626         assertThrows(IOException.class,
627                 () -> fuzzingTest(new int[] { 0x50, 0x4b, 0x03, 0x04, 0x2e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x84, 0xb6, 0xba, 0x46, 0x72, 0xb6, 0xfe, 0x77, 0x63,
628                         0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c, 0x00, 0x62, 0x62, 0x62, 0x01, 0x00, 0x09, 0x00, 0x03, 0xe7, 0xce, 0x64,
629                         0x55, 0xf3, 0xce, 0x64, 0x55, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0x5c, 0xf9, 0x01, 0x00, 0x04, 0x88, 0x13, 0x00, 0x00 }));
630     }
631 
632     @Test
633     public void testThrowsIOExceptionIfThereIsCorruptedZip64Extra() throws IOException {
634         try (InputStream fis = newInputStream("COMPRESS-546.zip");
635                 ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(fis)) {
636             assertThrows(IOException.class, () -> getAllZipEntries(zipInputStream));
637         }
638     }
639 
640     @Test
641     public void testUnshrinkEntry() throws Exception {
642         try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("SHRUNK.ZIP"))) {
643             ZipArchiveEntry entry = in.getNextZipEntry();
644             assertEquals(ZipMethod.UNSHRINKING.getCode(), entry.getMethod(), "method");
645             assertTrue(in.canReadEntryData(entry));
646 
647             try (InputStream original = newInputStream("test1.xml")) {
648                 try {
649                     assertArrayEquals(IOUtils.toByteArray(original), IOUtils.toByteArray(in));
650                 } finally {
651                     original.close();
652                 }
653 
654                 entry = in.getNextZipEntry();
655                 assertEquals(ZipMethod.UNSHRINKING.getCode(), entry.getMethod(), "method");
656                 assertTrue(in.canReadEntryData(entry));
657             }
658 
659             assertArrayEquals(readAllBytes("test2.xml"), IOUtils.toByteArray(in));
660         }
661     }
662 
663     @Test
664     public void testUnzipBZip2CompressedEntry() throws Exception {
665 
666         try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("bzip2-zip.zip"))) {
667             final ZipArchiveEntry ze = in.getNextZipEntry();
668             assertEquals(42, ze.getSize());
669             final byte[] expected = ArrayFill.fill(new byte[42], (byte) 'a');
670             assertArrayEquals(expected, IOUtils.toByteArray(in));
671         }
672     }
673 
674     /**
675      * @see "https://issues.apache.org/jira/browse/COMPRESS-176"
676      */
677     @Test
678     public void testWinzipBackSlashWorkaround() throws Exception {
679         try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("test-winzip.zip"))) {
680             ZipArchiveEntry zae = in.getNextZipEntry();
681             zae = in.getNextZipEntry();
682             zae = in.getNextZipEntry();
683             assertEquals("\u00e4/", zae.getName());
684         }
685     }
686 
687     /**
688      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-364" >COMPRESS-364</a>.
689      */
690     @Test
691     public void testWithBytesAfterData() throws Exception {
692         final int expectedNumEntries = 2;
693         try (InputStream is = ZipArchiveInputStreamTest.class.getResourceAsStream("/archive_with_bytes_after_data.zip");
694                 ZipArchiveInputStream zip = new ZipArchiveInputStream(is)) {
695             int actualNumEntries = 0;
696             ZipArchiveEntry zae = zip.getNextZipEntry();
697             while (zae != null) {
698                 actualNumEntries++;
699                 readEntry(zip, zae);
700                 zae = zip.getNextZipEntry();
701             }
702             assertEquals(expectedNumEntries, actualNumEntries);
703         }
704     }
705 
706     @Test
707     public void testZipUsingStoredWithDDAndNoDDSignature() throws IOException {
708         try (InputStream inputStream = forgeZipInputStream();
709                 ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, true)) {
710             getAllZipEntries(zipInputStream);
711         }
712     }
713 
714     @Test
715     public void testZipWithBadExtraFields() throws IOException {
716         try (InputStream fis = newInputStream("COMPRESS-548.zip");
717                 ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(fis)) {
718             getAllZipEntries(zipInputStream);
719         }
720     }
721 
722     @Test
723     public void testZipWithLongerBeginningGarbage() throws IOException {
724         final Path path = createTempPath("preamble", ".zip");
725 
726         try (OutputStream fos = Files.newOutputStream(path)) {
727             fos.write("#!/usr/bin/env some-program with quite a few arguments to make it longer than the local header\n".getBytes(StandardCharsets.UTF_8));
728             try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) {
729                 final ZipArchiveEntry entry = new ZipArchiveEntry("file-1.txt");
730                 entry.setMethod(ZipEntry.DEFLATED);
731                 zos.putArchiveEntry(entry);
732                 zos.write("entry-content\n".getBytes(StandardCharsets.UTF_8));
733                 zos.closeArchiveEntry();
734             }
735         }
736 
737         try (InputStream is = Files.newInputStream(path);
738                 ZipArchiveInputStream zis = new ZipArchiveInputStream(is)) {
739             final ZipArchiveEntry entry = zis.getNextEntry();
740             assertEquals("file-1.txt", entry.getName());
741             final byte[] content = IOUtils.toByteArray(zis);
742             assertArrayEquals("entry-content\n".getBytes(StandardCharsets.UTF_8), content);
743         }
744     }
745 
746     @Test
747     public void testZipWithShortBeginningGarbage() throws IOException {
748         final Path path = createTempPath("preamble", ".zip");
749 
750         try (OutputStream fos = Files.newOutputStream(path)) {
751             fos.write("#!/usr/bin/unzip\n".getBytes(StandardCharsets.UTF_8));
752             try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) {
753                 final ZipArchiveEntry entry = new ZipArchiveEntry("file-1.txt");
754                 entry.setMethod(ZipEntry.DEFLATED);
755                 zos.putArchiveEntry(entry);
756                 zos.write("entry-content\n".getBytes(StandardCharsets.UTF_8));
757                 zos.closeArchiveEntry();
758             }
759         }
760 
761         try (InputStream is = Files.newInputStream(path);
762                 ZipArchiveInputStream zis = new ZipArchiveInputStream(is)) {
763             final ZipArchiveEntry entry = zis.getNextEntry();
764             assertEquals("file-1.txt", entry.getName());
765             final byte[] content = IOUtils.toByteArray(zis);
766             assertArrayEquals("entry-content\n".getBytes(StandardCharsets.UTF_8), content);
767         }
768     }
769 
770     @ParameterizedTest
771     @ValueSource(booleans = { true, false })
772     public void zipInputStream(final boolean allowStoredEntriesWithDataDescriptor) {
773         try (ZipArchiveInputStream zIn = new ZipArchiveInputStream(Files.newInputStream(Paths.get("src/test/resources/COMPRESS-647/test.zip")),
774                 StandardCharsets.UTF_8.name(), false, allowStoredEntriesWithDataDescriptor)) {
775             ZipArchiveEntry zae = zIn.getNextEntry();
776             while (zae != null) {
777                 zae = zIn.getNextEntry();
778             }
779         } catch (final IOException e) {
780             // Ignore expected exception
781         }
782     }
783 
784 }