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 java.nio.charset.StandardCharsets.UTF_8;
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
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.assertNotEquals;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertNull;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  import static org.junit.jupiter.api.Assertions.fail;
31  
32  import java.io.ByteArrayOutputStream;
33  import java.io.File;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.OutputStream;
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.util.ArrayList;
43  import java.util.Arrays;
44  import java.util.Collections;
45  import java.util.Enumeration;
46  import java.util.HashMap;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.TreeMap;
51  import java.util.concurrent.atomic.AtomicInteger;
52  import java.util.zip.CRC32;
53  import java.util.zip.Deflater;
54  import java.util.zip.ZipEntry;
55  
56  import org.apache.commons.compress.AbstractTest;
57  import org.apache.commons.compress.utils.ByteUtils;
58  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
59  import org.apache.commons.io.IOUtils;
60  import org.apache.commons.io.function.IORunnable;
61  import org.apache.commons.lang3.ArrayFill;
62  import org.apache.commons.lang3.SystemUtils;
63  import org.junit.Assume;
64  import org.junit.jupiter.api.AfterEach;
65  import org.junit.jupiter.api.Assumptions;
66  import org.junit.jupiter.api.Test;
67  
68  public class ZipFileTest extends AbstractTest {
69  
70      private static final int OUT_OF_MEMORY = 137;
71  
72      private static void assertEntryName(final ArrayList<ZipArchiveEntry> entries, final int index, final String expectedName) {
73          final ZipArchiveEntry ze = entries.get(index);
74          assertEquals("src/main/java/org/apache/commons/compress/archivers/zip/" + expectedName + ".java", ze.getName());
75      }
76  
77      private static void nameSource(final String archive, final String entry, final ZipArchiveEntry.NameSource expected) throws Exception {
78          try (ZipFile zf = ZipFile.builder().setFile(getFile(archive)).get()) {
79              final ZipArchiveEntry ze = zf.getEntry(entry);
80              assertEquals(entry, ze.getName());
81              assertEquals(expected, ze.getNameSource());
82          }
83      }
84  
85      private ZipFile zf;
86  
87      private void assertAllReadMethods(final byte[] expected, final ZipFile zipFile, final ZipArchiveEntry entry) throws IOException {
88          // simple IOUtil read
89          try (InputStream stream = zf.getInputStream(entry)) {
90              final byte[] full = IOUtils.toByteArray(stream);
91              assertArrayEquals(expected, full);
92          }
93  
94          // big buffer at the beginning and then chunks by IOUtils read
95          try (InputStream stream = zf.getInputStream(entry)) {
96              byte[] full;
97              final byte[] bytes = new byte[0x40000];
98              final int read = stream.read(bytes);
99              if (read < 0) {
100                 full = ByteUtils.EMPTY_BYTE_ARRAY;
101             } else {
102                 full = readStreamRest(bytes, read, stream);
103             }
104             assertArrayEquals(expected, full);
105         }
106 
107         // small chunk / single byte and big buffer then
108         try (InputStream stream = zf.getInputStream(entry)) {
109             byte[] full;
110             final int single = stream.read();
111             if (single < 0) {
112                 full = ByteUtils.EMPTY_BYTE_ARRAY;
113             } else {
114                 final byte[] big = new byte[0x40000];
115                 big[0] = (byte) single;
116                 final int read = stream.read(big, 1, big.length - 1);
117                 if (read < 0) {
118                     full = new byte[] { (byte) single };
119                 } else {
120                     full = readStreamRest(big, read + 1, stream);
121                 }
122             }
123             assertArrayEquals(expected, full);
124         }
125     }
126 
127     private void assertFileEqualIgnoreEndOfLine(final File file1, final File file2) throws IOException {
128         final List<String> linesOfFile1 = Files.readAllLines(Paths.get(file1.getCanonicalPath()), UTF_8);
129         final List<String> linesOfFile2 = Files.readAllLines(Paths.get(file2.getCanonicalPath()), UTF_8);
130 
131         if (linesOfFile1.size() != linesOfFile2.size()) {
132             fail("files not equal : " + file1.getName() + " , " + file2.getName());
133         }
134 
135         String tempLineInFile1;
136         String tempLineInFile2;
137         for (int i = 0; i < linesOfFile1.size(); i++) {
138             tempLineInFile1 = linesOfFile1.get(i).replace("\r\n", "\n");
139             tempLineInFile2 = linesOfFile2.get(i).replace("\r\n", "\n");
140             assertEquals(tempLineInFile1, tempLineInFile2);
141         }
142     }
143 
144     private void assertFileEqualsToEntry(final File fileToCompare, final ZipArchiveEntry entry, final ZipFile zipFile) throws IOException {
145         final byte[] buffer = new byte[10240];
146         final File tempFile = createTempFile("temp", "txt");
147         try (OutputStream outputStream = Files.newOutputStream(tempFile.toPath());
148                 InputStream inputStream = zipFile.getInputStream(entry)) {
149             int readLen;
150             while ((readLen = inputStream.read(buffer)) > 0) {
151                 outputStream.write(buffer, 0, readLen);
152             }
153         }
154         assertFileEqualIgnoreEndOfLine(fileToCompare, tempFile);
155     }
156 
157     private long calculateCrc32(final byte[] content) {
158         final CRC32 crc = new CRC32();
159         crc.update(content);
160         return crc.getValue();
161     }
162 
163     private void multiByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
164         final byte[] buf = new byte[2];
165         try (ZipFile archive = ZipFile.builder().setFile(file).get()) {
166             final ZipArchiveEntry e = archive.getEntries().nextElement();
167             try (InputStream is = archive.getInputStream(e)) {
168                 IOUtils.toByteArray(is);
169                 assertEquals(-1, is.read(buf));
170                 assertEquals(-1, is.read(buf));
171             }
172         }
173     }
174 
175     /*
176      * ordertest.zip has been handcrafted.
177      *
178      * It contains enough files so any random coincidence of entries.keySet() and central directory order would be unlikely - in fact testCDOrder fails in svn
179      * revision 920284.
180      *
181      * The central directory has ZipFile and ZipUtil swapped so central directory order is different from entry data order.
182      */
183     private void readOrderTest() throws Exception {
184         zf = ZipFile.builder().setFile(getFile("ordertest.zip")).get();
185     }
186 
187     /**
188      * Utility to append the rest of the stream to already read data.
189      */
190     private byte[] readStreamRest(final byte[] beginning, final int length, final InputStream stream) throws IOException {
191         final byte[] rest = IOUtils.toByteArray(stream);
192         final byte[] full = new byte[length + rest.length];
193         System.arraycopy(beginning, 0, full, 0, length);
194         System.arraycopy(rest, 0, full, length, rest.length);
195         return full;
196     }
197 
198     private void singleByteReadConsistentlyReturnsMinusOneAtEof(final File file) throws Exception {
199         try (ZipFile archive = ZipFile.builder().setFile(file).get();) {
200             final ZipArchiveEntry e = archive.getEntries().nextElement();
201             try (InputStream is = archive.getInputStream(e)) {
202                 IOUtils.toByteArray(is);
203                 assertEquals(-1, is.read());
204                 assertEquals(-1, is.read());
205             }
206         }
207     }
208 
209     @AfterEach
210     public void tearDownClose() {
211         ZipFile.closeQuietly(zf);
212     }
213 
214     @Test
215     public void testCDOrder() throws Exception {
216         readOrderTest();
217         testCDOrderInMemory();
218     }
219 
220     @Test
221     public void testCDOrderInMemory() throws Exception {
222         final byte[] data = readAllBytes("ordertest.zip");
223         zf = ZipFile.builder().setByteArray(data).setCharset(StandardCharsets.UTF_8).get();
224         testCDOrderInMemory(zf);
225         try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data)) {
226             zf = ZipFile.builder().setSeekableByteChannel(channel).setCharset(StandardCharsets.UTF_8).get();
227             testCDOrderInMemory(zf);
228         }
229         try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data)) {
230             zf = new ZipFile(channel, StandardCharsets.UTF_8.name());
231             testCDOrderInMemory(zf);
232         }
233     }
234 
235     private void testCDOrderInMemory(final ZipFile zipFile) {
236         final ArrayList<ZipArchiveEntry> list = Collections.list(zipFile.getEntries());
237         assertEntryName(list, 0, "AbstractUnicodeExtraField");
238         assertEntryName(list, 1, "AsiExtraField");
239         assertEntryName(list, 2, "ExtraFieldUtils");
240         assertEntryName(list, 3, "FallbackZipEncoding");
241         assertEntryName(list, 4, "GeneralPurposeBit");
242         assertEntryName(list, 5, "JarMarker");
243         assertEntryName(list, 6, "NioZipEncoding");
244         assertEntryName(list, 7, "Simple8BitZipEncoding");
245         assertEntryName(list, 8, "UnicodeCommentExtraField");
246         assertEntryName(list, 9, "UnicodePathExtraField");
247         assertEntryName(list, 10, "UnixStat");
248         assertEntryName(list, 11, "UnparseableExtraFieldData");
249         assertEntryName(list, 12, "UnrecognizedExtraField");
250         assertEntryName(list, 13, "ZipArchiveEntry");
251         assertEntryName(list, 14, "ZipArchiveInputStream");
252         assertEntryName(list, 15, "ZipArchiveOutputStream");
253         assertEntryName(list, 16, "ZipEncoding");
254         assertEntryName(list, 17, "ZipEncodingHelper");
255         assertEntryName(list, 18, "ZipExtraField");
256         assertEntryName(list, 19, "ZipUtil");
257         assertEntryName(list, 20, "ZipLong");
258         assertEntryName(list, 21, "ZipShort");
259         assertEntryName(list, 22, "ZipFile");
260     }
261 
262     @Test
263     public void testConcurrentReadFile() throws Exception {
264         // mixed.zip contains both inflated and stored files
265         final File archive = getFile("mixed.zip");
266         zf = new ZipFile(archive);
267 
268         final Map<String, byte[]> content = new HashMap<>();
269         for (final ZipArchiveEntry entry : Collections.list(zf.getEntries())) {
270             try (InputStream inputStream = zf.getInputStream(entry)) {
271                 content.put(entry.getName(), IOUtils.toByteArray(inputStream));
272             }
273         }
274 
275         final AtomicInteger passedCount = new AtomicInteger();
276         final IORunnable run = () -> {
277             for (final ZipArchiveEntry entry : Collections.list(zf.getEntries())) {
278                 assertAllReadMethods(content.get(entry.getName()), zf, entry);
279             }
280             passedCount.incrementAndGet();
281         };
282         final Thread t0 = new Thread(run.asRunnable());
283         final Thread t1 = new Thread(run.asRunnable());
284         t0.start();
285         t1.start();
286         t0.join();
287         t1.join();
288         assertEquals(2, passedCount.get());
289     }
290 
291     @Test
292     public void testConcurrentReadSeekable() throws Exception {
293         // mixed.zip contains both inflated and stored files
294         byte[] data;
295         try (InputStream fis = newInputStream("mixed.zip")) {
296             data = IOUtils.toByteArray(fis);
297         }
298         try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(data)) {
299             zf = ZipFile.builder().setSeekableByteChannel(channel).setCharset(StandardCharsets.UTF_8).get();
300 
301             final Map<String, byte[]> content = new HashMap<>();
302             for (final ZipArchiveEntry entry : Collections.list(zf.getEntries())) {
303                 try (InputStream inputStream = zf.getInputStream(entry)) {
304                     content.put(entry.getName(), IOUtils.toByteArray(inputStream));
305                 }
306             }
307 
308             final AtomicInteger passedCount = new AtomicInteger();
309             final IORunnable run = () -> {
310                 for (final ZipArchiveEntry entry : Collections.list(zf.getEntries())) {
311                     assertAllReadMethods(content.get(entry.getName()), zf, entry);
312                 }
313                 passedCount.incrementAndGet();
314             };
315             final Thread t0 = new Thread(run.asRunnable());
316             final Thread t1 = new Thread(run.asRunnable());
317             t0.start();
318             t1.start();
319             t0.join();
320             t1.join();
321             assertEquals(2, passedCount.get());
322         }
323     }
324 
325     /**
326      * Test correct population of header and data offsets when they are written after stream.
327      */
328     @Test
329     public void testDelayedOffsetsAndSizes() throws Exception {
330         final ByteArrayOutputStream zipContent = new ByteArrayOutputStream();
331         try (ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(zipContent)) {
332             final ZipArchiveEntry inflatedEntry = new ZipArchiveEntry("inflated.txt");
333             inflatedEntry.setMethod(ZipEntry.DEFLATED);
334             zipOutput.putArchiveEntry(inflatedEntry);
335             zipOutput.write("Hello Deflated\n".getBytes());
336             zipOutput.closeArchiveEntry();
337 
338             final byte[] storedContent = "Hello Stored\n".getBytes();
339             final ZipArchiveEntry storedEntry = new ZipArchiveEntry("stored.txt");
340             storedEntry.setMethod(ZipEntry.STORED);
341             storedEntry.setSize(storedContent.length);
342             storedEntry.setCrc(calculateCrc32(storedContent));
343             zipOutput.putArchiveEntry(storedEntry);
344             zipOutput.write("Hello Stored\n".getBytes());
345             zipOutput.closeArchiveEntry();
346 
347         }
348 
349         try (ZipFile zf = ZipFile.builder().setByteArray(zipContent.toByteArray()).get()) {
350             final ZipArchiveEntry inflatedEntry = zf.getEntry("inflated.txt");
351             assertNotEquals(-1L, inflatedEntry.getLocalHeaderOffset());
352             assertNotEquals(-1L, inflatedEntry.getDataOffset());
353             assertTrue(inflatedEntry.isStreamContiguous());
354             assertNotEquals(-1L, inflatedEntry.getCompressedSize());
355             assertNotEquals(-1L, inflatedEntry.getSize());
356             final ZipArchiveEntry storedEntry = zf.getEntry("stored.txt");
357             assertNotEquals(-1L, storedEntry.getLocalHeaderOffset());
358             assertNotEquals(-1L, storedEntry.getDataOffset());
359             assertTrue(inflatedEntry.isStreamContiguous());
360             assertNotEquals(-1L, storedEntry.getCompressedSize());
361             assertNotEquals(-1L, storedEntry.getSize());
362         }
363     }
364 
365     @Test
366     public void testDoubleClose() throws Exception {
367         readOrderTest();
368         zf.close();
369         assertDoesNotThrow(zf::close, "Caught exception of second close");
370     }
371 
372     /**
373      * @see "https://issues.apache.org/jira/browse/COMPRESS-227"
374      */
375     @Test
376     public void testDuplicateEntry() throws Exception {
377         final File archive = getFile("COMPRESS-227.zip");
378         zf = new ZipFile(archive);
379 
380         final ZipArchiveEntry ze = zf.getEntry("test1.txt");
381         assertNotNull(ze);
382         try (InputStream inputStream = zf.getInputStream(ze)) {
383             assertNotNull(inputStream);
384 
385             int numberOfEntries = 0;
386             for (final ZipArchiveEntry entry : zf.getEntries("test1.txt")) {
387                 numberOfEntries++;
388                 try (InputStream inputStream2 = zf.getInputStream(entry)) {
389                     assertNotNull(inputStream2);
390                 }
391             }
392             assertEquals(2, numberOfEntries);
393         }
394     }
395 
396     /**
397      * Test entries alignment.
398      */
399     @Test
400     public void testEntryAlignment() throws Exception {
401         try (SeekableInMemoryByteChannel zipContent = new SeekableInMemoryByteChannel()) {
402             try (ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(zipContent)) {
403                 final ZipArchiveEntry inflatedEntry = new ZipArchiveEntry("inflated.txt");
404                 inflatedEntry.setMethod(ZipEntry.DEFLATED);
405                 inflatedEntry.setAlignment(1024);
406                 zipOutput.putArchiveEntry(inflatedEntry);
407                 zipOutput.write("Hello Deflated\n".getBytes(UTF_8));
408                 zipOutput.closeArchiveEntry();
409 
410                 final ZipArchiveEntry storedEntry = new ZipArchiveEntry("stored.txt");
411                 storedEntry.setMethod(ZipEntry.STORED);
412                 storedEntry.setAlignment(1024);
413                 zipOutput.putArchiveEntry(storedEntry);
414                 zipOutput.write("Hello Stored\n".getBytes(UTF_8));
415                 zipOutput.closeArchiveEntry();
416 
417                 final ZipArchiveEntry storedEntry2 = new ZipArchiveEntry("stored2.txt");
418                 storedEntry2.setMethod(ZipEntry.STORED);
419                 storedEntry2.setAlignment(1024);
420                 storedEntry2.addExtraField(new ResourceAlignmentExtraField(1));
421                 zipOutput.putArchiveEntry(storedEntry2);
422                 zipOutput.write("Hello overload-alignment Stored\n".getBytes(UTF_8));
423                 zipOutput.closeArchiveEntry();
424 
425                 final ZipArchiveEntry storedEntry3 = new ZipArchiveEntry("stored3.txt");
426                 storedEntry3.setMethod(ZipEntry.STORED);
427                 storedEntry3.addExtraField(new ResourceAlignmentExtraField(1024));
428                 zipOutput.putArchiveEntry(storedEntry3);
429                 zipOutput.write("Hello copy-alignment Stored\n".getBytes(UTF_8));
430                 zipOutput.closeArchiveEntry();
431 
432             }
433 
434             try (ZipFile zf = ZipFile.builder().setByteArray(Arrays.copyOfRange(zipContent.array(), 0, (int) zipContent.size())).get()) {
435                 final ZipArchiveEntry inflatedEntry = zf.getEntry("inflated.txt");
436                 final ResourceAlignmentExtraField inflatedAlignmentEx = (ResourceAlignmentExtraField) inflatedEntry
437                         .getExtraField(ResourceAlignmentExtraField.ID);
438                 assertNotEquals(-1L, inflatedEntry.getCompressedSize());
439                 assertNotEquals(-1L, inflatedEntry.getSize());
440                 assertEquals(0L, inflatedEntry.getDataOffset() % 1024);
441                 assertNotNull(inflatedAlignmentEx);
442                 assertEquals(1024, inflatedAlignmentEx.getAlignment());
443                 assertFalse(inflatedAlignmentEx.allowMethodChange());
444                 try (InputStream stream = zf.getInputStream(inflatedEntry)) {
445                     assertEquals("Hello Deflated\n", new String(IOUtils.toByteArray(stream), UTF_8));
446                 }
447                 final ZipArchiveEntry storedEntry = zf.getEntry("stored.txt");
448                 final ResourceAlignmentExtraField storedAlignmentEx = (ResourceAlignmentExtraField) storedEntry.getExtraField(ResourceAlignmentExtraField.ID);
449                 assertNotEquals(-1L, storedEntry.getCompressedSize());
450                 assertNotEquals(-1L, storedEntry.getSize());
451                 assertEquals(0L, storedEntry.getDataOffset() % 1024);
452                 assertNotNull(storedAlignmentEx);
453                 assertEquals(1024, storedAlignmentEx.getAlignment());
454                 assertFalse(storedAlignmentEx.allowMethodChange());
455                 try (InputStream stream = zf.getInputStream(storedEntry)) {
456                     assertEquals("Hello Stored\n", new String(IOUtils.toByteArray(stream), UTF_8));
457                 }
458 
459                 final ZipArchiveEntry storedEntry2 = zf.getEntry("stored2.txt");
460                 final ResourceAlignmentExtraField stored2AlignmentEx = (ResourceAlignmentExtraField) storedEntry2.getExtraField(ResourceAlignmentExtraField.ID);
461                 assertNotEquals(-1L, storedEntry2.getCompressedSize());
462                 assertNotEquals(-1L, storedEntry2.getSize());
463                 assertEquals(0L, storedEntry2.getDataOffset() % 1024);
464                 assertNotNull(stored2AlignmentEx);
465                 assertEquals(1024, stored2AlignmentEx.getAlignment());
466                 assertFalse(stored2AlignmentEx.allowMethodChange());
467                 try (InputStream stream = zf.getInputStream(storedEntry2)) {
468                     assertEquals("Hello overload-alignment Stored\n", new String(IOUtils.toByteArray(stream), UTF_8));
469                 }
470 
471                 final ZipArchiveEntry storedEntry3 = zf.getEntry("stored3.txt");
472                 final ResourceAlignmentExtraField stored3AlignmentEx = (ResourceAlignmentExtraField) storedEntry3.getExtraField(ResourceAlignmentExtraField.ID);
473                 assertNotEquals(-1L, storedEntry3.getCompressedSize());
474                 assertNotEquals(-1L, storedEntry3.getSize());
475                 assertEquals(0L, storedEntry3.getDataOffset() % 1024);
476                 assertNotNull(stored3AlignmentEx);
477                 assertEquals(1024, stored3AlignmentEx.getAlignment());
478                 assertFalse(stored3AlignmentEx.allowMethodChange());
479                 try (InputStream stream = zf.getInputStream(storedEntry3)) {
480                     assertEquals("Hello copy-alignment Stored\n", new String(IOUtils.toByteArray(stream), UTF_8));
481                 }
482             }
483         }
484     }
485 
486     /**
487      * Test too big alignment, resulting into exceeding extra field limit.
488      */
489     @Test
490     public void testEntryAlignmentExceed() throws Exception {
491         try (SeekableInMemoryByteChannel zipContent = new SeekableInMemoryByteChannel();
492                 ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(zipContent)) {
493             final ZipArchiveEntry inflatedEntry = new ZipArchiveEntry("inflated.txt");
494             inflatedEntry.setMethod(ZipEntry.STORED);
495             assertThrows(IllegalArgumentException.class, () -> inflatedEntry.setAlignment(0x20000));
496         }
497     }
498 
499     /**
500      * @see "https://issues.apache.org/jira/browse/COMPRESS-228"
501      */
502     @Test
503     public void testExcessDataInZip64ExtraField() throws Exception {
504         final File archive = getFile("COMPRESS-228.zip");
505         zf = new ZipFile(archive);
506         // actually, if we get here, the test already has passed
507 
508         final ZipArchiveEntry ze = zf.getEntry("src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java");
509         assertEquals(26101, ze.getSize());
510     }
511 
512     @Test
513     public void testExtractFileLiesAcrossSplitZipSegmentsCreatedByWinrar() throws Exception {
514         final File lastFile = getFile("COMPRESS-477/split_zip_created_by_winrar/split_zip_created_by_winrar.zip");
515         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile)) {
516             zf = ZipFile.builder().setSeekableByteChannel(channel).get();
517 
518             // the compressed content of ZipArchiveInputStream.java lies between .z01 and .z02
519             final ZipArchiveEntry zipEntry = zf.getEntry("commons-compress/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java");
520             final File fileToCompare = getFile("COMPRESS-477/split_zip_created_by_winrar/file_to_compare_1");
521             assertFileEqualsToEntry(fileToCompare, zipEntry, zf);
522         }
523     }
524 
525     @Test
526     public void testExtractFileLiesAcrossSplitZipSegmentsCreatedByZip() throws Exception {
527         final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip.zip");
528         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile)) {
529             zf = new ZipFile(channel);
530 
531             // the compressed content of UnsupportedCompressionAlgorithmException.java lies between .z01 and .z02
532             ZipArchiveEntry zipEntry = zf
533                     .getEntry("commons-compress/src/main/java/org/apache/commons/compress/archivers/dump/UnsupportedCompressionAlgorithmException.java");
534             File fileToCompare = getFile("COMPRESS-477/split_zip_created_by_zip/file_to_compare_1");
535             assertFileEqualsToEntry(fileToCompare, zipEntry, zf);
536 
537             // the compressed content of DeflateParameters.java lies between .z02 and .zip
538             zipEntry = zf.getEntry("commons-compress/src/main/java/org/apache/commons/compress/compressors/deflate/DeflateParameters.java");
539             fileToCompare = getFile("COMPRESS-477/split_zip_created_by_zip/file_to_compare_2");
540             assertFileEqualsToEntry(fileToCompare, zipEntry, zf);
541         }
542     }
543 
544     @Test
545     public void testExtractFileLiesAcrossSplitZipSegmentsCreatedByZipOfZip64() throws Exception {
546         final File lastFile = getFile("COMPRESS-477/split_zip_created_by_zip/split_zip_created_by_zip_zip64.zip");
547         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile)) {
548             zf = new ZipFile(channel);
549 
550             // the compressed content of UnsupportedCompressionAlgorithmException.java lies between .z01 and .z02
551             ZipArchiveEntry zipEntry = zf
552                     .getEntry("commons-compress/src/main/java/org/apache/commons/compress/archivers/dump/UnsupportedCompressionAlgorithmException.java");
553             File fileToCompare = getFile("COMPRESS-477/split_zip_created_by_zip/file_to_compare_1");
554             assertFileEqualsToEntry(fileToCompare, zipEntry, zf);
555 
556             // the compressed content of DeflateParameters.java lies between .z02 and .zip
557             zipEntry = zf.getEntry("commons-compress/src/main/java/org/apache/commons/compress/compressors/deflate/DeflateParameters.java");
558             fileToCompare = getFile("COMPRESS-477/split_zip_created_by_zip/file_to_compare_2");
559             assertFileEqualsToEntry(fileToCompare, zipEntry, zf);
560         }
561     }
562 
563     /**
564      * Test non power of 2 alignment.
565      */
566     @Test
567     public void testInvalidAlignment() {
568         assertThrows(IllegalArgumentException.class, () -> new ZipArchiveEntry("dummy").setAlignment(3));
569     }
570 
571     @Test
572     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingBzip2() throws Exception {
573         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("bzip2-zip.zip"));
574     }
575 
576     @Test
577     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate() throws Exception {
578         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("bla.zip"));
579     }
580 
581     @Test
582     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate64() throws Exception {
583         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-380/COMPRESS-380.zip"));
584     }
585 
586     @Test
587     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingExplode() throws Exception {
588         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("imploding-8Kdict-3trees.zip"));
589     }
590 
591     @Test
592     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingStore() throws Exception {
593         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-264.zip"));
594     }
595 
596     @Test
597     public void testMultiByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Exception {
598         multiByteReadConsistentlyReturnsMinusOneAtEof(getFile("SHRUNK.ZIP"));
599     }
600 
601     @Test
602     public void testNameSourceDefaultsToName() throws Exception {
603         nameSource("bla.zip", "test1.xml", ZipArchiveEntry.NameSource.NAME);
604     }
605 
606     @Test
607     public void testNameSourceIsSetToEFS() throws Exception {
608         nameSource("utf8-7zip-test.zip", "\u20AC_for_Dollar.txt", ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
609     }
610 
611     @Test
612     public void testNameSourceIsSetToUnicodeExtraField() throws Exception {
613         nameSource("utf8-winzip-test.zip", "\u20AC_for_Dollar.txt", ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
614     }
615 
616     /**
617      * Test correct population of header and data offsets.
618      */
619     @Test
620     public void testOffsets() throws Exception {
621         // mixed.zip contains both inflated and stored files
622         final File archive = getFile("mixed.zip");
623         try (ZipFile zf = new ZipFile(archive)) {
624             final ZipArchiveEntry inflatedEntry = zf.getEntry("inflated.txt");
625             assertEquals(0x0000, inflatedEntry.getLocalHeaderOffset());
626             assertEquals(0x0046, inflatedEntry.getDataOffset());
627             assertTrue(inflatedEntry.isStreamContiguous());
628             final ZipArchiveEntry storedEntry = zf.getEntry("stored.txt");
629             assertEquals(0x5892, storedEntry.getLocalHeaderOffset());
630             assertEquals(0x58d6, storedEntry.getDataOffset());
631             assertTrue(inflatedEntry.isStreamContiguous());
632         }
633     }
634 
635     @Test
636     public void testPhysicalOrder() throws Exception {
637         readOrderTest();
638         final ArrayList<ZipArchiveEntry> l = Collections.list(zf.getEntriesInPhysicalOrder());
639         assertEntryName(l, 0, "AbstractUnicodeExtraField");
640         assertEntryName(l, 1, "AsiExtraField");
641         assertEntryName(l, 2, "ExtraFieldUtils");
642         assertEntryName(l, 3, "FallbackZipEncoding");
643         assertEntryName(l, 4, "GeneralPurposeBit");
644         assertEntryName(l, 5, "JarMarker");
645         assertEntryName(l, 6, "NioZipEncoding");
646         assertEntryName(l, 7, "Simple8BitZipEncoding");
647         assertEntryName(l, 8, "UnicodeCommentExtraField");
648         assertEntryName(l, 9, "UnicodePathExtraField");
649         assertEntryName(l, 10, "UnixStat");
650         assertEntryName(l, 11, "UnparseableExtraFieldData");
651         assertEntryName(l, 12, "UnrecognizedExtraField");
652         assertEntryName(l, 13, "ZipArchiveEntry");
653         assertEntryName(l, 14, "ZipArchiveInputStream");
654         assertEntryName(l, 15, "ZipArchiveOutputStream");
655         assertEntryName(l, 16, "ZipEncoding");
656         assertEntryName(l, 17, "ZipEncodingHelper");
657         assertEntryName(l, 18, "ZipExtraField");
658         assertEntryName(l, 19, "ZipFile");
659         assertEntryName(l, 20, "ZipLong");
660         assertEntryName(l, 21, "ZipShort");
661         assertEntryName(l, 22, "ZipUtil");
662     }
663 
664     @Test
665     public void testPhysicalOrderOfSpecificFile() throws Exception {
666         readOrderTest();
667         final String entryName = "src/main/java/org/apache/commons/compress/archivers/zip/ZipExtraField.java";
668         final Iterable<ZipArchiveEntry> entries = zf.getEntriesInPhysicalOrder(entryName);
669         final Iterator<ZipArchiveEntry> iter = entries.iterator();
670         final ZipArchiveEntry entry = iter.next();
671 
672         assertEquals(entryName, entry.getName());
673         assertFalse(iter.hasNext());
674     }
675 
676     /**
677      * @see "https://issues.apache.org/jira/browse/COMPRESS-380"
678      */
679     @Test
680     public void testReadDeflate64CompressedStream() throws Exception {
681         final File input = getFile("COMPRESS-380/COMPRESS-380-input");
682         final File archive = getFile("COMPRESS-380/COMPRESS-380.zip");
683         try (InputStream in = Files.newInputStream(input.toPath());
684                 ZipFile zf = new ZipFile(archive)) {
685             final byte[] orig = IOUtils.toByteArray(in);
686             final ZipArchiveEntry e = zf.getEntry("input2");
687             try (InputStream s = zf.getInputStream(e)) {
688                 final byte[] fromZip = IOUtils.toByteArray(s);
689                 assertArrayEquals(orig, fromZip);
690             }
691         }
692     }
693 
694     /**
695      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-621" >COMPRESS-621</a>.
696      */
697     @Test
698     public void testReadingOfExtraDataBeforeZip() throws IOException {
699         final byte[] fileHeader = "Before Zip file".getBytes(UTF_8);
700         final String entryName = "COMPRESS-621.txt";
701         final byte[] entryContent = "https://issues.apache.org/jira/browse/COMPRESS-621".getBytes(UTF_8);
702         try (ZipFile archive = new ZipFile(getFile("COMPRESS-621.zip"))) {
703             assertEquals(fileHeader.length, archive.getFirstLocalFileHeaderOffset());
704             try (InputStream input = archive.getContentBeforeFirstLocalFileHeader()) {
705                 assertArrayEquals(fileHeader, IOUtils.toByteArray(input));
706             }
707 
708             final ZipArchiveEntry e = archive.getEntry(entryName);
709             assertEquals(entryContent.length, e.getSize());
710             try (InputStream input = archive.getInputStream(e)) {
711                 assertArrayEquals(entryContent, IOUtils.toByteArray(input));
712             }
713         }
714     }
715 
716     /**
717      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-264" >COMPRESS-264</a>.
718      */
719     @Test
720     public void testReadingOfFirstStoredEntry() throws Exception {
721         final File archive = getFile("COMPRESS-264.zip");
722         zf = new ZipFile(archive);
723         final ZipArchiveEntry ze = zf.getEntry("test.txt");
724         assertEquals(5, ze.getSize());
725         try (InputStream inputStream = zf.getInputStream(ze)) {
726             assertArrayEquals(new byte[] { 'd', 'a', 't', 'a', '\n' }, IOUtils.toByteArray(inputStream));
727         }
728     }
729 
730     @Test
731     public void testReadingOfStoredEntry() throws Exception {
732         final File file = createTempFile("commons-compress-zipfiletest", ".zip");
733         ZipArchiveEntry ze;
734         try (OutputStream o = Files.newOutputStream(file.toPath());
735                 ZipArchiveOutputStream zo = new ZipArchiveOutputStream(o)) {
736             ze = new ZipArchiveEntry("foo");
737             ze.setMethod(ZipEntry.STORED);
738             ze.setSize(4);
739             ze.setCrc(0xb63cfbcdL);
740             zo.putArchiveEntry(ze);
741             zo.write(new byte[] { 1, 2, 3, 4 });
742             zo.closeArchiveEntry();
743         }
744 
745         zf = new ZipFile(file);
746         ze = zf.getEntry("foo");
747         assertNotNull(ze);
748         try (InputStream i = zf.getInputStream(ze)) {
749             final byte[] b = new byte[4];
750             assertEquals(4, i.read(b));
751             assertEquals(-1, i.read());
752         }
753     }
754 
755     @Test
756     public void testSelfExtractingZipUsingUnzipsfx() throws IOException, InterruptedException {
757         final File unzipsfx = new File("/usr/bin/unzipsfx");
758         Assumptions.assumeTrue(unzipsfx.exists());
759 
760         final File testZip = createTempFile("commons-compress-selfExtractZipTest", ".zip");
761 
762         final String testEntryName = "test_self_extract_zip/foo";
763         final File extractedFile = new File(testZip.getParentFile(), testEntryName);
764 
765         final byte[] testData = { 1, 2, 3, 4 };
766         final byte[] buffer = new byte[512];
767         int bytesRead;
768         try (InputStream unzipsfxInputStream = Files.newInputStream(unzipsfx.toPath())) {
769             try (OutputStream outputStream = Files.newOutputStream(testZip.toPath());
770                     ZipArchiveOutputStream zo = new ZipArchiveOutputStream(outputStream)) {
771 
772                 while ((bytesRead = unzipsfxInputStream.read(buffer)) > 0) {
773                     zo.writePreamble(buffer, 0, bytesRead);
774                 }
775 
776                 final ZipArchiveEntry ze = new ZipArchiveEntry(testEntryName);
777                 ze.setMethod(ZipEntry.STORED);
778                 ze.setSize(4);
779                 ze.setCrc(0xb63cfbcdL);
780                 zo.putArchiveEntry(ze);
781                 zo.write(testData);
782                 zo.closeArchiveEntry();
783             }
784 
785             final ProcessBuilder pbChmod = new ProcessBuilder("chmod", "+x", testZip.getPath());
786             pbChmod.redirectErrorStream(true);
787             final Process processChmod = pbChmod.start();
788             try (InputStream processInputStream = processChmod.getInputStream()) {
789                 assertEquals(0, processChmod.waitFor(), new String(IOUtils.toByteArray(processInputStream)));
790             }
791 
792             final ProcessBuilder pb = new ProcessBuilder(testZip.getPath());
793             pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
794             pb.directory(testZip.getParentFile());
795             pb.redirectErrorStream(true);
796             final Process process = pb.start();
797             final int rc = process.waitFor();
798             if (rc == OUT_OF_MEMORY && SystemUtils.IS_OS_MAC) {
799                 // On my old Mac mini, this test runs out of memory, so allow the build to continue.
800                 Assume.assumeTrue(Boolean.getBoolean("skipReturnCode137"));
801                 return;
802             }
803             try (InputStream processInputStream = process.getInputStream()) {
804                 assertEquals(0, rc, new String(IOUtils.toByteArray(processInputStream)));
805             }
806             if (!extractedFile.exists()) {
807                 // fail if extracted file does not exist
808                 fail("Can not find the extracted file");
809             }
810 
811             try (InputStream inputStream = Files.newInputStream(extractedFile.toPath())) {
812                 bytesRead = org.apache.commons.compress.utils.IOUtils.readFully(inputStream, buffer);
813                 assertEquals(testData.length, bytesRead);
814                 assertArrayEquals(testData, Arrays.copyOfRange(buffer, 0, bytesRead));
815             }
816         } finally {
817             extractedFile.delete();
818             extractedFile.getParentFile().delete();
819         }
820     }
821 
822     @Test
823     public void testSetLevelTooBigForZipArchiveOutputStream() throws IOException {
824         try (ZipArchiveOutputStream fixture = new ZipArchiveOutputStream(new ByteArrayOutputStream())) {
825             assertThrows(IllegalArgumentException.class, () -> fixture.setLevel(Deflater.BEST_COMPRESSION + 1));
826         }
827     }
828 
829     @Test
830     public void testSetLevelTooSmallForZipArchiveOutputStream() throws IOException {
831         try (ZipArchiveOutputStream fixture = new ZipArchiveOutputStream(new ByteArrayOutputStream())) {
832         assertThrows(IllegalArgumentException.class, () -> fixture.setLevel(Deflater.DEFAULT_COMPRESSION - 1));
833         }
834     }
835 
836     @Test
837     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingBzip2() throws Exception {
838         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("bzip2-zip.zip"));
839     }
840 
841     @Test
842     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate() throws Exception {
843         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("bla.zip"));
844     }
845 
846     @Test
847     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingDeflate64() throws Exception {
848         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-380/COMPRESS-380.zip"));
849     }
850 
851     @Test
852     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingExplode() throws Exception {
853         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("imploding-8Kdict-3trees.zip"));
854     }
855 
856     @Test
857     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingStore() throws Exception {
858         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("COMPRESS-264.zip"));
859     }
860 
861     @Test
862     public void testSingleByteReadConsistentlyReturnsMinusOneAtEofUsingUnshrink() throws Exception {
863         singleByteReadConsistentlyReturnsMinusOneAtEof(getFile("SHRUNK.ZIP"));
864     }
865 
866     /**
867      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-208" >COMPRESS-208</a>.
868      */
869     @Test
870     public void testSkipsPK00Prefix() throws Exception {
871         final File archive = getFile("COMPRESS-208.zip");
872         zf = new ZipFile(archive);
873         assertNotNull(zf.getEntry("test1.xml"));
874         assertNotNull(zf.getEntry("test2.xml"));
875     }
876 
877     @Test
878     public void testThrowsExceptionWhenWritingPreamble() throws IOException {
879         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(new ByteArrayOutputStream())) {
880             outputStream.putArchiveEntry(new ZipArchiveEntry());
881             assertThrows(IllegalStateException.class, () -> outputStream.writePreamble(ByteUtils.EMPTY_BYTE_ARRAY));
882             outputStream.closeArchiveEntry();
883         }
884     }
885 
886     @Test
887     public void testUnixSymlinkSampleFile() throws Exception {
888         final String entryPrefix = "COMPRESS-214_unix_symlinks/";
889         final TreeMap<String, String> expectedVals = new TreeMap<>();
890 
891         // I threw in some Japanese characters to keep things interesting.
892         expectedVals.put(entryPrefix + "link1", "../COMPRESS-214_unix_symlinks/./a/b/c/../../../\uF999");
893         expectedVals.put(entryPrefix + "link2", "../COMPRESS-214_unix_symlinks/./a/b/c/../../../g");
894         expectedVals.put(entryPrefix + "link3", "../COMPRESS-214_unix_symlinks/././a/b/c/../../../\u76F4\u6A39");
895         expectedVals.put(entryPrefix + "link4", "\u82B1\u5B50/\u745B\u5B50");
896         expectedVals.put(entryPrefix + "\uF999", "./\u82B1\u5B50/\u745B\u5B50/\u5897\u8C37/\uF999");
897         expectedVals.put(entryPrefix + "g", "./a/b/c/d/e/f/g");
898         expectedVals.put(entryPrefix + "\u76F4\u6A39", "./g");
899 
900         // Notice how a directory link might contain a trailing slash, or it might not.
901         // Also note: symlinks are always stored as files, even if they link to directories.
902         expectedVals.put(entryPrefix + "link5", "../COMPRESS-214_unix_symlinks/././a/b");
903         expectedVals.put(entryPrefix + "link6", "../COMPRESS-214_unix_symlinks/././a/b/");
904 
905         // I looked into creating a test with hard links, but ZIP does not appear to
906         // support hard links, so nevermind.
907 
908         final File archive = getFile("COMPRESS-214_unix_symlinks.zip");
909 
910         zf = new ZipFile(archive);
911         final Enumeration<ZipArchiveEntry> en = zf.getEntries();
912         while (en.hasMoreElements()) {
913             final ZipArchiveEntry zae = en.nextElement();
914             final String link = zf.getUnixSymlink(zae);
915             if (zae.isUnixSymlink()) {
916                 final String name = zae.getName();
917                 final String expected = expectedVals.get(name);
918                 assertEquals(expected, link);
919             } else {
920                 // Should be null if it's not a symlink!
921                 assertNull(link);
922             }
923         }
924     }
925 
926     @Test
927     public void testUnshrinking() throws Exception {
928         zf = new ZipFile(getFile("SHRUNK.ZIP"));
929         ZipArchiveEntry test = zf.getEntry("TEST1.XML");
930         try (InputStream original = newInputStream("test1.xml");
931                 InputStream inputStream = zf.getInputStream(test)) {
932             assertArrayEquals(IOUtils.toByteArray(original), IOUtils.toByteArray(inputStream));
933         }
934         test = zf.getEntry("TEST2.XML");
935         try (InputStream original = newInputStream("test2.xml");
936                 InputStream inputStream = zf.getInputStream(test)) {
937             assertArrayEquals(IOUtils.toByteArray(original), IOUtils.toByteArray(inputStream));
938         }
939     }
940 
941     @Test
942     public void testUnzipBZip2CompressedEntry() throws Exception {
943         final File archive = getFile("bzip2-zip.zip");
944         zf = new ZipFile(archive);
945         final ZipArchiveEntry ze = zf.getEntry("lots-of-as");
946         assertEquals(42, ze.getSize());
947         final byte[] expected = ArrayFill.fill(new byte[42], (byte) 'a');
948         try (InputStream inputStream = zf.getInputStream(ze)) {
949             assertArrayEquals(expected, IOUtils.toByteArray(inputStream));
950         }
951     }
952 
953     /**
954      * @see "https://issues.apache.org/jira/browse/COMPRESS-176"
955      */
956     @Test
957     public void testWinzipBackSlashWorkaround() throws Exception {
958         final File archive = getFile("test-winzip.zip");
959         zf = new ZipFile(archive);
960         assertNull(zf.getEntry("\u00e4\\\u00fc.txt"));
961         assertNotNull(zf.getEntry("\u00e4/\u00fc.txt"));
962     }
963 
964     @Test
965     public void testZipWithShortBeginningGarbage() throws IOException {
966         final Path path = createTempPath("preamble", ".zip");
967         try (OutputStream fos = Files.newOutputStream(path)) {
968             fos.write("#!/usr/bin/unzip\n".getBytes(StandardCharsets.UTF_8));
969             try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) {
970                 final ZipArchiveEntry entry = new ZipArchiveEntry("file-1.txt");
971                 entry.setMethod(ZipEntry.DEFLATED);
972                 zos.putArchiveEntry(entry);
973                 zos.write("entry-content\n".getBytes(StandardCharsets.UTF_8));
974                 zos.closeArchiveEntry();
975             }
976         }
977         try (ZipFile zipFile = ZipFile.builder().setPath(path).get()) {
978             final ZipArchiveEntry entry = zipFile.getEntry("file-1.txt");
979             assertEquals("file-1.txt", entry.getName());
980             try (InputStream inputStream = zipFile.getInputStream(entry)) {
981                 final byte[] content = IOUtils.toByteArray(inputStream);
982                 assertArrayEquals("entry-content\n".getBytes(StandardCharsets.UTF_8), content);
983             }
984         }
985     }
986 
987 
988 }