View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers;
20  
21  import static java.nio.charset.StandardCharsets.UTF_8;
22  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
23  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
24  import static org.junit.jupiter.api.Assertions.assertEquals;
25  import static org.junit.jupiter.api.Assertions.assertFalse;
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  
31  import java.io.ByteArrayInputStream;
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.Channels;
38  import java.nio.channels.SeekableByteChannel;
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.Enumeration;
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.zip.ZipEntry;
49  import java.util.zip.ZipException;
50  
51  import org.apache.commons.compress.AbstractTest;
52  import org.apache.commons.compress.archivers.zip.Zip64Mode;
53  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
54  import org.apache.commons.compress.archivers.zip.ZipArchiveEntryPredicate;
55  import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
56  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
57  import org.apache.commons.compress.archivers.zip.ZipFile;
58  import org.apache.commons.compress.archivers.zip.ZipMethod;
59  import org.apache.commons.compress.archivers.zip.ZipSplitReadOnlySeekableByteChannel;
60  import org.apache.commons.compress.utils.InputStreamStatistics;
61  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
62  import org.apache.commons.io.IOUtils;
63  import org.junit.jupiter.api.Test;
64  
65  public final class ZipTest extends AbstractTest {
66  
67      final String first_payload = "ABBA";
68  
69      final String second_payload = "AAAAAAAAAAAA";
70  
71      final ZipArchiveEntryPredicate allFilesPredicate = zipArchiveEntry -> true;
72  
73      private void addFilesToZip(final ZipArchiveOutputStream outputStream, final File fileToAdd) throws IOException {
74          if (fileToAdd.isDirectory()) {
75              for (final File file : fileToAdd.listFiles()) {
76                  addFilesToZip(outputStream, file);
77              }
78          } else {
79              final ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(fileToAdd.getPath());
80              zipArchiveEntry.setMethod(ZipEntry.DEFLATED);
81  
82              outputStream.putArchiveEntry(zipArchiveEntry);
83              try {
84                  outputStream.write(fileToAdd);
85              } finally {
86                  outputStream.closeArchiveEntry();
87              }
88          }
89      }
90  
91      private void assertSameFileContents(final File expectedFile, final File actualFile) throws IOException {
92          final int size = (int) Math.max(expectedFile.length(), actualFile.length());
93          try (ZipFile expected = newZipFile(expectedFile);
94                  ZipFile actual = newZipFile(actualFile)) {
95              final byte[] expectedBuf = new byte[size];
96              final byte[] actualBuf = new byte[size];
97  
98              final Enumeration<ZipArchiveEntry> actualInOrder = actual.getEntriesInPhysicalOrder();
99              final Enumeration<ZipArchiveEntry> expectedInOrder = expected.getEntriesInPhysicalOrder();
100 
101             while (actualInOrder.hasMoreElements()) {
102                 final ZipArchiveEntry actualElement = actualInOrder.nextElement();
103                 final ZipArchiveEntry expectedElement = expectedInOrder.nextElement();
104                 assertEquals(expectedElement.getName(), actualElement.getName());
105                 // Don't compare timestamps since they may vary;
106                 // there's no support for stubbed out clock (TimeSource) in ZipArchiveOutputStream
107                 assertEquals(expectedElement.getMethod(), actualElement.getMethod());
108                 assertEquals(expectedElement.getGeneralPurposeBit(), actualElement.getGeneralPurposeBit());
109                 assertEquals(expectedElement.getCrc(), actualElement.getCrc());
110                 assertEquals(expectedElement.getCompressedSize(), actualElement.getCompressedSize());
111                 assertEquals(expectedElement.getSize(), actualElement.getSize());
112                 assertEquals(expectedElement.getExternalAttributes(), actualElement.getExternalAttributes());
113                 assertEquals(expectedElement.getInternalAttributes(), actualElement.getInternalAttributes());
114 
115                 try (InputStream actualIs = actual.getInputStream(actualElement);
116                         InputStream expectedIs = expected.getInputStream(expectedElement)) {
117                     org.apache.commons.compress.utils.IOUtils.readFully(expectedIs, expectedBuf);
118                     org.apache.commons.compress.utils.IOUtils.readFully(actualIs, actualBuf);
119                 }
120                 assertArrayEquals(expectedBuf, actualBuf); // Buffers are larger than payload. don't care
121             }
122 
123         }
124     }
125 
126     private int countNonDirectories(final File file) {
127         if (!file.isDirectory()) {
128             return 1;
129         }
130 
131         int result = 0;
132         for (final File fileInDirectory : file.listFiles()) {
133             result += countNonDirectories(fileInDirectory);
134         }
135 
136         return result;
137     }
138 
139     private void createArchiveEntry(final String payload, final ZipArchiveOutputStream zos, final String name) throws IOException {
140         final ZipArchiveEntry in = new ZipArchiveEntry(name);
141         zos.putArchiveEntry(in);
142 
143         zos.write(payload.getBytes());
144         zos.closeArchiveEntry();
145     }
146 
147     private byte[] createArtificialData(final int size) {
148         final ByteArrayOutputStream output = new ByteArrayOutputStream();
149         for (int i = 0; i < size; i += 1) {
150             output.write((byte) ((i & 1) == 0 ? i / 2 % 256 : i / 2 / 256));
151         }
152         return output.toByteArray();
153     }
154 
155     private ZipArchiveOutputStream createFirstEntry(final ZipArchiveOutputStream zos) throws IOException {
156         createArchiveEntry(first_payload, zos, "file1.txt");
157         return zos;
158     }
159 
160     private File createReferenceFile(final Zip64Mode zipMode, final String prefix) throws IOException {
161         final File reference = createTempFile(prefix, ".zip");
162         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(reference)) {
163             zos.setUseZip64(zipMode);
164             createFirstEntry(zos);
165             createSecondEntry(zos);
166         }
167         return reference;
168     }
169 
170     private ZipArchiveOutputStream createSecondEntry(final ZipArchiveOutputStream zos) throws IOException {
171         createArchiveEntry(second_payload, zos, "file2.txt");
172         return zos;
173     }
174 
175     private void createTestSplitZipSegments() throws IOException {
176         final File directoryToZip = getFilesToZip();
177         final File outputZipFile = newTempFile("splitZip.zip");
178         final long splitSize = 100 * 1024L; /* 100 KB */
179         try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) {
180             addFilesToZip(zipArchiveOutputStream, directoryToZip);
181         }
182     }
183 
184     private File getFilesToZip() throws IOException {
185         final File originalZipFile = getFile("COMPRESS-477/split_zip_created_by_zip/zip_to_compare_created_by_zip.zip");
186         try (ZipFile zipFile = newZipFile(originalZipFile)) {
187             zipFile.stream().filter(e -> !e.isDirectory()).forEach(zipEntry -> {
188                 Path outputFile = newTempPath(zipEntry.getName());
189                 if (!Files.exists(outputFile.getParent())) {
190                     Files.createDirectories(outputFile.getParent());
191                 }
192                 outputFile = newTempPath(zipEntry.getName());
193                 try (InputStream inputStream = zipFile.getInputStream(zipEntry);
194                         OutputStream outputStream = Files.newOutputStream(outputFile)) {
195                     IOUtils.copy(inputStream, outputStream);
196                 }
197             });
198         }
199         return getTempDirFile().listFiles()[0];
200     }
201 
202     private ZipFile newZipFile(final File file) throws IOException {
203         return ZipFile.builder().setFile(file).get();
204     }
205 
206     private void readStream(final InputStream in, final ArchiveEntry entry, final Map<String, List<List<Long>>> map) throws IOException {
207         final InputStreamStatistics stats = (InputStreamStatistics) in;
208         IOUtils.consume(in);
209         final String name = entry.getName();
210         final List<List<Long>> list = map.computeIfAbsent(name, k -> new ArrayList<>());
211         final long t = stats.getUncompressedCount();
212         final long b = stats.getCompressedCount();
213         list.add(Arrays.asList(t, b));
214     }
215 
216     /**
217      * Tests split archive with 32-bit limit, both STORED and DEFLATED.
218      */
219     @Test
220     void testBuildArtificialSplitZip32Test() throws IOException {
221         final File outputZipFile = newTempFile("artificialSplitZip.zip");
222         final long splitSize = 64 * 1024L; /* 64 KB */
223         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) {
224             outputStream.setUseZip64(Zip64Mode.Never);
225             final ZipArchiveEntry ze1 = new ZipArchiveEntry("file01");
226             ze1.setMethod(ZipEntry.STORED);
227             outputStream.putArchiveEntry(ze1);
228             outputStream.write(createArtificialData(65536));
229             outputStream.closeArchiveEntry();
230             final ZipArchiveEntry ze2 = new ZipArchiveEntry("file02");
231             ze2.setMethod(ZipEntry.DEFLATED);
232             outputStream.putArchiveEntry(ze2);
233             outputStream.write(createArtificialData(65536));
234             outputStream.closeArchiveEntry();
235         }
236 
237         try (ZipFile zipFile = ZipFile.builder()
238                 .setPath(outputZipFile.toPath())
239                 .setMaxNumberOfDisks(Integer.MAX_VALUE)
240                 .get()
241         ) {
242             assertArrayEquals(createArtificialData(65536), IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file01"))));
243             assertArrayEquals(createArtificialData(65536), IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file02"))));
244         }
245     }
246 
247     /**
248      * Tests split archive with 64-bit limit, both STORED and DEFLATED.
249      */
250     @Test
251     void testBuildArtificialSplitZip64Test() throws IOException {
252         final File outputZipFile = newTempFile("artificialSplitZip.zip");
253         final long splitSize = 64 * 1024L; /* 64 KB */
254         final byte[] data = createArtificialData(128 * 1024);
255         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) {
256             outputStream.setUseZip64(Zip64Mode.Always);
257             final ZipArchiveEntry ze1 = new ZipArchiveEntry("file01");
258             ze1.setMethod(ZipEntry.STORED);
259             outputStream.putArchiveEntry(ze1);
260             outputStream.write(data);
261             outputStream.closeArchiveEntry();
262             final ZipArchiveEntry ze2 = new ZipArchiveEntry("file02");
263             ze2.setMethod(ZipEntry.DEFLATED);
264             outputStream.putArchiveEntry(ze2);
265             outputStream.write(data);
266             outputStream.closeArchiveEntry();
267         }
268         try (ZipFile zipFile = ZipFile.builder().setPath(outputZipFile.toPath()).setMaxNumberOfDisks(Integer.MAX_VALUE).get()) {
269             assertArrayEquals(data, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file01"))));
270             assertArrayEquals(data, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file02"))));
271         }
272     }
273 
274     /**
275      * Tests split archive with 32-bit limit, with end of central directory skipping lack of space in segment.
276      */
277     @Test
278     void testBuildSplitZip32_endOfCentralDirectorySkipBoundary() throws IOException {
279         final File outputZipFile = newTempFile("artificialSplitZip.zip");
280         final long splitSize = 64 * 1024L; /* 64 KB */
281         // 4 is PK signature, 36 is size of header + local file header,
282         // 36 is length of central directory entry
283         // 1 is remaining byte in first archive, this should be skipped
284         final byte[] data1 = createArtificialData(64 * 1024 - 4 - 36 - 52 - 1);
285         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) {
286             outputStream.setUseZip64(Zip64Mode.Never);
287             final ZipArchiveEntry ze1 = new ZipArchiveEntry("file01");
288             ze1.setMethod(ZipEntry.STORED);
289             outputStream.putArchiveEntry(ze1);
290             outputStream.write(data1);
291             outputStream.closeArchiveEntry();
292         }
293         assertEquals(64 * 1024L - 1, Files.size(outputZipFile.toPath().getParent().resolve("artificialSplitZip.z01")));
294         try (ZipFile zipFile = ZipFile.builder().setPath(outputZipFile.toPath()).setMaxNumberOfDisks(Integer.MAX_VALUE).get()) {
295             assertArrayEquals(data1, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file01"))));
296         }
297     }
298 
299     /**
300      * Tests split archive with 32-bit limit, with file local headers crossing segment boundaries.
301      */
302     @Test
303     void testBuildSplitZip32_metaCrossBoundary() throws IOException {
304         final File outputZipFile = newTempFile("artificialSplitZip.zip");
305         final long splitSize = 64 * 1024L; /* 64 KB */
306         // 4 is PK signature, 36 is size of header + local file header,
307         // 15 is next local file header up to second byte of CRC
308         final byte[] data1 = createArtificialData(64 * 1024 - 4 - 36 - 15);
309         // 21 is remaining size of second local file header
310         // 19 is next local file header up to second byte of compressed size
311         final byte[] data2 = createArtificialData(64 * 1024 - 21 - 19);
312         // 17 is remaining size of third local file header
313         // 23 is next local file header up to second byte of uncompressed size
314         final byte[] data3 = createArtificialData(64 * 1024 - 17 - 23);
315         // 13 is remaining size of third local file header
316         // 1 is to wrap to next part
317         final byte[] data4 = createArtificialData(64 * 1024 - 13 + 1);
318         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) {
319             outputStream.setUseZip64(Zip64Mode.Never);
320             final ZipArchiveEntry ze1 = new ZipArchiveEntry("file01");
321             ze1.setMethod(ZipEntry.STORED);
322             outputStream.putArchiveEntry(ze1);
323             outputStream.write(data1);
324             outputStream.closeArchiveEntry();
325             final ZipArchiveEntry ze2 = new ZipArchiveEntry("file02");
326             ze2.setMethod(ZipEntry.STORED);
327             outputStream.putArchiveEntry(ze2);
328             outputStream.write(data2);
329             outputStream.closeArchiveEntry();
330             final ZipArchiveEntry ze3 = new ZipArchiveEntry("file03");
331             ze3.setMethod(ZipEntry.STORED);
332             outputStream.putArchiveEntry(ze3);
333             outputStream.write(data3);
334             outputStream.closeArchiveEntry();
335             final ZipArchiveEntry ze4 = new ZipArchiveEntry("file04");
336             ze4.setMethod(ZipEntry.STORED);
337             outputStream.putArchiveEntry(ze4);
338             outputStream.write(data4);
339             outputStream.closeArchiveEntry();
340         }
341         try (ZipFile zipFile = ZipFile.builder().setPath(outputZipFile.toPath()).setMaxNumberOfDisks(Integer.MAX_VALUE).get()) {
342             assertArrayEquals(data1, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file01"))));
343             assertArrayEquals(data2, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file02"))));
344             assertArrayEquals(data3, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file03"))));
345             assertArrayEquals(data4, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file04"))));
346         }
347     }
348 
349     @Test
350     void testBuildSplitZipTest() throws IOException {
351         final File directoryToZip = getFilesToZip();
352         createTestSplitZipSegments();
353         final File lastFile = newTempFile("splitZip.zip");
354         try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
355                 InputStream inputStream = Channels.newInputStream(channel);
356                 ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, UTF_8.toString(), true, false, true)) {
357 
358             ArchiveEntry entry;
359             final int filesNum = countNonDirectories(directoryToZip);
360             int filesCount = 0;
361             while ((entry = splitInputStream.getNextEntry()) != null) {
362                 if (entry.isDirectory()) {
363                     continue;
364                 }
365                 // compare all files one by one
366                 assertArrayEquals(IOUtils.toByteArray(splitInputStream), Files.readAllBytes(Paths.get(entry.getName())));
367                 filesCount++;
368             }
369             // and the number of files should equal
370             assertEquals(filesCount, filesNum);
371         }
372     }
373 
374     @Test
375     void testBuildSplitZipWithSegmentAlreadyExistThrowsException() throws IOException {
376         final File directoryToZip = getFilesToZip();
377         final File outputZipFile = newTempFile("splitZip.zip");
378         final long splitSize = 100 * 1024L; /* 100 KB */
379         try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) {
380             // create a file that has the same name of one of the created split segments
381             final File sameNameFile = newTempFile("splitZip.z01");
382             sameNameFile.createNewFile();
383             assertThrows(IOException.class, () -> addFilesToZip(zipArchiveOutputStream, directoryToZip));
384         } catch (final Exception e) {
385             // Ignore:
386             // java.io.IOException: This archive contains unclosed entries.
387             // at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.finish(ZipArchiveOutputStream.java:563)
388             // at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.close(ZipArchiveOutputStream.java:1119)
389             // at org.apache.commons.compress.archivers.ZipTestCase.buildSplitZipWithSegmentAlreadyExistThrowsException(ZipTestCase.java:715)
390         }
391     }
392 
393     @Test
394     void testBuildSplitZipWithTooLargeSizeThrowsException() throws IOException {
395         final Path file = Files.createTempFile("temp", "zip");
396         try {
397             assertThrows(IllegalArgumentException.class, () -> new ZipArchiveOutputStream(file, 4294967295L + 1));
398         } finally {
399             Files.delete(file);
400         }
401     }
402 
403     @Test
404     void testBuildSplitZipWithTooSmallSizeThrowsException() throws IOException {
405         createTempFile("temp", "zip").toPath();
406         assertThrows(IllegalArgumentException.class, () -> new ZipArchiveOutputStream(createTempFile("temp", "zip"), 64 * 1024 - 1));
407     }
408 
409     @Test
410     void testCopyRawEntriesFromFile() throws IOException {
411         final File reference = createReferenceFile(Zip64Mode.Never, "expected.");
412         final File file1 = createTempFile("src1.", ".zip");
413         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
414             zos.setUseZip64(Zip64Mode.Never);
415             createFirstEntry(zos).close();
416         }
417         final File file2 = createTempFile("src2.", ".zip");
418         try (ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(file2)) {
419             zos1.setUseZip64(Zip64Mode.Never);
420             createSecondEntry(zos1).close();
421         }
422         try (ZipFile zipFile1 = newZipFile(file1);
423                 ZipFile zipFile2 = newZipFile(file2)) {
424             final File fileResult = createTempFile("file-actual.", ".zip");
425             try (ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult)) {
426                 zipFile1.copyRawEntries(zos2, allFilesPredicate);
427                 zipFile2.copyRawEntries(zos2, allFilesPredicate);
428             }
429             // copyRawEntries does not add superfluous zip64 header like regular ZIP output stream
430             // does when using Zip64Mode.AsNeeded so all the source material has to be Zip64Mode.Never,
431             // if exact binary equality is to be achieved
432             assertSameFileContents(reference, fileResult);
433         }
434     }
435 
436     @Test
437     void testCopyRawZip64EntryFromFile() throws IOException {
438         final File reference = createTempFile("z64reference.", ".zip");
439         try (ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(reference)) {
440             zos1.setUseZip64(Zip64Mode.Always);
441             createFirstEntry(zos1);
442         }
443         final File file1 = createTempFile("zip64src.", ".zip");
444         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
445             zos.setUseZip64(Zip64Mode.Always);
446             createFirstEntry(zos).close();
447         }
448         final File fileResult = createTempFile("file-actual.", ".zip");
449         try (ZipFile zipFile1 = newZipFile(file1)) {
450             try (ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult)) {
451                 zos2.setUseZip64(Zip64Mode.Always);
452                 zipFile1.copyRawEntries(zos2, allFilesPredicate);
453             }
454             assertSameFileContents(reference, fileResult);
455         }
456     }
457 
458     @Test
459     void testDirectoryEntryFromFile() throws Exception {
460         final File tmp = getTempDirFile();
461         final File archive = createTempFile("test.", ".zip");
462         final long beforeArchiveWrite;
463         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(archive)) {
464             beforeArchiveWrite = tmp.lastModified();
465             final ZipArchiveEntry in = new ZipArchiveEntry(tmp, "foo");
466             zos.putArchiveEntry(in);
467             zos.closeArchiveEntry();
468         }
469         try (ZipFile zf = newZipFile(archive)) {
470             final ZipArchiveEntry out = zf.getEntry("foo/");
471             assertNotNull(out);
472             assertEquals("foo/", out.getName());
473             assertEquals(0, out.getSize());
474             // ZIP stores time with a granularity of 2 seconds
475             assertEquals(beforeArchiveWrite / 2000, out.getLastModifiedDate().getTime() / 2000);
476             assertTrue(out.isDirectory());
477         }
478     }
479 
480     @Test
481     void testExplicitDirectoryEntry() throws Exception {
482         final File archive = createTempFile("test.", ".zip");
483         final long beforeArchiveWrite;
484         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(archive)) {
485             beforeArchiveWrite = getTempDirFile().lastModified();
486             final ZipArchiveEntry in = new ZipArchiveEntry("foo/");
487             in.setTime(beforeArchiveWrite);
488             zos.putArchiveEntry(in);
489             zos.closeArchiveEntry();
490         }
491         try (ZipFile zf = newZipFile(archive)) {
492             final ZipArchiveEntry out = zf.getEntry("foo/");
493             assertNotNull(out);
494             assertEquals("foo/", out.getName());
495             assertEquals(0, out.getSize());
496             assertEquals(beforeArchiveWrite / 2000, out.getLastModifiedDate().getTime() / 2000);
497             assertTrue(out.isDirectory());
498         }
499     }
500 
501     @Test
502     void testExplicitFileEntry() throws Exception {
503         final File file = createTempFile();
504         final File archive = createTempFile("test.", ".zip");
505         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(archive)) {
506             final ZipArchiveEntry in = new ZipArchiveEntry("foo");
507             in.setTime(file.lastModified());
508             in.setSize(file.length());
509             outputStream.putArchiveEntry(in);
510             outputStream.write(file);
511             outputStream.closeArchiveEntry();
512         }
513         try (ZipFile zf = newZipFile(archive)) {
514             final ZipArchiveEntry out = zf.getEntry("foo");
515             assertNotNull(out);
516             assertEquals("foo", out.getName());
517             assertEquals(file.length(), out.getSize());
518             assertEquals(file.lastModified() / 2000, out.getLastModifiedDate().getTime() / 2000);
519             assertFalse(out.isDirectory());
520         }
521     }
522 
523     @Test
524     void testFileEntryFromFile() throws Exception {
525         final File file = createTempFile();
526         final File archive = createTempFile("test.", ".zip");
527         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(archive)) {
528             final ZipArchiveEntry in = new ZipArchiveEntry(file, "foo");
529             outputStream.putArchiveEntry(in);
530             outputStream.write(file);
531             outputStream.closeArchiveEntry();
532         }
533         try (ZipFile zf = newZipFile(archive)) {
534             final ZipArchiveEntry out = zf.getEntry("foo");
535             assertNotNull(out);
536             assertEquals("foo", out.getName());
537             assertEquals(file.length(), out.getSize());
538             assertEquals(file.lastModified() / 2000, out.getLastModifiedDate().getTime() / 2000);
539             assertFalse(out.isDirectory());
540         }
541     }
542 
543     private void testInputStreamStatistics(final String fileName, final Map<String, List<Long>> expectedStatistics) throws IOException, ArchiveException {
544         final File input = getFile(fileName);
545         final Map<String, List<List<Long>>> actualStatistics = new HashMap<>();
546         // stream access
547         try (InputStream fis = Files.newInputStream(input.toPath());
548                 ArchiveInputStream<?> in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", fis)) {
549             in.forEach(entry -> readStream(in, entry, actualStatistics));
550         }
551         // file access
552         try (ZipFile zipFile = newZipFile(input)) {
553             zipFile.stream().forEach(zae -> {
554                 try (InputStream in = zipFile.getInputStream(zae)) {
555                     readStream(in, zae, actualStatistics);
556                 }
557             });
558         }
559         // compare statistics of stream / file access
560         for (final Map.Entry<String, List<List<Long>>> me : actualStatistics.entrySet()) {
561             assertEquals(me.getValue().get(0), me.getValue().get(1), "Mismatch of stats for: " + me.getKey());
562         }
563         for (final Map.Entry<String, List<Long>> me : expectedStatistics.entrySet()) {
564             assertEquals(me.getValue(), actualStatistics.get(me.getKey()).get(0), "Mismatch of stats with expected value for: " + me.getKey());
565         }
566     }
567 
568     @Test
569     void testInputStreamStatisticsForBzip2Entry() throws IOException, ArchiveException {
570         final Map<String, List<Long>> expected = new HashMap<>();
571         expected.put("lots-of-as", Arrays.asList(42L, 39L));
572         testInputStreamStatistics("bzip2-zip.zip", expected);
573     }
574 
575     @Test
576     void testInputStreamStatisticsForDeflate64Entry() throws IOException, ArchiveException {
577         final Map<String, List<Long>> expected = new HashMap<>();
578         expected.put("input2", Arrays.asList(3072L, 2111L));
579         testInputStreamStatistics("COMPRESS-380/COMPRESS-380.zip", expected);
580     }
581 
582     @Test
583     void testInputStreamStatisticsForImplodedEntry() throws IOException, ArchiveException {
584         final Map<String, List<Long>> expected = new HashMap<>();
585         expected.put("LICENSE.TXT", Arrays.asList(11560L, 4131L));
586         testInputStreamStatistics("imploding-8Kdict-3trees.zip", expected);
587     }
588 
589     @Test
590     void testInputStreamStatisticsForShrunkEntry() throws IOException, ArchiveException {
591         final Map<String, List<Long>> expected = new HashMap<>();
592         expected.put("TEST1.XML", Arrays.asList(76L, 66L));
593         expected.put("TEST2.XML", Arrays.asList(81L, 76L));
594         testInputStreamStatistics("SHRUNK.ZIP", expected);
595     }
596 
597     @Test
598     void testInputStreamStatisticsForStoredEntry() throws IOException, ArchiveException {
599         final Map<String, List<Long>> expected = new HashMap<>();
600         expected.put("test.txt", Arrays.asList(5L, 5L));
601         testInputStreamStatistics("COMPRESS-264.zip", expected);
602     }
603 
604     @Test
605     void testInputStreamStatisticsOfZipBombExcel() throws IOException, ArchiveException {
606         final Map<String, List<Long>> expected = new HashMap<>();
607         expected.put("[Content_Types].xml", Arrays.asList(8390036L, 8600L));
608         expected.put("xl/worksheets/sheet1.xml", Arrays.asList(1348L, 508L));
609         testInputStreamStatistics("zipbomb.xlsx", expected);
610     }
611 
612     /**
613      * Checks if all entries from a nested archive can be read. The archive: OSX_ArchiveWithNestedArchive.zip contains: NestedArchiv.zip and test.xml3.
614      *
615      * The nested archive: NestedArchive.zip contains test1.xml and test2.xml
616      *
617      * @throws Exception
618      */
619     @Test
620     void testListAllFilesWithNestedArchive() throws Exception {
621         final File input = getFile("OSX_ArchiveWithNestedArchive.zip");
622         final List<String> results = new ArrayList<>();
623         final List<ZipException> expectedExceptions = new ArrayList<>();
624         try (InputStream fis = Files.newInputStream(input.toPath());
625                 ZipArchiveInputStream in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", fis)) {
626             ZipArchiveEntry entry;
627             while ((entry = in.getNextEntry()) != null) {
628                 results.add(entry.getName());
629                 final ZipArchiveInputStream nestedIn = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", in);
630                 try {
631                     ZipArchiveEntry nestedEntry;
632                     while ((nestedEntry = nestedIn.getNextEntry()) != null) {
633                         results.add(nestedEntry.getName());
634                     }
635                 } catch (final ZipException ex) {
636                     // expected since you cannot create a final ArchiveInputStream from test3.xml
637                     expectedExceptions.add(ex);
638                 }
639                 // nested stream must not be closed here
640             }
641         }
642         assertTrue(results.contains("NestedArchiv.zip"));
643         assertTrue(results.contains("test1.xml"));
644         assertTrue(results.contains("test2.xml"));
645         assertTrue(results.contains("test3.xml"));
646         assertEquals(1, expectedExceptions.size());
647     }
648 
649     /**
650      * Test case for being able to skip an entry in an {@link ZipArchiveInputStream} even if the compression method of that entry is unsupported.
651      *
652      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93">COMPRESS-93</a>
653      */
654     @Test
655     void testSkipEntryWithUnsupportedCompressionMethod() throws IOException {
656         try (ZipArchiveInputStream zip = new ZipArchiveInputStream(newInputStream("moby.zip"))) {
657             final ZipArchiveEntry entry = zip.getNextZipEntry();
658             assertEquals(ZipMethod.TOKENIZATION.getCode(), entry.getMethod(), "method");
659             assertEquals("README", entry.getName());
660             assertFalse(zip.canReadEntryData(entry));
661             assertDoesNotThrow(() -> assertNull(zip.getNextZipEntry()), "COMPRESS-93: Unable to skip an unsupported ZIP entry");
662         }
663     }
664 
665     /**
666      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-208">COMPRESS-208</a>.
667      */
668     @Test
669     void testSkipsPK00Prefix() throws Exception {
670         final File input = getFile("COMPRESS-208.zip");
671         final ArrayList<String> al = new ArrayList<>();
672         al.add("test1.xml");
673         al.add("test2.xml");
674         try (InputStream fis = Files.newInputStream(input.toPath());
675                 ZipArchiveInputStream inputStream = new ZipArchiveInputStream(fis)) {
676             checkArchiveContent(inputStream, al);
677         }
678     }
679 
680     /**
681      * Test case for <a href="https://issues.apache.org/jira/browse/COMPRESS-93">COMPRESS-93</a>.
682      */
683     @Test
684     void testTokenizationCompressionMethod() throws IOException {
685         try (ZipFile moby = ZipFile.builder().setFile(getFile("moby.zip")).get()) {
686             final ZipArchiveEntry entry = moby.getEntry("README");
687             assertEquals(ZipMethod.TOKENIZATION.getCode(), entry.getMethod(), "method");
688             assertFalse(moby.canReadEntryData(entry));
689         }
690     }
691 
692     @Test
693     void testUnixModeInAddRaw() throws IOException {
694         final File file1 = createTempFile("unixModeBits.", ".zip");
695         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
696             final ZipArchiveEntry archiveEntry = new ZipArchiveEntry("fred");
697             archiveEntry.setUnixMode(0664);
698             archiveEntry.setMethod(ZipEntry.STORED);
699             archiveEntry.setSize(3);
700             archiveEntry.setCompressedSize(3);
701             zos.addRawArchiveEntry(archiveEntry, new ByteArrayInputStream("fud".getBytes()));
702         }
703         try (ZipFile zf1 = newZipFile(file1)) {
704             final ZipArchiveEntry fred = zf1.getEntry("fred");
705             assertEquals(0664, fred.getUnixMode());
706         }
707     }
708 
709     @Test
710     void testUnsupportedCompressionMethodInAddRaw() throws IOException {
711         final File file1 = createTempFile("unsupportedCompressionMethod.", ".zip");
712         try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
713             final ZipArchiveEntry archiveEntry = new ZipArchiveEntry("fred");
714             archiveEntry.setMethod(Integer.MAX_VALUE);
715             archiveEntry.setSize(3);
716             archiveEntry.setCompressedSize(3);
717             archiveEntry.setCrc(0);
718             zos.addRawArchiveEntry(archiveEntry, new ByteArrayInputStream("fud".getBytes()));
719         }
720     }
721 
722     /**
723      * Archives 2 files and unarchives it again. If the file length of result and source is the same, it looks like the operations have worked
724      *
725      * @throws Exception
726      */
727     @Test
728     void testZipArchiveCreation() throws Exception {
729         // Archive
730         final File output = newTempFile("bla.zip");
731         final File file1 = getFile("test1.xml");
732         final File file2 = getFile("test2.xml");
733         try (OutputStream out = Files.newOutputStream(output.toPath())) {
734             try (ArchiveOutputStream<ZipArchiveEntry> os = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream("zip", out)) {
735                 // entry 1
736                 os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
737                 os.write(file1);
738                 os.closeArchiveEntry();
739                 // entry 2
740                 os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
741                 os.write(file2);
742                 os.closeArchiveEntry();
743             }
744         }
745         // Unarchive the same
746         final List<File> results = new ArrayList<>();
747         try (InputStream fileInputStream = Files.newInputStream(output.toPath())) {
748             try (ArchiveInputStream<ZipArchiveEntry> archiveInputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", fileInputStream)) {
749                 archiveInputStream.forEach(entry -> {
750                     final File outfile = new File(tempResultDir.getCanonicalPath() + "/result/" + entry.getName());
751                     outfile.getParentFile().mkdirs();
752                     Files.copy(archiveInputStream, outfile.toPath());
753                     results.add(outfile);
754                 });
755             }
756         }
757         assertEquals(results.size(), 2);
758         File result = results.get(0);
759         assertEquals(file1.length(), result.length());
760         result = results.get(1);
761         assertEquals(file2.length(), result.length());
762     }
763 
764     /**
765      * Archives 2 files and unarchives it again. If the file contents of result and source is the same, it looks like the operations have worked
766      *
767      * @throws Exception
768      */
769     @Test
770     void testZipArchiveCreationInMemory() throws Exception {
771         final byte[] file1Contents = readAllBytes("test1.xml");
772         final byte[] file2Contents = readAllBytes("test2.xml");
773         final List<byte[]> results = new ArrayList<>();
774         try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel()) {
775             try (ZipArchiveOutputStream os = new ZipArchiveOutputStream(channel)) {
776                 os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
777                 os.write(file1Contents);
778                 os.closeArchiveEntry();
779 
780                 os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
781                 os.write(file2Contents);
782                 os.closeArchiveEntry();
783             }
784             // Unarchive the same
785             try (ZipArchiveInputStream inputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", new ByteArrayInputStream(channel.array()))) {
786                 ZipArchiveEntry entry;
787                 while ((entry = inputStream.getNextEntry()) != null) {
788                     final byte[] result = new byte[(int) entry.getSize()];
789                     IOUtils.readFully(inputStream, result);
790                     results.add(result);
791                 }
792             }
793         }
794         assertArrayEquals(results.get(0), file1Contents);
795         assertArrayEquals(results.get(1), file2Contents);
796     }
797 
798     @Test
799     void testZipArchiveEntryNewFromPath() throws Exception {
800         final Path archivePath;
801         final File tmpFile = createTempFile();
802         final Path tmpFilePath = tmpFile.toPath();
803         final File archiveFile = createTempFile("test.", ".zip");
804         archivePath = archiveFile.toPath();
805         try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(archivePath)) {
806             final ZipArchiveEntry in = outputStream.createArchiveEntry(tmpFilePath, "foo");
807             outputStream.putArchiveEntry(in);
808             outputStream.write(tmpFilePath);
809             outputStream.closeArchiveEntry();
810         }
811         try (ZipFile zf = newZipFile(archiveFile)) {
812             final ZipArchiveEntry out = zf.getEntry("foo");
813             assertNotNull(out);
814             assertEquals("foo", out.getName());
815             assertEquals(tmpFile.length(), out.getSize());
816             assertEquals(tmpFile.lastModified() / 2000, out.getLastModifiedDate().getTime() / 2000);
817             assertFalse(out.isDirectory());
818         }
819     }
820 
821     /**
822      * Simple unarchive test. Asserts nothing.
823      *
824      * @throws Exception
825      */
826     @Test
827     void testZipUnarchive() throws Exception {
828         final File input = getFile("bla.zip");
829         try (InputStream is = Files.newInputStream(input.toPath());
830                 ArchiveInputStream<ZipArchiveEntry> in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", is)) {
831             final ZipArchiveEntry entry = in.getNextEntry();
832             Files.copy(in, newTempFile(entry.getName()).toPath());
833         }
834     }
835 }