1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.commons.compress.archivers.zip;
21
22 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
23 import static org.junit.jupiter.api.Assertions.assertEquals;
24 import static org.junit.jupiter.api.Assertions.assertFalse;
25 import static org.junit.jupiter.api.Assertions.assertNotNull;
26 import static org.junit.jupiter.api.Assertions.assertNull;
27 import static org.junit.jupiter.api.Assertions.assertThrows;
28 import static org.junit.jupiter.api.Assertions.assertTrue;
29
30 import java.io.BufferedInputStream;
31 import java.io.ByteArrayInputStream;
32 import java.io.ByteArrayOutputStream;
33 import java.io.EOFException;
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.FileOutputStream;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.channels.Channels;
41 import java.nio.channels.SeekableByteChannel;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.Files;
44 import java.nio.file.Path;
45 import java.nio.file.Paths;
46 import java.time.Instant;
47 import java.util.zip.ZipEntry;
48 import java.util.zip.ZipException;
49
50 import org.apache.commons.compress.AbstractTest;
51 import org.apache.commons.compress.archivers.ArchiveEntry;
52 import org.apache.commons.compress.archivers.ArchiveInputStream;
53 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
54 import org.apache.commons.compress.utils.ByteUtils;
55 import org.apache.commons.io.IOUtils;
56 import org.apache.commons.lang3.ArrayFill;
57 import org.junit.jupiter.api.Test;
58 import org.junit.jupiter.params.ParameterizedTest;
59 import org.junit.jupiter.params.provider.ValueSource;
60
61 import io.airlift.compress.zstd.ZstdInputStream;
62
63 class ZipArchiveInputStreamTest extends AbstractTest {
64
65 private static final class AirliftZipArchiveInputStream extends ZipArchiveInputStream {
66
67 private boolean used;
68
69 private AirliftZipArchiveInputStream(final InputStream inputStream) {
70 super(inputStream);
71 }
72
73 @Override
74 protected InputStream createZstdInputStream(final InputStream bis) throws IOException {
75 return new ZstdInputStream(bis) {
76 @Override
77 public int read(final byte[] outputBuffer, final int outputOffset, final int outputLength) throws IOException {
78 used = true;
79 return super.read(outputBuffer, outputOffset, outputLength);
80 }
81 };
82 }
83
84 public boolean isUsed() {
85 return used;
86 }
87 }
88
89 private static void nameSource(final String archive, final String entry, int entryNo, final ZipArchiveEntry.NameSource expected) throws Exception {
90 try (ZipArchiveInputStream zis = new ZipArchiveInputStream(Files.newInputStream(getFile(archive).toPath()))) {
91 ZipArchiveEntry ze;
92 do {
93 ze = zis.getNextZipEntry();
94 } while (--entryNo > 0);
95 assertEquals(entry, ze.getName());
96 assertEquals(expected, ze.getNameSource());
97 }
98 }
99
100 private static void nameSource(final String archive, final String entry, final ZipArchiveEntry.NameSource expected) throws Exception {
101 nameSource(archive, entry, 1, expected);
102 }
103
104 private static byte[] readEntry(final ZipArchiveInputStream zip, final ZipArchiveEntry zae) throws IOException {
105 final int len = (int) zae.getSize();
106 final byte[] buff = new byte[len];
107 zip.read(buff, 0, len);
108
109 return buff;
110 }
111
112 private void extractZipInputStream(final ZipArchiveInputStream inputStream) throws IOException {
113 ZipArchiveEntry zae = inputStream.getNextZipEntry();
114 while (zae != null) {
115 if (zae.getName().endsWith(".zip")) {
116 try (ZipArchiveInputStream innerInputStream = new ZipArchiveInputStream(inputStream)) {
117 extractZipInputStream(innerInputStream);
118 }
119 }
120 zae = inputStream.getNextZipEntry();
121 }
122 }
123
124
125
126
127
128
129
130 private InputStream forgeZipInputStream() throws IOException {
131 try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
132 ZipArchiveOutputStream zo = new ZipArchiveOutputStream(byteArrayOutputStream)) {
133
134 final ZipArchiveEntry entryA = new ZipArchiveEntry("foo");
135 entryA.setMethod(ZipEntry.STORED);
136 entryA.setSize(4);
137 entryA.setCrc(0xb63cfbcdL);
138
139 entryA.setTime(Instant.parse("2022-12-26T17:01:00Z").toEpochMilli());
140 zo.putArchiveEntry(entryA);
141 zo.write(new byte[] { 1, 2, 3, 4 });
142 zo.closeArchiveEntry();
143 zo.close();
144
145 final byte[] zipContent = byteArrayOutputStream.toByteArray();
146 final byte[] zipContentWithDataDescriptor = new byte[zipContent.length + 12];
147 System.arraycopy(zipContent, 0, zipContentWithDataDescriptor, 0, 37);
148
149 zipContentWithDataDescriptor[6] = 8;
150
151
152 System.arraycopy(zipContent, 14, zipContentWithDataDescriptor, 37, 12);
153
154
155 System.arraycopy(zipContent, 37, zipContentWithDataDescriptor, 49, zipContent.length - 37);
156
157 return new ByteArrayInputStream(zipContentWithDataDescriptor);
158 }
159 }
160
161 private void fuzzingTest(final int[] bytes) throws Exception {
162 final int len = bytes.length;
163 final byte[] input = new byte[len];
164 for (int i = 0; i < len; i++) {
165 input[i] = (byte) bytes[i];
166 }
167 try (ArchiveInputStream<?> ais = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", new ByteArrayInputStream(input))) {
168 ais.getNextEntry();
169 IOUtils.toByteArray(ais);
170 }
171 }
172
173 private void getAllZipEntries(final ZipArchiveInputStream zipInputStream) throws IOException {
174 while (zipInputStream.getNextZipEntry() != null) {
175
176 }
177 }
178
179 private void multiByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
180 final byte[] buf = new byte[2];
181 try (InputStream in = newInputStream("bla.zip");
182 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
183 assertEquals(-1, archive.getCompressedCount());
184 assertNotNull(archive.getNextEntry());
185 IOUtils.toByteArray(archive);
186 assertEquals(-1, archive.read(buf));
187 assertEquals(-1, archive.read(buf));
188 }
189 }
190
191 private void singleByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
192 try (InputStream in = Files.newInputStream(file.toPath());
193 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
194 assertNotNull(archive.getNextEntry());
195 IOUtils.toByteArray(archive);
196 assertEquals(-1, archive.read());
197 assertEquals(-1, archive.read());
198 }
199 }
200
201 @Test
202 void testGetCompressedCountEmptyZip() throws IOException {
203 try (ZipArchiveInputStream zin = new ZipArchiveInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY))) {
204 assertEquals(-1, zin.getCompressedCount());
205 }
206 }
207
208 @Test
209 void testGetFirstEntryEmptyZip() throws IOException {
210 try (ZipArchiveInputStream zin = new ZipArchiveInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY))) {
211 final ZipArchiveEntry entry = zin.getNextEntry();
212 assertNull(entry);
213 }
214 }
215
216 @Test
217 void testGetUncompressedCountEmptyZip() throws IOException {
218 try (ZipArchiveInputStream zin = new ZipArchiveInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY))) {
219 assertEquals(0, zin.getUncompressedCount());
220 }
221 }
222
223
224
225
226 @Test
227 void testMessageWithCorruptFileName() throws Exception {
228 try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("COMPRESS-351.zip"))) {
229 final EOFException ex = assertThrows(EOFException.class, () -> {
230 ZipArchiveEntry ze = in.getNextZipEntry();
231 while (ze != null) {
232 ze = in.getNextZipEntry();
233 }
234 }, "expected EOFException");
235 final String m = ex.getMessage();
236 assertTrue(m.startsWith("Truncated ZIP entry: ?2016"));
237 }
238 }
239
240 @Test
241 void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingBzip2() throws Exception {
242 multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("bzip2-zip.zip"));
243 }
244
245 @Test
246 void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate() throws Exception {
247 multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("bla.zip"));
248 }
249
250 @Test
251 void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate64() throws Exception {
252 multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-380/COMPRESS-380.zip"));
253 }
254
255 @Test
256 void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingExplode() throws Exception {
257 multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("imploding-8Kdict-3trees.zip"));
258 }
259
260 @Test
261 void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingStore() throws Exception {
262 multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-264.zip"));
263 }
264
265 @Test
266 void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Exception {
267 multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("SHRUNK.ZIP"));
268 }
269
270 @Test
271 void testMultiByteReadThrowsAtEofForCorruptedStoredEntry() throws Exception {
272 final byte[] content = readAllBytes("COMPRESS-264.zip");
273
274 for (int i = 17; i < 26; i++) {
275 content[i] = (byte) 0xff;
276 }
277 final byte[] buf = new byte[2];
278 try (ByteArrayInputStream in = new ByteArrayInputStream(content);
279 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
280 assertNotNull(archive.getNextEntry());
281 final IOException ex1 = assertThrows(IOException.class, () -> IOUtils.toByteArray(archive), "expected exception");
282 assertEquals("Truncated ZIP file", ex1.getMessage());
283 final IOException ex2 = assertThrows(IOException.class, () -> archive.read(buf), "expected exception");
284 assertEquals("Truncated ZIP file", ex2.getMessage());
285 final IOException ex3 = assertThrows(IOException.class, () -> archive.read(buf), "expected exception");
286 assertEquals("Truncated ZIP file", ex3.getMessage());
287 }
288 }
289
290 @Test
291 void testNameSourceDefaultsToName() throws Exception {
292 nameSource("bla.zip", "test1.xml", ZipArchiveEntry.NameSource.NAME);
293 }
294
295 @Test
296 void testNameSourceIsSetToEFS() throws Exception {
297 nameSource("utf8-7zip-test.zip", "\u20AC_for_Dollar.txt", 3, ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
298 }
299
300 @Test
301 void testNameSourceIsSetToUnicodeExtraField() throws Exception {
302 nameSource("utf8-winzip-test.zip", "\u20AC_for_Dollar.txt", ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
303 }
304
305
306
307
308 @Test
309 void testOffsets() throws Exception {
310
311 try (InputStream archiveStream = ZipArchiveInputStream.class.getResourceAsStream("/mixed.zip");
312 ZipArchiveInputStream zipStream = new ZipArchiveInputStream(archiveStream)) {
313 final ZipArchiveEntry inflatedEntry = zipStream.getNextZipEntry();
314 assertEquals("inflated.txt", inflatedEntry.getName());
315 assertEquals(0x0000, inflatedEntry.getLocalHeaderOffset());
316 assertEquals(0x0046, inflatedEntry.getDataOffset());
317 final ZipArchiveEntry storedEntry = zipStream.getNextZipEntry();
318 assertEquals("stored.txt", storedEntry.getName());
319 assertEquals(0x5892, storedEntry.getLocalHeaderOffset());
320 assertEquals(0x58d6, storedEntry.getDataOffset());
321 assertNull(zipStream.getNextZipEntry());
322 }
323 }
324
325 @Test
326 void testProperlyMarksEntriesAsUnreadableIfUncompressedSizeIsUnknown() throws Exception {
327
328 try (ZipArchiveInputStream zis = new ZipArchiveInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY))) {
329 final ZipArchiveEntry e = new ZipArchiveEntry("test");
330 e.setMethod(ZipMethod.DEFLATED.getCode());
331 assertTrue(zis.canReadEntryData(e));
332 e.setMethod(ZipMethod.ENHANCED_DEFLATED.getCode());
333 assertTrue(zis.canReadEntryData(e));
334 e.setMethod(ZipMethod.BZIP2.getCode());
335 assertFalse(zis.canReadEntryData(e));
336 }
337 }
338
339 @Test
340 void testProperlyReadsStoredEntries() throws IOException {
341 try (InputStream fs = newInputStream("bla-stored.zip");
342 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs)) {
343 ZipArchiveEntry e = archive.getNextZipEntry();
344 assertNotNull(e);
345 assertEquals("test1.xml", e.getName());
346 assertEquals(610, e.getCompressedSize());
347 assertEquals(610, e.getSize());
348 byte[] data = IOUtils.toByteArray(archive);
349 assertEquals(610, data.length);
350 e = archive.getNextZipEntry();
351 assertNotNull(e);
352 assertEquals("test2.xml", e.getName());
353 assertEquals(82, e.getCompressedSize());
354 assertEquals(82, e.getSize());
355 data = IOUtils.toByteArray(archive);
356 assertEquals(82, data.length);
357 assertNull(archive.getNextEntry());
358 }
359 }
360
361 @Test
362 void testProperlyReadsStoredEntryWithDataDescriptorWithoutSignature() throws IOException {
363 try (InputStream fs = newInputStream("bla-stored-dd-nosig.zip");
364 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
365 final ZipArchiveEntry e = archive.getNextZipEntry();
366 assertNotNull(e);
367 assertEquals("test1.xml", e.getName());
368 assertEquals(-1, e.getCompressedSize());
369 assertEquals(-1, e.getSize());
370 final byte[] data = IOUtils.toByteArray(archive);
371 assertEquals(610, data.length);
372 assertEquals(610, e.getCompressedSize());
373 assertEquals(610, e.getSize());
374 }
375 }
376
377 @Test
378 void testProperlyReadsStoredEntryWithDataDescriptorWithSignature() throws IOException {
379 try (InputStream fs = newInputStream("bla-stored-dd.zip");
380 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
381 final ZipArchiveEntry e = archive.getNextZipEntry();
382 assertNotNull(e);
383 assertEquals("test1.xml", e.getName());
384 assertEquals(-1, e.getCompressedSize());
385 assertEquals(-1, e.getSize());
386 final byte[] data = IOUtils.toByteArray(archive);
387 assertEquals(610, data.length);
388 assertEquals(610, e.getCompressedSize());
389 assertEquals(610, e.getSize());
390 }
391 }
392
393
394
395
396 @Test
397 void testProperUseOfInflater() throws Exception {
398 try (ZipFile zf = ZipFile.builder().setFile(getFile("COMPRESS-189.zip")).get()) {
399 final ZipArchiveEntry zae = zf.getEntry("USD0558682-20080101.ZIP");
400 try (ZipArchiveInputStream in = new ZipArchiveInputStream(new BufferedInputStream(zf.getInputStream(zae)))) {
401 ZipArchiveEntry innerEntry;
402 while ((innerEntry = in.getNextZipEntry()) != null) {
403 if (innerEntry.getName().endsWith("XML")) {
404 assertTrue(0 < in.read());
405 }
406 }
407 }
408 }
409 }
410
411
412
413
414 @Test
415 void testReadDeflate64CompressedStream() throws Exception {
416 final byte[] orig = readAllBytes("COMPRESS-380/COMPRESS-380-input");
417 final File archive = getFile("COMPRESS-380/COMPRESS-380.zip");
418 try (ZipArchiveInputStream zin = new ZipArchiveInputStream(Files.newInputStream(archive.toPath()))) {
419 assertNotNull(zin.getNextZipEntry());
420 final byte[] fromZip = IOUtils.toByteArray(zin);
421 assertArrayEquals(orig, fromZip);
422 }
423 }
424
425 @Test
426 void testReadDeflate64CompressedStreamWithDataDescriptor() throws Exception {
427
428 final File archive = getFile("COMPRESS-380/COMPRESS-380-dd.zip");
429 try (ZipArchiveInputStream zin = new ZipArchiveInputStream(Files.newInputStream(archive.toPath()))) {
430 final ZipArchiveEntry e = zin.getNextZipEntry();
431 assertEquals(-1, e.getSize());
432 assertEquals(ZipMethod.ENHANCED_DEFLATED.getCode(), e.getMethod());
433 final byte[] fromZip = IOUtils.toByteArray(zin);
434 final byte[] expected = { 'M', 'a', 'n', 'i', 'f', 'e', 's', 't', '-', 'V', 'e', 'r', 's', 'i', 'o', 'n', ':', ' ', '1', '.', '0', '\r', '\n', '\r',
435 '\n' };
436 assertArrayEquals(expected, fromZip);
437 zin.getNextZipEntry();
438 assertEquals(25, e.getSize());
439 }
440 }
441
442
443
444
445 @Test
446 void testReadingOfFirstStoredEntry() throws Exception {
447
448 try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("COMPRESS-264.zip"))) {
449 final ZipArchiveEntry ze = in.getNextZipEntry();
450 assertEquals(5, ze.getSize());
451 assertArrayEquals(new byte[] { 'd', 'a', 't', 'a', '\n' }, IOUtils.toByteArray(in));
452 }
453 }
454
455 @Test
456 void testRejectsStoredEntriesWithDataDescriptorByDefault() throws IOException {
457 try (InputStream fs = newInputStream("bla-stored-dd.zip");
458 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs)) {
459 final ZipArchiveEntry e = archive.getNextZipEntry();
460 assertNotNull(e);
461 assertEquals("test1.xml", e.getName());
462 assertEquals(-1, e.getCompressedSize());
463 assertEquals(-1, e.getSize());
464 assertThrows(UnsupportedZipFeatureException.class, () -> IOUtils.toByteArray(archive));
465 }
466 }
467
468 @Test
469 void testShouldConsumeArchiveCompletely() throws Exception {
470 try (InputStream is = ZipArchiveInputStreamTest.class.getResourceAsStream("/archive_with_trailer.zip");
471 ZipArchiveInputStream zip = new ZipArchiveInputStream(is)) {
472 getAllZipEntries(zip);
473 final byte[] expected = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n' };
474 final byte[] actual = new byte[expected.length];
475 is.read(actual);
476 assertArrayEquals(expected, actual);
477 }
478 }
479
480
481
482
483 @Test
484 void testShouldReadNestedZip() throws IOException {
485 try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("COMPRESS-219.zip"))) {
486 extractZipInputStream(in);
487 }
488 }
489
490 @Test
491 void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingBzip2() throws Exception {
492 singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("bzip2-zip.zip"));
493 }
494
495 @Test
496 void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate() throws Exception {
497 singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("bla.zip"));
498 }
499
500 @Test
501 void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate64() throws Exception {
502 singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-380/COMPRESS-380.zip"));
503 }
504
505 @Test
506 void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingExplode() throws Exception {
507 singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("imploding-8Kdict-3trees.zip"));
508 }
509
510 @Test
511 void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingStore() throws Exception {
512 singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-264.zip"));
513 }
514
515 @Test
516 void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Exception {
517 singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("SHRUNK.ZIP"));
518 }
519
520 @Test
521 void testSingleByteReadThrowsAtEofForCorruptedStoredEntry() throws Exception {
522 final byte[] content = readAllBytes("COMPRESS-264.zip");
523
524 for (int i = 17; i < 26; i++) {
525 content[i] = (byte) 0xff;
526 }
527 try (ByteArrayInputStream in = new ByteArrayInputStream(content);
528 ZipArchiveInputStream archive = new ZipArchiveInputStream(in)) {
529 assertNotNull(archive.getNextEntry());
530 final IOException ex1 = assertThrows(IOException.class, () -> IOUtils.toByteArray(archive), "expected exception");
531 assertEquals("Truncated ZIP file", ex1.getMessage());
532 final IOException ex2 = assertThrows(IOException.class, archive::read, "expected exception");
533 assertEquals("Truncated ZIP file", ex2.getMessage());
534 final IOException ex3 = assertThrows(IOException.class, archive::read, "expected exception");
535 assertEquals("Truncated ZIP file", ex3.getMessage());
536 }
537 }
538
539 @Test
540 void testSplitZipCreatedByWinrar() throws IOException {
541 final File lastFile = getFile("COMPRESS-477/split_zip_created_by_winrar/split_zip_created_by_winrar.zip");
542 try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
543 InputStream inputStream = Channels.newInputStream(channel);
544 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, false, true)) {
545
546 final File fileToCompare = getFile("COMPRESS-477/split_zip_created_by_winrar/zip_to_compare_created_by_winrar.zip");
547 try (ZipArchiveInputStream inputStreamToCompare = new ZipArchiveInputStream(Files.newInputStream(fileToCompare.toPath()),
548 StandardCharsets.UTF_8.name(), true, false, true)) {
549
550 ArchiveEntry entry;
551 while ((entry = splitInputStream.getNextEntry()) != null && inputStreamToCompare.getNextEntry() != null) {
552 if (entry.isDirectory()) {
553 continue;
554 }
555 assertArrayEquals(IOUtils.toByteArray(splitInputStream), IOUtils.toByteArray(inputStreamToCompare));
556 }
557 }
558 }
559 }
560
561 @Test
562 void testSplitZipCreatedByZip() throws IOException {
563 final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip.zip");
564 try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
565 InputStream inputStream = Channels.newInputStream(channel);
566 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, false, true)) {
567
568 final Path fileToCompare = getPath("COMPRESS-477/split_zip_created_by_zip/zip_to_compare_created_by_zip.zip");
569 try (ZipArchiveInputStream inputStreamToCompare = new ZipArchiveInputStream(Files.newInputStream(fileToCompare), StandardCharsets.UTF_8.name(),
570 true, false, true)) {
571
572 ArchiveEntry entry;
573 while ((entry = splitInputStream.getNextEntry()) != null && inputStreamToCompare.getNextEntry() != null) {
574 if (entry.isDirectory()) {
575 continue;
576 }
577 assertArrayEquals(IOUtils.toByteArray(splitInputStream), IOUtils.toByteArray(inputStreamToCompare));
578 }
579 }
580 }
581 }
582
583 @Test
584 void testSplitZipCreatedByZipOfZip64() throws IOException {
585 final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip_zip64.zip");
586 try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
587 InputStream inputStream = Channels.newInputStream(channel);
588 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, false, true)) {
589
590 final Path fileToCompare = getPath("COMPRESS-477/split_zip_created_by_zip/zip_to_compare_created_by_zip_zip64.zip");
591 try (ZipArchiveInputStream inputStreamToCompare = new ZipArchiveInputStream(Files.newInputStream(fileToCompare), StandardCharsets.UTF_8.name(),
592 true, false, true)) {
593
594 ArchiveEntry entry;
595 while ((entry = splitInputStream.getNextEntry()) != null && inputStreamToCompare.getNextEntry() != null) {
596 if (entry.isDirectory()) {
597 continue;
598 }
599 assertArrayEquals(IOUtils.toByteArray(splitInputStream), IOUtils.toByteArray(inputStreamToCompare));
600 }
601 }
602 }
603 }
604
605 @Test
606 void testSplitZipCreatedByZipThrowsException() throws IOException {
607 final File zipSplitFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip.z01");
608 try (ZipArchiveInputStream inputStream = new ZipArchiveInputStream(Files.newInputStream(zipSplitFile.toPath()), StandardCharsets.UTF_8.name(), true,
609 false, true)) {
610
611 assertThrows(EOFException.class, () -> {
612 ArchiveEntry entry = inputStream.getNextEntry();
613 while (entry != null) {
614 entry = inputStream.getNextEntry();
615 }
616 });
617 }
618 }
619
620
621
622
623 @Test
624 void testThrowOnInvalidEntry() throws Exception {
625 try (ZipArchiveInputStream zip = new ZipArchiveInputStream(ZipArchiveInputStreamTest.class.getResourceAsStream("/invalid-zip.zip"))) {
626 final ZipException expected = assertThrows(ZipException.class, zip::getNextZipEntry, "IOException expected");
627 assertTrue(expected.getMessage().contains("Cannot find zip signature"));
628 }
629 }
630
631 @Test
632 void testThrowsIfStoredDDIsDifferentFromLengthRead() throws IOException {
633 try (InputStream fs = newInputStream("bla-stored-dd-contradicts-actualsize.zip");
634 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
635 final ZipArchiveEntry e = archive.getNextZipEntry();
636 assertNotNull(e);
637 assertEquals("test1.xml", e.getName());
638 assertEquals(-1, e.getCompressedSize());
639 assertEquals(-1, e.getSize());
640 assertThrows(ZipException.class, () -> IOUtils.toByteArray(archive));
641 }
642 }
643
644 @Test
645 void testThrowsIfStoredDDIsInconsistent() throws IOException {
646 try (InputStream fs = newInputStream("bla-stored-dd-sizes-differ.zip");
647 ZipArchiveInputStream archive = new ZipArchiveInputStream(fs, StandardCharsets.UTF_8.name(), true, true)) {
648 final ZipArchiveEntry e = archive.getNextZipEntry();
649 assertNotNull(e);
650 assertEquals("test1.xml", e.getName());
651 assertEquals(-1, e.getCompressedSize());
652 assertEquals(-1, e.getSize());
653 assertThrows(ZipException.class, () -> IOUtils.toByteArray(archive));
654 }
655 }
656
657
658
659
660 @Test
661 void testThrowsIfThereIsNoEocd() {
662 assertThrows(IOException.class, () -> fuzzingTest(new int[] { 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
663 0x00, 0x43, 0xbe, 0x00, 0x00, 0x00, 0xb7, 0xe8, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 }));
664 }
665
666
667
668
669 @Test
670 void testThrowsIfZip64ExtraCouldNotBeUnderstood() {
671 assertThrows(IOException.class,
672 () -> fuzzingTest(new int[] { 0x50, 0x4b, 0x03, 0x04, 0x2e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x84, 0xb6, 0xba, 0x46, 0x72, 0xb6, 0xfe, 0x77, 0x63,
673 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c, 0x00, 0x62, 0x62, 0x62, 0x01, 0x00, 0x09, 0x00, 0x03, 0xe7, 0xce, 0x64,
674 0x55, 0xf3, 0xce, 0x64, 0x55, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0x5c, 0xf9, 0x01, 0x00, 0x04, 0x88, 0x13, 0x00, 0x00 }));
675 }
676
677 @Test
678 void testThrowsIOExceptionIfThereIsCorruptedZip64Extra() throws IOException {
679 try (InputStream fis = newInputStream("COMPRESS-546.zip");
680 ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(fis)) {
681 assertThrows(IOException.class, () -> getAllZipEntries(zipInputStream));
682 }
683 }
684
685 @Test
686 void testUnshrinkEntry() throws Exception {
687 try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("SHRUNK.ZIP"))) {
688 ZipArchiveEntry entry = in.getNextZipEntry();
689 assertEquals(ZipMethod.UNSHRINKING.getCode(), entry.getMethod(), "method");
690 assertTrue(in.canReadEntryData(entry));
691
692 try (InputStream original = newInputStream("test1.xml")) {
693 try {
694 assertArrayEquals(IOUtils.toByteArray(original), IOUtils.toByteArray(in));
695 } finally {
696 original.close();
697 }
698
699 entry = in.getNextZipEntry();
700 assertEquals(ZipMethod.UNSHRINKING.getCode(), entry.getMethod(), "method");
701 assertTrue(in.canReadEntryData(entry));
702 }
703
704 assertArrayEquals(readAllBytes("test2.xml"), IOUtils.toByteArray(in));
705 }
706 }
707
708 @Test
709 void testUnzipBZip2CompressedEntry() throws Exception {
710
711 try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("bzip2-zip.zip"))) {
712 final ZipArchiveEntry ze = in.getNextZipEntry();
713 assertEquals(42, ze.getSize());
714 final byte[] expected = ArrayFill.fill(new byte[42], (byte) 'a');
715 assertArrayEquals(expected, IOUtils.toByteArray(in));
716 }
717 }
718
719
720
721
722 @Test
723 void testWinzipBackSlashWorkaround() throws Exception {
724 try (ZipArchiveInputStream in = new ZipArchiveInputStream(newInputStream("test-winzip.zip"))) {
725 ZipArchiveEntry zae = in.getNextZipEntry();
726 zae = in.getNextZipEntry();
727 zae = in.getNextZipEntry();
728 assertEquals("\u00e4/", zae.getName());
729 }
730 }
731
732
733
734
735 @Test
736 void testWithBytesAfterData() throws Exception {
737 final int expectedNumEntries = 2;
738 try (InputStream is = ZipArchiveInputStreamTest.class.getResourceAsStream("/archive_with_bytes_after_data.zip");
739 ZipArchiveInputStream zip = new ZipArchiveInputStream(is)) {
740 int actualNumEntries = 0;
741 ZipArchiveEntry zae = zip.getNextZipEntry();
742 while (zae != null) {
743 actualNumEntries++;
744 readEntry(zip, zae);
745 zae = zip.getNextZipEntry();
746 }
747 assertEquals(expectedNumEntries, actualNumEntries);
748 }
749 }
750
751
752
753
754 @Test
755 void testWriteZipWithLinks() throws IOException {
756 try (OutputStream output = new FileOutputStream("target/zipWithLinks.zip");
757 ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(output)) {
758 zipOutputStream.putArchiveEntry(new ZipArchiveEntry("original"));
759 zipOutputStream.write("original content".getBytes());
760 zipOutputStream.closeArchiveEntry();
761 final ZipArchiveEntry entry = new ZipArchiveEntry("link");
762 entry.setUnixMode(UnixStat.LINK_FLAG | 0444);
763 assertEquals(ZipArchiveEntry.PLATFORM_UNIX, entry.getPlatform());
764 assertTrue(entry.isUnixSymlink());
765 zipOutputStream.putArchiveEntry(entry);
766 zipOutputStream.write("original".getBytes());
767 zipOutputStream.closeArchiveEntry();
768 }
769
770 try (ZipFile zipFile = ZipFile.builder().setFile("target/zipWithLinks.zip").get()) {
771 assertTrue(zipFile.getEntry("link").isUnixSymlink(), "'link' detected but it's not sym link");
772 assertFalse(zipFile.getEntry("original").isUnixSymlink(), "'original' detected but it's not sym link");
773 }
774
775 try (ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(new FileInputStream("target/zipWithLinks.zip"))) {
776 ZipArchiveEntry entry;
777 int entriesCount = 0;
778 while ((entry = zipInputStream.getNextEntry()) != null) {
779 if ("link".equals(entry.getName())) {
780
781
782 } else {
783 assertFalse(entry.isUnixSymlink(), "'original' detected but it's sym link and should be regular file");
784 }
785 entriesCount++;
786 }
787 assertEquals(2, entriesCount);
788 }
789 }
790
791 @Test
792 void testZipArchiveInputStreamSubclassReplacement() throws IOException {
793 try (InputStream fs = newInputStream("COMPRESS-692/compress-692.zip");
794 AirliftZipArchiveInputStream archive = new AirliftZipArchiveInputStream(fs)) {
795 assertFalse(archive.isUsed());
796 ZipArchiveEntry e = archive.getNextEntry();
797 assertNotNull(e);
798 assertEquals(ZipMethod.ZSTD.getCode(), e.getMethod());
799 assertEquals("dolor.txt", e.getName());
800 assertEquals(635, e.getCompressedSize());
801 assertEquals(6066, e.getSize());
802 byte[] data = IOUtils.toByteArray(archive);
803 assertEquals(6066, data.length);
804 assertTrue(archive.isUsed());
805 e = archive.getNextEntry();
806 assertNotNull(e);
807 assertEquals(ZipMethod.ZSTD.getCode(), e.getMethod());
808 assertEquals("ipsum.txt", e.getName());
809 assertEquals(636, e.getCompressedSize());
810 assertEquals(6072, e.getSize());
811 data = IOUtils.toByteArray(archive);
812 assertEquals(6072, data.length);
813 assertNotNull(archive.getNextEntry());
814 }
815 }
816
817 @ParameterizedTest
818 @ValueSource(booleans = { true, false })
819 void testZipInputStream(final boolean allowStoredEntriesWithDataDescriptor) {
820 try (ZipArchiveInputStream zIn = new ZipArchiveInputStream(Files.newInputStream(Paths.get("src/test/resources/COMPRESS-647/test.zip")),
821 StandardCharsets.UTF_8.name(), false, allowStoredEntriesWithDataDescriptor)) {
822 ZipArchiveEntry zae = zIn.getNextEntry();
823 while (zae != null) {
824 zae = zIn.getNextEntry();
825 }
826 } catch (final IOException e) {
827
828 }
829 }
830
831 @Test
832 void testZipUsingStoredWithDDAndNoDDSignature() throws IOException {
833 try (InputStream inputStream = forgeZipInputStream();
834 ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(inputStream, StandardCharsets.UTF_8.name(), true, true)) {
835 getAllZipEntries(zipInputStream);
836 }
837 }
838
839 @Test
840 void testZipWithBadExtraFields() throws IOException {
841 try (InputStream fis = newInputStream("COMPRESS-548.zip");
842 ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(fis)) {
843 getAllZipEntries(zipInputStream);
844 }
845 }
846
847 @Test
848 void testZipWithLongerBeginningGarbage() throws IOException {
849 final Path path = createTempPath("preamble", ".zip");
850
851 try (OutputStream fos = Files.newOutputStream(path)) {
852 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));
853 try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) {
854 final ZipArchiveEntry entry = new ZipArchiveEntry("file-1.txt");
855 entry.setMethod(ZipEntry.DEFLATED);
856 zos.putArchiveEntry(entry);
857 zos.writeUtf8("entry-content\n");
858 zos.closeArchiveEntry();
859 }
860 }
861
862 try (InputStream is = Files.newInputStream(path);
863 ZipArchiveInputStream zis = new ZipArchiveInputStream(is)) {
864 final ZipArchiveEntry entry = zis.getNextEntry();
865 assertEquals("file-1.txt", entry.getName());
866 final byte[] content = IOUtils.toByteArray(zis);
867 assertArrayEquals("entry-content\n".getBytes(StandardCharsets.UTF_8), content);
868 }
869 }
870
871 @Test
872 void testZipWithShortBeginningGarbage() throws IOException {
873 final Path path = createTempPath("preamble", ".zip");
874
875 try (OutputStream fos = Files.newOutputStream(path)) {
876 fos.write("#!/usr/bin/unzip\n".getBytes(StandardCharsets.UTF_8));
877 try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) {
878 final ZipArchiveEntry entry = new ZipArchiveEntry("file-1.txt");
879 entry.setMethod(ZipEntry.DEFLATED);
880 zos.putArchiveEntry(entry);
881 zos.writeUtf8("entry-content\n");
882 zos.closeArchiveEntry();
883 }
884 }
885
886 try (InputStream is = Files.newInputStream(path);
887 ZipArchiveInputStream zis = new ZipArchiveInputStream(is)) {
888 final ZipArchiveEntry entry = zis.getNextEntry();
889 assertEquals("file-1.txt", entry.getName());
890 final byte[] content = IOUtils.toByteArray(zis);
891 assertArrayEquals("entry-content\n".getBytes(StandardCharsets.UTF_8), content);
892 }
893 }
894 }