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