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 org.junit.Assert.*;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream; 	
26  import java.io.FileOutputStream; 	
27  import java.io.IOException; 	
28  import java.io.InputStream; 	
29  import java.io.OutputStream;
30  import java.util.ArrayList;
31  import java.util.Enumeration;
32  import java.util.List;
33  import java.util.zip.ZipEntry;
34  import java.util.zip.ZipException;
35  
36  import org.apache.commons.compress.AbstractTestCase;
37  import org.apache.commons.compress.archivers.zip.Zip64Mode;
38  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
39  import org.apache.commons.compress.archivers.zip.ZipArchiveEntryPredicate;
40  import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; 	
41  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; 	
42  import org.apache.commons.compress.archivers.zip.ZipFile; 	
43  import org.apache.commons.compress.archivers.zip.ZipMethod;
44  import org.apache.commons.compress.utils.IOUtils;
45  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
46  import org.junit.Assert;
47  import org.junit.Test;
48  
49  public final class ZipTestCase extends AbstractTestCase {
50      /**
51       * Archives 2 files and unarchives it again. If the file length of result
52       * and source is the same, it looks like the operations have worked
53       * @throws Exception
54       */
55      @Test
56      public void testZipArchiveCreation() throws Exception {
57          // Archive
58          final File output = new File(dir, "bla.zip");
59          final File file1 = getFile("test1.xml");
60          final File file2 = getFile("test2.xml");
61  
62          final OutputStream out = new FileOutputStream(output);
63          ArchiveOutputStream os = null;
64          try {
65              os = new ArchiveStreamFactory()
66                  .createArchiveOutputStream("zip", out);
67              os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
68              IOUtils.copy(new FileInputStream(file1), os);
69              os.closeArchiveEntry();
70  
71              os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
72              IOUtils.copy(new FileInputStream(file2), os);
73              os.closeArchiveEntry();
74          } finally {
75              if (os != null) {
76                  os.close();
77              }
78          }
79          out.close();
80  
81          // Unarchive the same
82          final List<File> results = new ArrayList<>();
83  
84          final InputStream is = new FileInputStream(output);
85          ArchiveInputStream in = null;
86          try {
87              in = new ArchiveStreamFactory()
88                  .createArchiveInputStream("zip", is);
89  
90              ZipArchiveEntry entry = null;
91              while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) {
92                  final File outfile = new File(resultDir.getCanonicalPath() + "/result/" + entry.getName());
93                  outfile.getParentFile().mkdirs();
94                  try (OutputStream o = new FileOutputStream(outfile)) {
95                      IOUtils.copy(in, o);
96                  }
97                  results.add(outfile);
98              }
99          } finally {
100             if (in != null) {
101                 in.close();
102             }
103         }
104         is.close();
105 
106         assertEquals(results.size(), 2);
107         File result = results.get(0);
108         assertEquals(file1.length(), result.length());
109         result = results.get(1);
110         assertEquals(file2.length(), result.length());
111     }
112 
113     /**
114      * Archives 2 files and unarchives it again. If the file contents of result
115      * and source is the same, it looks like the operations have worked
116      * @throws Exception
117      */
118     @Test
119     public void testZipArchiveCreationInMemory() throws Exception {
120         final File file1 = getFile("test1.xml");
121         final File file2 = getFile("test2.xml");
122         final byte[] file1Contents = new byte[(int) file1.length()];
123         final byte[] file2Contents = new byte[(int) file2.length()];
124         IOUtils.readFully(new FileInputStream(file1), file1Contents);
125         IOUtils.readFully(new FileInputStream(file2), file2Contents);
126 
127         SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel();
128         try (ZipArchiveOutputStream os = new ZipArchiveOutputStream(c)) {
129             os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
130             os.write(file1Contents);
131             os.closeArchiveEntry();
132 
133             os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
134             os.write(file2Contents);
135             os.closeArchiveEntry();
136         }
137 
138         // Unarchive the same
139         final List<byte[]> results = new ArrayList<>();
140 
141         try (ArchiveInputStream in = new ArchiveStreamFactory()
142              .createArchiveInputStream("zip", new ByteArrayInputStream(c.array()))) {
143 
144             ZipArchiveEntry entry;
145             while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) {
146                 byte[] result = new byte[(int) entry.getSize()];
147                 IOUtils.readFully(in, result);
148                 results.add(result);
149             }
150         }
151 
152         assertArrayEquals(results.get(0), file1Contents);
153         assertArrayEquals(results.get(1), file2Contents);
154     }
155 
156     /**
157      * Simple unarchive test. Asserts nothing.
158      * @throws Exception
159      */
160     @Test
161     public void testZipUnarchive() throws Exception {
162         final File input = getFile("bla.zip");
163         final InputStream is = new FileInputStream(input);
164         final ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream("zip", is);
165         final ZipArchiveEntry entry = (ZipArchiveEntry)in.getNextEntry();
166         final OutputStream out = new FileOutputStream(new File(dir, entry.getName()));
167         IOUtils.copy(in, out);
168         out.close();
169         in.close();
170     }
171 
172     /**
173      * Test case for 
174      * <a href="https://issues.apache.org/jira/browse/COMPRESS-208"
175      * >COMPRESS-208</a>.
176      */
177     @Test
178     public void testSkipsPK00Prefix() throws Exception {
179         final File input = getFile("COMPRESS-208.zip");
180         final ArrayList<String> al = new ArrayList<>();
181         al.add("test1.xml");
182         al.add("test2.xml");
183         try (InputStream is = new FileInputStream(input)) {
184             checkArchiveContent(new ZipArchiveInputStream(is), al);
185         }
186     }
187 
188     /**
189      * Test case for
190      * <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
191      * >COMPRESS-93</a>.
192      */
193     @Test
194     public void testSupportedCompressionMethod() throws IOException {
195         /*
196         ZipFile bla = new ZipFile(getFile("bla.zip"));
197         assertTrue(bla.canReadEntryData(bla.getEntry("test1.xml")));
198         bla.close();
199         */
200         
201         final ZipFile moby = new ZipFile(getFile("moby.zip"));
202         final ZipArchiveEntry entry = moby.getEntry("README");
203         assertEquals("method", ZipMethod.TOKENIZATION.getCode(), entry.getMethod());
204         assertFalse(moby.canReadEntryData(entry));
205         moby.close();
206     }
207 
208     /**
209      * Test case for being able to skip an entry in an 
210      * {@link ZipArchiveInputStream} even if the compression method of that
211      * entry is unsupported.
212      *
213      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
214      *        >COMPRESS-93</a>
215      */
216     @Test
217     public void testSkipEntryWithUnsupportedCompressionMethod()
218             throws IOException {
219         try (ZipArchiveInputStream zip = new ZipArchiveInputStream(new FileInputStream(getFile("moby.zip")))) {
220             final ZipArchiveEntry entry = zip.getNextZipEntry();
221             assertEquals("method", ZipMethod.TOKENIZATION.getCode(), entry.getMethod());
222             assertEquals("README", entry.getName());
223             assertFalse(zip.canReadEntryData(entry));
224             try {
225                 assertNull(zip.getNextZipEntry());
226             } catch (final IOException e) {
227                 e.printStackTrace();
228                 fail("COMPRESS-93: Unable to skip an unsupported zip entry");
229             }
230         }
231     }
232 
233     /**
234      * Checks if all entries from a nested archive can be read.
235      * The archive: OSX_ArchiveWithNestedArchive.zip contains:
236      * NestedArchiv.zip and test.xml3.
237      * 
238      * The nested archive:  NestedArchive.zip contains test1.xml and test2.xml
239      * 
240      * @throws Exception
241      */
242     @Test
243     public void testListAllFilesWithNestedArchive() throws Exception {
244         final File input = getFile("OSX_ArchiveWithNestedArchive.zip");
245 
246         final List<String> results = new ArrayList<>();
247         final List<ZipException> expectedExceptions = new ArrayList<>();
248 
249         final InputStream is = new FileInputStream(input);
250         ArchiveInputStream in = null;
251         try {
252             in = new ArchiveStreamFactory().createArchiveInputStream("zip", is);
253 
254             ZipArchiveEntry entry = null;
255             while ((entry = (ZipArchiveEntry) in.getNextEntry()) != null) {
256                 results.add(entry.getName());
257 
258                 final ArchiveInputStream nestedIn = new ArchiveStreamFactory().createArchiveInputStream("zip", in);
259                 try {
260                     ZipArchiveEntry nestedEntry = null;
261                     while ((nestedEntry = (ZipArchiveEntry) nestedIn.getNextEntry()) != null) {
262                         results.add(nestedEntry.getName());
263                     }
264                 } catch (ZipException ex) {
265                     // expected since you cannot create a final ArchiveInputStream from test3.xml
266                     expectedExceptions.add(ex);
267                 }
268                 // nested stream must not be closed here
269             }
270         } finally {
271             if (in != null) {
272                 in.close();
273             }
274         }
275         is.close();
276 
277         assertTrue(results.contains("NestedArchiv.zip"));
278         assertTrue(results.contains("test1.xml"));
279         assertTrue(results.contains("test2.xml"));
280         assertTrue(results.contains("test3.xml"));
281         assertEquals(1, expectedExceptions.size());
282     }
283 
284     @Test
285     public void testDirectoryEntryFromFile() throws Exception {
286         final File[] tmp = createTempDirAndFile();
287         File archive = null;
288         ZipArchiveOutputStream zos = null;
289         ZipFile zf = null;
290         try {
291             archive = File.createTempFile("test.", ".zip", tmp[0]);
292             archive.deleteOnExit();
293             zos = new ZipArchiveOutputStream(archive);
294             final long beforeArchiveWrite = tmp[0].lastModified();
295             final ZipArchiveEntry in = new ZipArchiveEntry(tmp[0], "foo");
296             zos.putArchiveEntry(in);
297             zos.closeArchiveEntry();
298             zos.close();
299             zos = null;
300             zf = new ZipFile(archive);
301             final ZipArchiveEntry out = zf.getEntry("foo/");
302             assertNotNull(out);
303             assertEquals("foo/", out.getName());
304             assertEquals(0, out.getSize());
305             // ZIP stores time with a granularity of 2 seconds
306             assertEquals(beforeArchiveWrite / 2000,
307                          out.getLastModifiedDate().getTime() / 2000);
308             assertTrue(out.isDirectory());
309         } finally {
310             ZipFile.closeQuietly(zf);
311             if (zos != null) {
312                 zos.close();
313             }
314             tryHardToDelete(archive);
315             tryHardToDelete(tmp[1]);
316             rmdir(tmp[0]);
317         }
318     }
319 
320     @Test
321     public void testExplicitDirectoryEntry() throws Exception {
322         final File[] tmp = createTempDirAndFile();
323         File archive = null;
324         ZipArchiveOutputStream zos = null;
325         ZipFile zf = null;
326         try {
327             archive = File.createTempFile("test.", ".zip", tmp[0]);
328             archive.deleteOnExit();
329             zos = new ZipArchiveOutputStream(archive);
330             final long beforeArchiveWrite = tmp[0].lastModified();
331             final ZipArchiveEntry in = new ZipArchiveEntry("foo/");
332             in.setTime(beforeArchiveWrite);
333             zos.putArchiveEntry(in);
334             zos.closeArchiveEntry();
335             zos.close();
336             zos = null;
337             zf = new ZipFile(archive);
338             final ZipArchiveEntry out = zf.getEntry("foo/");
339             assertNotNull(out);
340             assertEquals("foo/", out.getName());
341             assertEquals(0, out.getSize());
342             assertEquals(beforeArchiveWrite / 2000,
343                          out.getLastModifiedDate().getTime() / 2000);
344             assertTrue(out.isDirectory());
345         } finally {
346             ZipFile.closeQuietly(zf);
347             if (zos != null) {
348                 zos.close();
349             }
350             tryHardToDelete(archive);
351             tryHardToDelete(tmp[1]);
352             rmdir(tmp[0]);
353         }
354     }
355     String first_payload = "ABBA";
356     String second_payload = "AAAAAAAAAAAA";
357     ZipArchiveEntryPredicate allFilesPredicate = new ZipArchiveEntryPredicate() {
358         @Override
359         public boolean test(final ZipArchiveEntry zipArchiveEntry) {
360             return true;
361         }
362     };
363 
364     @Test
365     public void testCopyRawEntriesFromFile()
366             throws IOException {
367 
368         final File[] tmp = createTempDirAndFile();
369         final File reference = createReferenceFile(tmp[0], Zip64Mode.Never, "expected.");
370 
371         final File a1 = File.createTempFile("src1.", ".zip", tmp[0]);
372         final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1);
373         zos.setUseZip64(Zip64Mode.Never);
374         createFirstEntry(zos).close();
375 
376         final File a2 = File.createTempFile("src2.", ".zip", tmp[0]);
377         final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(a2);
378         zos1.setUseZip64(Zip64Mode.Never);
379         createSecondEntry(zos1).close();
380 
381         final ZipFile zf1 = new ZipFile(a1);
382         final ZipFile zf2 = new ZipFile(a2);
383         final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]);
384         final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult);
385         zf1.copyRawEntries(zos2, allFilesPredicate);
386         zf2.copyRawEntries(zos2, allFilesPredicate);
387         zos2.close();
388         // copyRawEntries does not add superfluous zip64 header like regular zip output stream
389         // does when using Zip64Mode.AsNeeded so all the source material has to be Zip64Mode.Never,
390         // if exact binary equality is to be achieved
391         assertSameFileContents(reference, fileResult);
392         zf1.close();
393         zf2.close();
394     }
395 
396     @Test
397     public void testCopyRawZip64EntryFromFile()
398             throws IOException {
399 
400         final File[] tmp = createTempDirAndFile();
401         final File reference = File.createTempFile("z64reference.", ".zip", tmp[0]);
402         final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(reference);
403         zos1.setUseZip64(Zip64Mode.Always);
404         createFirstEntry(zos1);
405         zos1.close();
406 
407         final File a1 = File.createTempFile("zip64src.", ".zip", tmp[0]);
408         final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1);
409         zos.setUseZip64(Zip64Mode.Always);
410         createFirstEntry(zos).close();
411 
412         final ZipFile zf1 = new ZipFile(a1);
413         final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]);
414         final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult);
415         zos2.setUseZip64(Zip64Mode.Always);
416         zf1.copyRawEntries(zos2, allFilesPredicate);
417         zos2.close();
418         assertSameFileContents(reference, fileResult);
419         zf1.close();
420     }
421 
422     @Test
423     public void testUnixModeInAddRaw() throws IOException {
424 
425         final File[] tmp = createTempDirAndFile();
426 
427         final File a1 = File.createTempFile("unixModeBits.", ".zip", tmp[0]);
428         final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1);
429 
430         final ZipArchiveEntry archiveEntry = new ZipArchiveEntry("fred");
431         archiveEntry.setUnixMode(0664);
432         archiveEntry.setMethod(ZipEntry.DEFLATED);
433         zos.addRawArchiveEntry(archiveEntry, new ByteArrayInputStream("fud".getBytes()));
434         zos.close();
435 
436         final ZipFile zf1 = new ZipFile(a1);
437         final ZipArchiveEntry fred = zf1.getEntry("fred");
438         assertEquals(0664, fred.getUnixMode());
439         zf1.close();
440     }
441 
442     private File createReferenceFile(final File directory, final Zip64Mode zipMode, final String prefix) throws IOException {
443         final File reference = File.createTempFile(prefix, ".zip", directory);
444         final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(reference);
445         zos.setUseZip64(zipMode);
446         createFirstEntry(zos);
447         createSecondEntry(zos);
448         zos.close();
449         return reference;
450     }
451 
452     private ZipArchiveOutputStream createFirstEntry(final ZipArchiveOutputStream zos) throws IOException {
453         createArchiveEntry(first_payload, zos, "file1.txt");
454         return zos;
455     }
456 
457     private ZipArchiveOutputStream createSecondEntry(final ZipArchiveOutputStream zos) throws IOException {
458         createArchiveEntry(second_payload, zos, "file2.txt");
459         return zos;
460     }
461 
462 
463     private void assertSameFileContents(final File expectedFile, final File actualFile) throws IOException {
464         final int size = (int) Math.max(expectedFile.length(), actualFile.length());
465         final ZipFile expected = new ZipFile(expectedFile);
466         final ZipFile actual = new ZipFile(actualFile);
467         final byte[] expectedBuf = new byte[size];
468         final byte[] actualBuf = new byte[size];
469 
470         final Enumeration<ZipArchiveEntry> actualInOrder = actual.getEntriesInPhysicalOrder();
471         final Enumeration<ZipArchiveEntry> expectedInOrder = expected.getEntriesInPhysicalOrder();
472 
473         while (actualInOrder.hasMoreElements()){
474             final ZipArchiveEntry actualElement = actualInOrder.nextElement();
475             final ZipArchiveEntry expectedElement = expectedInOrder.nextElement();
476             assertEquals( expectedElement.getName(), actualElement.getName());
477             // Don't compare timestamps since they may vary;
478             // there's no support for stubbed out clock (TimeSource) in ZipArchiveOutputStream
479             assertEquals( expectedElement.getMethod(), actualElement.getMethod());
480             assertEquals( expectedElement.getGeneralPurposeBit(), actualElement.getGeneralPurposeBit());
481             assertEquals( expectedElement.getCrc(), actualElement.getCrc());
482             assertEquals( expectedElement.getCompressedSize(), actualElement.getCompressedSize());
483             assertEquals( expectedElement.getSize(), actualElement.getSize());
484             assertEquals( expectedElement.getExternalAttributes(), actualElement.getExternalAttributes());
485             assertEquals( expectedElement.getInternalAttributes(), actualElement.getInternalAttributes());
486 
487             final InputStream actualIs = actual.getInputStream(actualElement);
488             final InputStream expectedIs = expected.getInputStream(expectedElement);
489             IOUtils.readFully(expectedIs, expectedBuf);
490             IOUtils.readFully(actualIs, actualBuf);
491             expectedIs.close();
492             actualIs.close();
493             Assert.assertArrayEquals(expectedBuf, actualBuf); // Buffers are larger than payload. dont care
494         }
495 
496         expected.close();
497         actual.close();
498     }
499 
500 
501     private void createArchiveEntry(final String payload, final ZipArchiveOutputStream zos, final String name)
502             throws IOException {
503         final ZipArchiveEntry in = new ZipArchiveEntry(name);
504         zos.putArchiveEntry(in);
505 
506         zos.write(payload.getBytes());
507         zos.closeArchiveEntry();
508     }
509 
510     @Test
511     public void testFileEntryFromFile() throws Exception {
512         final File[] tmp = createTempDirAndFile();
513         File archive = null;
514         ZipArchiveOutputStream zos = null;
515         ZipFile zf = null;
516         FileInputStream fis = null;
517         try {
518             archive = File.createTempFile("test.", ".zip", tmp[0]);
519             archive.deleteOnExit();
520             zos = new ZipArchiveOutputStream(archive);
521             final ZipArchiveEntry in = new ZipArchiveEntry(tmp[1], "foo");
522             zos.putArchiveEntry(in);
523             final byte[] b = new byte[(int) tmp[1].length()];
524             fis = new FileInputStream(tmp[1]);
525             while (fis.read(b) > 0) {
526                 zos.write(b);
527             }
528             fis.close();
529             fis = null;
530             zos.closeArchiveEntry();
531             zos.close();
532             zos = null;
533             zf = new ZipFile(archive);
534             final ZipArchiveEntry out = zf.getEntry("foo");
535             assertNotNull(out);
536             assertEquals("foo", out.getName());
537             assertEquals(tmp[1].length(), out.getSize());
538             assertEquals(tmp[1].lastModified() / 2000,
539                          out.getLastModifiedDate().getTime() / 2000);
540             assertFalse(out.isDirectory());
541         } finally {
542             ZipFile.closeQuietly(zf);
543             if (zos != null) {
544                 zos.close();
545             }
546             tryHardToDelete(archive);
547             if (fis != null) {
548                 fis.close();
549             }
550             tryHardToDelete(tmp[1]);
551             rmdir(tmp[0]);
552         }
553     }
554 
555     @Test
556     public void testExplicitFileEntry() throws Exception {
557         final File[] tmp = createTempDirAndFile();
558         File archive = null;
559         ZipArchiveOutputStream zos = null;
560         ZipFile zf = null;
561         FileInputStream fis = null;
562         try {
563             archive = File.createTempFile("test.", ".zip", tmp[0]);
564             archive.deleteOnExit();
565             zos = new ZipArchiveOutputStream(archive);
566             final ZipArchiveEntry in = new ZipArchiveEntry("foo");
567             in.setTime(tmp[1].lastModified());
568             in.setSize(tmp[1].length());
569             zos.putArchiveEntry(in);
570             final byte[] b = new byte[(int) tmp[1].length()];
571             fis = new FileInputStream(tmp[1]);
572             while (fis.read(b) > 0) {
573                 zos.write(b);
574             }
575             fis.close();
576             fis = null;
577             zos.closeArchiveEntry();
578             zos.close();
579             zos = null;
580             zf = new ZipFile(archive);
581             final ZipArchiveEntry out = zf.getEntry("foo");
582             assertNotNull(out);
583             assertEquals("foo", out.getName());
584             assertEquals(tmp[1].length(), out.getSize());
585             assertEquals(tmp[1].lastModified() / 2000,
586                          out.getLastModifiedDate().getTime() / 2000);
587             assertFalse(out.isDirectory());
588         } finally {
589             ZipFile.closeQuietly(zf);
590             if (zos != null) {
591                 zos.close();
592             }
593             tryHardToDelete(archive);
594             if (fis != null) {
595                 fis.close();
596             }
597             tryHardToDelete(tmp[1]);
598             rmdir(tmp[0]);
599         }
600     }
601 }