View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.compress.archivers.tar;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNotSame;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertSame;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  
30  import java.io.BufferedOutputStream;
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.charset.StandardCharsets;
38  import java.nio.file.Files;
39  import java.nio.file.Path;
40  import java.util.Arrays;
41  import java.util.Calendar;
42  import java.util.Date;
43  import java.util.TimeZone;
44  import java.util.zip.GZIPInputStream;
45  
46  import org.apache.commons.compress.AbstractTest;
47  import org.apache.commons.compress.archivers.ArchiveException;
48  import org.apache.commons.compress.archivers.ArchiveStreamFactory;
49  import org.apache.commons.io.IOUtils;
50  import org.junit.jupiter.api.Test;
51  
52  public class TarArchiveInputStreamTest extends AbstractTest {
53  
54      private void datePriorToEpoch(final String archive) throws Exception {
55          try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(getFile(archive).toPath()))) {
56              final TarArchiveEntry tae = in.getNextTarEntry();
57              assertEquals("foo", tae.getName());
58              assertEquals(TarConstants.LF_NORMAL, tae.getLinkFlag());
59              final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
60              cal.set(1969, 11, 31, 23, 59, 59);
61              cal.set(Calendar.MILLISECOND, 0);
62              assertEquals(cal.getTime(), tae.getLastModifiedDate());
63              assertTrue(tae.isCheckSumOK());
64          }
65      }
66  
67      private void getNextEntryUntilIOException(final TarArchiveInputStream archive) {
68          assertThrows(IOException.class, () -> {
69              while (archive.getNextTarEntry() != null) {
70                  // noop
71              }
72          });
73      }
74  
75      private TarArchiveInputStream getTestStream(final String name) {
76          return new TarArchiveInputStream(TarArchiveInputStreamTest.class.getResourceAsStream(name));
77      }
78  
79      @Test
80      public void testCompress197() {
81          try (TarArchiveInputStream tar = getTestStream("/COMPRESS-197.tar")) {
82              TarArchiveEntry entry = tar.getNextTarEntry();
83              while (entry != null) {
84                  entry = tar.getNextTarEntry();
85              }
86          } catch (final IOException e) {
87              fail("COMPRESS-197: " + e.getMessage());
88          }
89      }
90  
91      @Test
92      public void testCompress558() throws IOException {
93          final String folderName = "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/";
94          // @formatter:off
95          final String consumerJavaName =
96              "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/java/example/queue/exclusive/Consumer.java";
97          final String producerJavaName =
98              "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/java/example/queue/exclusive/Producer.java";
99          // @formatter:on
100 
101         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
102         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
103             tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
104             final TarArchiveEntry rootfolder = new TarArchiveEntry(folderName);
105             tos.putArchiveEntry(rootfolder);
106             final TarArchiveEntry consumerJava = new TarArchiveEntry(consumerJavaName);
107             tos.putArchiveEntry(consumerJava);
108             final TarArchiveEntry producerJava = new TarArchiveEntry(producerJavaName);
109             tos.putArchiveEntry(producerJava);
110             tos.closeArchiveEntry();
111         }
112         final byte[] data = bos.toByteArray();
113         try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
114                 TarArchiveInputStream tis = new TarArchiveInputStream(bis)) {
115             assertEquals(folderName, tis.getNextTarEntry().getName());
116             assertEquals(TarConstants.LF_DIR, tis.getCurrentEntry().getLinkFlag());
117             assertEquals(consumerJavaName, tis.getNextTarEntry().getName());
118             assertEquals(TarConstants.LF_NORMAL, tis.getCurrentEntry().getLinkFlag());
119             assertEquals(producerJavaName, tis.getNextTarEntry().getName());
120             assertEquals(TarConstants.LF_NORMAL, tis.getCurrentEntry().getLinkFlag());
121         }
122     }
123 
124     @Test
125     public void testDatePriorToEpochInGNUFormat() throws Exception {
126         datePriorToEpoch("preepoch-star.tar");
127     }
128 
129     @Test
130     public void testDatePriorToEpochInPAXFormat() throws Exception {
131         datePriorToEpoch("preepoch-posix.tar");
132     }
133 
134     @Test
135     public void testDirectoryWithLongNameEndsWithSlash() throws IOException, ArchiveException {
136         final String rootPath = getTempDirFile().getAbsolutePath();
137         final String dirDirectory = "COMPRESS-509";
138         final int count = 100;
139         final File root = new File(rootPath + "/" + dirDirectory);
140         root.mkdirs();
141         for (int i = 1; i < count; i++) {
142             // create empty dirs with incremental length
143             String subDir = "";
144             for (int j = 0; j < i; j++) {
145                 subDir += "a";
146             }
147             final File dir = new File(rootPath + "/" + dirDirectory, "/" + subDir);
148             dir.mkdir();
149 
150             // tar these dirs
151             final String fileName = "/" + dirDirectory + "/" + subDir;
152             final File tarF = new File(rootPath + "/tar" + i + ".tar");
153             try (OutputStream dest = Files.newOutputStream(tarF.toPath())) {
154                 final TarArchiveOutputStream out = new TarArchiveOutputStream(new BufferedOutputStream(dest));
155                 out.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
156                 out.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
157 
158                 final File file = new File(rootPath, fileName);
159                 final TarArchiveEntry entry = new TarArchiveEntry(file);
160                 entry.setName(fileName);
161                 out.putArchiveEntry(entry);
162                 out.closeArchiveEntry();
163                 out.flush();
164             }
165 
166             // untar these tars
167             try (InputStream is = Files.newInputStream(tarF.toPath());
168                     TarArchiveInputStream debInputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("tar", is)) {
169                 TarArchiveEntry outEntry;
170                 while ((outEntry = debInputStream.getNextEntry()) != null) {
171                     assertTrue(outEntry.getName().endsWith("/"), outEntry.getName());
172                 }
173             }
174         }
175     }
176 
177     @Test
178     public void testGetAndSetOfPaxEntry() throws Exception {
179         try (TarArchiveInputStream is = getTestStream("/COMPRESS-356.tar")) {
180             final TarArchiveEntry entry = is.getNextTarEntry();
181             assertEquals("package/package.json", entry.getName());
182             assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
183             assertEquals(is.getCurrentEntry(), entry);
184             final TarArchiveEntry weaselEntry = new TarArchiveEntry(entry.getName());
185             weaselEntry.setSize(entry.getSize());
186             is.setCurrentEntry(weaselEntry);
187             assertEquals(entry, is.getCurrentEntry());
188             assertNotSame(entry, is.getCurrentEntry());
189             assertSame(weaselEntry, is.getCurrentEntry());
190             assertThrows(IllegalStateException.class, () -> {
191                 is.setCurrentEntry(null);
192                 is.read();
193             }, "should abort because current entry is nulled");
194             is.setCurrentEntry(entry);
195             is.read();
196         }
197     }
198 
199     @Test
200     public void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
201         final byte[] buf = new byte[2];
202         try (InputStream in = newInputStream("bla.tar");
203                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
204             assertNotNull(archive.getNextEntry());
205             IOUtils.toByteArray(archive);
206             assertEquals(-1, archive.read(buf));
207             assertEquals(-1, archive.read(buf));
208         }
209     }
210 
211     @Test
212     public void testParseTarTruncatedInContent() throws IOException {
213         try (InputStream in = newInputStream("COMPRESS-544_truncated_in_content-fail.tar");
214                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
215             getNextEntryUntilIOException(archive);
216         }
217     }
218 
219     @Test
220     public void testParseTarTruncatedInPadding() throws IOException {
221         try (InputStream in = newInputStream("COMPRESS-544_truncated_in_padding-fail.tar");
222                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
223             getNextEntryUntilIOException(archive);
224         }
225     }
226 
227     @Test
228     public void testParseTarWithNonNumberPaxHeaders() throws IOException {
229         try (InputStream in = newInputStream("COMPRESS-529-fail.tar");
230                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
231             assertThrows(IOException.class, () -> archive.getNextEntry());
232         }
233     }
234 
235     @Test
236     public void testParseTarWithSpecialPaxHeaders() throws IOException {
237         try (InputStream in = newInputStream("COMPRESS-530-fail.tar");
238                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
239             assertThrows(IOException.class, () -> archive.getNextEntry());
240             assertThrows(IOException.class, () -> IOUtils.toByteArray(archive));
241         }
242     }
243 
244     @Test
245     public void testReadsArchiveCompletely_COMPRESS245() {
246         try (InputStream is = TarArchiveInputStreamTest.class.getResourceAsStream("/COMPRESS-245.tar.gz")) {
247             final InputStream gin = new GZIPInputStream(is);
248             try (TarArchiveInputStream tar = new TarArchiveInputStream(gin)) {
249                 int count = 0;
250                 TarArchiveEntry entry = tar.getNextTarEntry();
251                 while (entry != null) {
252                     count++;
253                     entry = tar.getNextTarEntry();
254                 }
255                 assertEquals(31, count);
256             }
257         } catch (final IOException e) {
258             fail("COMPRESS-245: " + e.getMessage());
259         }
260     }
261 
262     @Test
263     public void testRejectsArchivesWithNegativeSizes() throws Exception {
264         try (InputStream in = newInputStream("COMPRESS-569-fail.tar");
265                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
266             getNextEntryUntilIOException(archive);
267         }
268     }
269 
270     /**
271      * This test ensures the implementation is reading the padded last block if a tool has added one to an archive
272      */
273     @Test
274     public void testShouldConsumeArchiveCompletely() throws Exception {
275         try (InputStream is = TarArchiveInputStreamTest.class.getResourceAsStream("/archive_with_trailer.tar");
276                 TarArchiveInputStream tar = new TarArchiveInputStream(is)) {
277             while (tar.getNextTarEntry() != null) {
278                 // just consume the archive
279             }
280             final byte[] expected = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n' };
281             final byte[] actual = new byte[expected.length];
282             is.read(actual);
283             assertArrayEquals(expected, actual, () -> Arrays.toString(actual));
284         }
285     }
286 
287     @Test
288     public void testShouldReadBigGid() throws Exception {
289         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
290         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
291             tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
292             final TarArchiveEntry t = new TarArchiveEntry("name");
293             t.setGroupId(4294967294L);
294             t.setSize(1);
295             tos.putArchiveEntry(t);
296             tos.write(30);
297             tos.closeArchiveEntry();
298         }
299         final byte[] data = bos.toByteArray();
300         final ByteArrayInputStream bis = new ByteArrayInputStream(data);
301         try (TarArchiveInputStream tis = new TarArchiveInputStream(bis)) {
302             final TarArchiveEntry t = tis.getNextTarEntry();
303             assertEquals(4294967294L, t.getLongGroupId());
304         }
305     }
306 
307     /**
308      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-324">COMPRESS-324</a>
309      */
310     @Test
311     public void testShouldReadGNULongNameEntryWithWrongName() throws Exception {
312         try (TarArchiveInputStream is = getTestStream("/COMPRESS-324.tar")) {
313             final TarArchiveEntry entry = is.getNextTarEntry();
314             assertEquals(
315                     "1234567890123456789012345678901234567890123456789012345678901234567890"
316                             + "1234567890123456789012345678901234567890123456789012345678901234567890"
317                             + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890.txt",
318                     entry.getName());
319         }
320     }
321 
322     @Test
323     public void testShouldThrowAnExceptionOnTruncatedEntries() throws Exception {
324         final Path dir = createTempDirectory("COMPRESS-279");
325         try (TarArchiveInputStream is = getTestStream("/COMPRESS-279-fail.tar")) {
326             assertThrows(IOException.class, () -> {
327                 TarArchiveEntry entry = is.getNextTarEntry();
328                 int count = 0;
329                 while (entry != null) {
330                     Files.copy(is, dir.resolve(String.valueOf(count)));
331                     count++;
332                     entry = is.getNextTarEntry();
333                 }
334             });
335         }
336     }
337 
338     @Test
339     public void testShouldUseSpecifiedEncodingWhenReadingGNULongNames() throws Exception {
340         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
341         final String encoding = StandardCharsets.UTF_16.name();
342         final String name = "1234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890\u00e4";
343         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, encoding)) {
344             tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
345             final TarArchiveEntry t = new TarArchiveEntry(name);
346             t.setSize(1);
347             tos.putArchiveEntry(t);
348             tos.write(30);
349             tos.closeArchiveEntry();
350         }
351         final byte[] data = bos.toByteArray();
352         final ByteArrayInputStream bis = new ByteArrayInputStream(data);
353         try (TarArchiveInputStream tis = new TarArchiveInputStream(bis, encoding)) {
354             final TarArchiveEntry t = tis.getNextTarEntry();
355             assertEquals(name, t.getName());
356         }
357     }
358 
359     @Test
360     public void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
361         try (InputStream in = newInputStream("bla.tar");
362                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
363             assertNotNull(archive.getNextEntry());
364             IOUtils.toByteArray(archive);
365             assertEquals(-1, archive.read());
366             assertEquals(-1, archive.read());
367         }
368     }
369 
370     /**
371      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-417">COMPRESS-417</a>
372      */
373     @Test
374     public void testSkipsDevNumbersWhenEntryIsNoDevice() throws Exception {
375         try (TarArchiveInputStream is = getTestStream("/COMPRESS-417.tar")) {
376             assertEquals("test1.xml", is.getNextTarEntry().getName());
377             assertEquals(TarConstants.LF_NORMAL, is.getCurrentEntry().getLinkFlag());
378             assertEquals("test2.xml", is.getNextTarEntry().getName());
379             assertEquals(TarConstants.LF_NORMAL, is.getCurrentEntry().getLinkFlag());
380             assertNull(is.getNextTarEntry());
381         }
382     }
383 
384     /**
385      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-355">COMPRESS-355</a>
386      */
387     @Test
388     public void testSurvivesBlankLinesInPaxHeader() throws Exception {
389         try (TarArchiveInputStream is = getTestStream("/COMPRESS-355.tar")) {
390             final TarArchiveEntry entry = is.getNextTarEntry();
391             assertEquals("package/package.json", entry.getName());
392             assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
393             assertNull(is.getNextTarEntry());
394         }
395     }
396 
397     /**
398      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-356">COMPRESS-356</a>
399      */
400     @Test
401     public void testSurvivesPaxHeaderWithNameEndingInSlash() throws Exception {
402         try (TarArchiveInputStream is = getTestStream("/COMPRESS-356.tar")) {
403             final TarArchiveEntry entry = is.getNextTarEntry();
404             assertEquals("package/package.json", entry.getName());
405             assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
406             assertNull(is.getNextTarEntry());
407         }
408     }
409 
410     @Test
411     public void testThrowException() throws IOException {
412         try (InputStream in = newInputStream("COMPRESS-553-fail.tar");
413                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
414             getNextEntryUntilIOException(archive);
415         }
416     }
417 
418     @Test
419     public void testThrowExceptionWithNullEntry() throws IOException {
420         try (InputStream in = newInputStream("COMPRESS-554-fail.tar");
421                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
422             getNextEntryUntilIOException(archive);
423         }
424     }
425 
426     @Test
427     public void testWorkaroundForBrokenTimeHeader() throws Exception {
428         try (TarArchiveInputStream in = new TarArchiveInputStream(newInputStream("simple-aix-native-tar.tar"))) {
429             TarArchiveEntry tae = in.getNextTarEntry();
430             tae = in.getNextTarEntry();
431             assertEquals("sample/link-to-txt-file.lnk", tae.getName());
432             assertEquals(TarConstants.LF_SYMLINK, tae.getLinkFlag());
433             assertEquals(new Date(0), tae.getLastModifiedDate());
434             assertTrue(tae.isSymbolicLink());
435             assertTrue(tae.isCheckSumOK());
436         }
437     }
438 
439 }