View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   *
17   */
18  
19  package org.apache.commons.compress.archivers.zip;
20  
21  import static org.apache.commons.compress.AbstractTestCase.getFile;
22  import static org.junit.Assert.assertArrayEquals;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertFalse;
25  import static org.junit.Assert.assertTrue;
26  import static org.junit.Assert.fail;
27  
28  import java.io.BufferedInputStream;
29  import java.io.ByteArrayInputStream;
30  import java.io.EOFException;
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.util.Arrays;
36  import java.util.zip.ZipException;
37  
38  import org.apache.commons.compress.archivers.ArchiveEntry;
39  import org.apache.commons.compress.utils.IOUtils;
40  import org.junit.Assert;
41  import org.junit.Test;
42  
43  public class ZipArchiveInputStreamTest {
44  
45      /**
46       * @see "https://issues.apache.org/jira/browse/COMPRESS-176"
47       */
48      @Test
49      public void winzipBackSlashWorkaround() throws Exception {
50          ZipArchiveInputStream in = null;
51          try {
52              in = new ZipArchiveInputStream(new FileInputStream(getFile("test-winzip.zip")));
53              ZipArchiveEntry zae = in.getNextZipEntry();
54              zae = in.getNextZipEntry();
55              zae = in.getNextZipEntry();
56              assertEquals("\u00e4/", zae.getName());
57          } finally {
58              if (in != null) {
59                  in.close();
60              }
61          }
62      }
63  
64      /**
65       * @see "https://issues.apache.org/jira/browse/COMPRESS-189"
66       */
67      @Test
68      public void properUseOfInflater() throws Exception {
69          ZipFile zf = null;
70          ZipArchiveInputStream in = null;
71          try {
72              zf = new ZipFile(getFile("COMPRESS-189.zip"));
73              final ZipArchiveEntry zae = zf.getEntry("USD0558682-20080101.ZIP");
74              in = new ZipArchiveInputStream(new BufferedInputStream(zf.getInputStream(zae)));
75              ZipArchiveEntry innerEntry;
76              while ((innerEntry = in.getNextZipEntry()) != null) {
77                  if (innerEntry.getName().endsWith("XML")) {
78                      assertTrue(0 < in.read());
79                  }
80              }
81          } finally {
82              if (zf != null) {
83                  zf.close();
84              }
85              if (in != null) {
86                  in.close();
87              }
88          }
89      }
90  
91      @Test
92      public void shouldConsumeArchiveCompletely() throws Exception {
93          final InputStream is = ZipArchiveInputStreamTest.class
94              .getResourceAsStream("/archive_with_trailer.zip");
95          final ZipArchiveInputStream zip = new ZipArchiveInputStream(is);
96          while (zip.getNextZipEntry() != null) {
97              // just consume the archive
98          }
99          final byte[] expected = new byte[] {
100             'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n'
101         };
102         final byte[] actual = new byte[expected.length];
103         is.read(actual);
104         assertArrayEquals(expected, actual);
105         zip.close();
106     }
107 
108     /**
109      * @see "https://issues.apache.org/jira/browse/COMPRESS-219"
110      */
111     @Test
112     public void shouldReadNestedZip() throws IOException {
113         ZipArchiveInputStream in = null;
114         try {
115             in = new ZipArchiveInputStream(new FileInputStream(getFile("COMPRESS-219.zip")));
116             extractZipInputStream(in);
117         } finally {
118             if (in != null) {
119                 in.close();
120             }
121         }
122     }
123 
124     private void extractZipInputStream(final ZipArchiveInputStream in)
125         throws IOException {
126         ZipArchiveEntry zae = in.getNextZipEntry();
127         while (zae != null) {
128             if (zae.getName().endsWith(".zip")) {
129                 extractZipInputStream(new ZipArchiveInputStream(in));
130             }
131             zae = in.getNextZipEntry();
132         }
133     }
134 
135     @Test
136     public void testUnshrinkEntry() throws Exception {
137         final ZipArchiveInputStream in = new ZipArchiveInputStream(new FileInputStream(getFile("SHRUNK.ZIP")));
138 
139         ZipArchiveEntry entry = in.getNextZipEntry();
140         assertEquals("method", ZipMethod.UNSHRINKING.getCode(), entry.getMethod());
141         assertTrue(in.canReadEntryData(entry));
142 
143         FileInputStream original = new FileInputStream(getFile("test1.xml"));
144         try {
145             assertArrayEquals(IOUtils.toByteArray(original), IOUtils.toByteArray(in));
146         } finally {
147             original.close();
148         }
149 
150         entry = in.getNextZipEntry();
151         assertEquals("method", ZipMethod.UNSHRINKING.getCode(), entry.getMethod());
152         assertTrue(in.canReadEntryData(entry));
153 
154         original = new FileInputStream(getFile("test2.xml"));
155         try {
156             assertArrayEquals(IOUtils.toByteArray(original), IOUtils.toByteArray(in));
157         } finally {
158             original.close();
159         }
160     }
161 
162 
163     /**
164      * Test case for
165      * <a href="https://issues.apache.org/jira/browse/COMPRESS-264"
166      * >COMPRESS-264</a>.
167      */
168     @Test
169     public void testReadingOfFirstStoredEntry() throws Exception {
170 
171         try (ZipArchiveInputStream in = new ZipArchiveInputStream(new FileInputStream(getFile("COMPRESS-264.zip")))) {
172             final ZipArchiveEntry ze = in.getNextZipEntry();
173             assertEquals(5, ze.getSize());
174             assertArrayEquals(new byte[] { 'd', 'a', 't', 'a', '\n' },
175                     IOUtils.toByteArray(in));
176         }
177     }
178 
179     /**
180      * Test case for
181      * <a href="https://issues.apache.org/jira/browse/COMPRESS-351"
182      * >COMPRESS-351</a>.
183      */
184     @Test
185     public void testMessageWithCorruptFileName() throws Exception {
186         try (ZipArchiveInputStream in = new ZipArchiveInputStream(new FileInputStream(getFile("COMPRESS-351.zip")))) {
187             ZipArchiveEntry ze = in.getNextZipEntry();
188             while (ze != null) {
189                 ze = in.getNextZipEntry();
190             }
191             fail("expected EOFException");
192         } catch (final EOFException ex) {
193             final String m = ex.getMessage();
194             assertTrue(m.startsWith("Truncated ZIP entry: ?2016")); // the first character is not printable
195         }
196     }
197 
198     @Test
199     public void testUnzipBZip2CompressedEntry() throws Exception {
200 
201         try (ZipArchiveInputStream in = new ZipArchiveInputStream(new FileInputStream(getFile("bzip2-zip.zip")))) {
202             final ZipArchiveEntry ze = in.getNextZipEntry();
203             assertEquals(42, ze.getSize());
204             final byte[] expected = new byte[42];
205             Arrays.fill(expected, (byte) 'a');
206             assertArrayEquals(expected, IOUtils.toByteArray(in));
207         }
208     }
209 
210     /**
211      * @see "https://issues.apache.org/jira/browse/COMPRESS-380"
212      */
213     @Test
214     public void readDeflate64CompressedStream() throws Exception {
215         final File input = getFile("COMPRESS-380/COMPRESS-380-input");
216         final File archive = getFile("COMPRESS-380/COMPRESS-380.zip");
217         try (FileInputStream in = new FileInputStream(input);
218              ZipArchiveInputStream zin = new ZipArchiveInputStream(new FileInputStream(archive))) {
219             byte[] orig = IOUtils.toByteArray(in);
220             ZipArchiveEntry e = zin.getNextZipEntry();
221             byte[] fromZip = IOUtils.toByteArray(zin);
222             assertArrayEquals(orig, fromZip);
223         }
224     }
225 
226     @Test
227     public void readDeflate64CompressedStreamWithDataDescriptor() throws Exception {
228         // this is a copy of bla.jar with META-INF/MANIFEST.MF's method manually changed to ENHANCED_DEFLATED
229         final File archive = getFile("COMPRESS-380/COMPRESS-380-dd.zip");
230         try (ZipArchiveInputStream zin = new ZipArchiveInputStream(new FileInputStream(archive))) {
231             ZipArchiveEntry e = zin.getNextZipEntry();
232             assertEquals(-1, e.getSize());
233             assertEquals(ZipMethod.ENHANCED_DEFLATED.getCode(), e.getMethod());
234             byte[] fromZip = IOUtils.toByteArray(zin);
235             byte[] expected = new byte[] {
236                 'M', 'a', 'n', 'i', 'f', 'e', 's', 't', '-', 'V', 'e', 'r', 's', 'i', 'o', 'n', ':', ' ', '1', '.', '0',
237                 '\r', '\n', '\r', '\n'
238             };
239             assertArrayEquals(expected, fromZip);
240             zin.getNextZipEntry();
241             assertEquals(25, e.getSize());
242         }
243     }
244 
245     /**
246      * Test case for
247      * <a href="https://issues.apache.org/jira/browse/COMPRESS-364"
248      * >COMPRESS-364</a>.
249      */
250     @Test
251     public void testWithBytesAfterData() throws Exception {
252         final int expectedNumEntries = 2;
253         final InputStream is = ZipArchiveInputStreamTest.class
254                 .getResourceAsStream("/archive_with_bytes_after_data.zip");
255         final ZipArchiveInputStream zip = new ZipArchiveInputStream(is);
256 
257         try {
258             int actualNumEntries = 0;
259             ZipArchiveEntry zae = zip.getNextZipEntry();
260             while (zae != null) {
261                 actualNumEntries++;
262                 readEntry(zip, zae);
263                 zae = zip.getNextZipEntry();
264             }
265             assertEquals(expectedNumEntries, actualNumEntries);
266         } finally {
267             zip.close();
268         }
269     }
270 
271     /**
272      * <code>getNextZipEntry()</code> should throw a <code>ZipException</code> rather than return
273      * <code>null</code> when an unexpected structure is encountered.
274      */
275     @Test
276     public void testThrowOnInvalidEntry() throws Exception {
277         final InputStream is = ZipArchiveInputStreamTest.class
278                 .getResourceAsStream("/invalid-zip.zip");
279         final ZipArchiveInputStream zip = new ZipArchiveInputStream(is);
280 
281         try {
282             zip.getNextZipEntry();
283             fail("IOException expected");
284         } catch (ZipException expected) {
285             assertTrue(expected.getMessage().contains("Unexpected record signature"));
286         } finally {
287             zip.close();
288         }
289     }
290 
291     /**
292      * Test correct population of header and data offsets.
293      */
294     @Test
295     public void testOffsets() throws Exception {
296         // mixed.zip contains both inflated and stored files
297         try (InputStream archiveStream = ZipArchiveInputStream.class.getResourceAsStream("/mixed.zip");
298              ZipArchiveInputStream zipStream =  new ZipArchiveInputStream((archiveStream))
299         ) {
300             ZipArchiveEntry inflatedEntry = zipStream.getNextZipEntry();
301             Assert.assertEquals("inflated.txt", inflatedEntry.getName());
302             Assert.assertEquals(0x0000, inflatedEntry.getLocalHeaderOffset());
303             Assert.assertEquals(0x0046, inflatedEntry.getDataOffset());
304             ZipArchiveEntry storedEntry = zipStream.getNextZipEntry();
305             Assert.assertEquals("stored.txt", storedEntry.getName());
306             Assert.assertEquals(0x5892, storedEntry.getLocalHeaderOffset());
307             Assert.assertEquals(0x58d6, storedEntry.getDataOffset());
308             Assert.assertNull(zipStream.getNextZipEntry());
309         }
310     }
311 
312     @Test
313     public void nameSourceDefaultsToName() throws Exception {
314         nameSource("bla.zip", "test1.xml", ZipArchiveEntry.NameSource.NAME);
315     }
316 
317     @Test
318     public void nameSourceIsSetToUnicodeExtraField() throws Exception {
319         nameSource("utf8-winzip-test.zip", "\u20AC_for_Dollar.txt",
320                    ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
321     }
322 
323     @Test
324     public void nameSourceIsSetToEFS() throws Exception {
325         nameSource("utf8-7zip-test.zip", "\u20AC_for_Dollar.txt", 3,
326                    ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
327     }
328 
329     @Test
330     public void properlyMarksEntriesAsUnreadableIfUncompressedSizeIsUnknown() throws Exception {
331         // we never read any data
332         try (ZipArchiveInputStream zis = new ZipArchiveInputStream(new ByteArrayInputStream(new byte[0]))) {
333             ZipArchiveEntry e = new ZipArchiveEntry("test");
334             e.setMethod(ZipMethod.DEFLATED.getCode());
335             assertTrue(zis.canReadEntryData(e));
336             e.setMethod(ZipMethod.ENHANCED_DEFLATED.getCode());
337             assertTrue(zis.canReadEntryData(e));
338             e.setMethod(ZipMethod.BZIP2.getCode());
339             assertFalse(zis.canReadEntryData(e));
340         }
341     }
342 
343     private static byte[] readEntry(ZipArchiveInputStream zip, ZipArchiveEntry zae) throws IOException {
344         final int len = (int)zae.getSize();
345         final byte[] buff = new byte[len];
346         zip.read(buff, 0, len);
347 
348         return buff;
349     }
350 
351     private static void nameSource(String archive, String entry, ZipArchiveEntry.NameSource expected) throws Exception {
352         nameSource(archive, entry, 1, expected);
353     }
354 
355     private static void nameSource(String archive, String entry, int entryNo, ZipArchiveEntry.NameSource expected)
356         throws Exception {
357         try (ZipArchiveInputStream zis = new ZipArchiveInputStream(new FileInputStream(getFile(archive)))) {
358             ZipArchiveEntry ze;
359             do {
360                 ze = zis.getNextZipEntry();
361             } while (--entryNo > 0);
362             assertEquals(entry, ze.getName());
363             assertEquals(expected, ze.getNameSource());
364         }
365     }
366 }