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.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertFalse;
25  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertThrows;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  
30  import java.io.ByteArrayInputStream;
31  import java.io.ByteArrayOutputStream;
32  import java.io.File;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.OutputStream;
36  import java.nio.charset.StandardCharsets;
37  import java.nio.file.Files;
38  import java.util.Arrays;
39  import java.util.List;
40  
41  import org.apache.commons.compress.AbstractTest;
42  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
43  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
44  import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
45  import org.apache.commons.compress.archivers.tar.TarConstants;
46  import org.apache.commons.compress.archivers.tar.TarFile;
47  import org.apache.commons.compress.utils.ByteUtils;
48  import org.apache.commons.io.IOUtils;
49  import org.junit.jupiter.api.Test;
50  
51  public final class TarTest extends AbstractTest {
52  
53      private String createLongName(final int nameLength) {
54          final StringBuilder buffer = new StringBuilder();
55          for (int i = 0; i < nameLength; i++) {
56              buffer.append('a');
57          }
58          return buffer.toString();
59      }
60  
61      private byte[] createTarWithOneLongNameEntry(final String longName) throws IOException {
62          final ByteArrayOutputStream bos = new ByteArrayOutputStream();
63          try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
64              tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
65              final TarArchiveEntry longFileNameEntry = new TarArchiveEntry(longName);
66              tos.putArchiveEntry(longFileNameEntry);
67              tos.closeArchiveEntry();
68          }
69          return bos.toByteArray();
70      }
71  
72      @Test
73      public void testCOMPRESS114() throws Exception {
74          final File input = getFile("COMPRESS-114.tar");
75          try (InputStream is = Files.newInputStream(input.toPath());
76                  TarArchiveInputStream in = new TarArchiveInputStream(is, StandardCharsets.ISO_8859_1.name())) {
77              TarArchiveEntry entry = in.getNextEntry();
78              assertEquals("3\u00b1\u00b1\u00b1F06\u00b1W2345\u00b1ZB\u00b1la\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1BLA", entry.getName());
79              assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
80              entry = in.getNextEntry();
81              assertEquals("0302-0601-3\u00b1\u00b1\u00b1F06\u00b1W2345\u00b1ZB\u00b1la\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1BLA", entry.getName());
82              assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
83          }
84      }
85  
86      @Test
87      public void testCOMPRESS178() throws Exception {
88          final File input = getFile("COMPRESS-178-fail.tar");
89          try (InputStream is = Files.newInputStream(input.toPath());
90                  ArchiveInputStream<?> in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("tar", is)) {
91              final IOException e = assertThrows(IOException.class, in::getNextEntry, "Expected IOException");
92              final Throwable t = e.getCause();
93              assertInstanceOf(IllegalArgumentException.class, t, "Expected cause = IllegalArgumentException");
94          }
95      }
96  
97      @Test
98      public void testCOMPRESS178Lenient() throws Exception {
99          final File input = getFile("COMPRESS-178-fail.tar");
100         try (InputStream is = Files.newInputStream(input.toPath());
101                 ArchiveInputStream<?> in = new TarArchiveInputStream(is, true)) {
102             in.getNextEntry();
103         }
104     }
105 
106     @Test
107     public void testDirectoryEntryFromFile() throws Exception {
108         final File archive = createTempFile("test.", ".tar");
109         final long beforeArchiveWrite;
110         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
111             final File dir = getTempDirFile();
112             beforeArchiveWrite = dir.lastModified();
113             final TarArchiveEntry in = new TarArchiveEntry(dir, "foo");
114             tos.putArchiveEntry(in);
115             tos.closeArchiveEntry();
116         }
117         final TarArchiveEntry out;
118         try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(archive.toPath()))) {
119             out = tis.getNextTarEntry();
120         }
121         assertNotNull(out);
122         assertEquals("foo/", out.getName());
123         assertEquals(TarConstants.LF_DIR, out.getLinkFlag());
124         assertEquals(0, out.getSize());
125         // TAR stores time with a granularity of 1 second
126         assertEquals(beforeArchiveWrite / 1000, out.getLastModifiedDate().getTime() / 1000);
127         assertTrue(out.isDirectory());
128     }
129 
130     @Test
131     public void testDirectoryRead() throws IOException {
132         final File input = getFile("directory.tar");
133         try (InputStream is = Files.newInputStream(input.toPath());
134                 TarArchiveInputStream in = new TarArchiveInputStream(is)) {
135             final TarArchiveEntry directoryEntry = in.getNextTarEntry();
136             assertEquals("directory/", directoryEntry.getName());
137             assertEquals(TarConstants.LF_DIR, directoryEntry.getLinkFlag());
138             assertTrue(directoryEntry.isDirectory());
139             final byte[] directoryRead = IOUtils.toByteArray(in);
140             assertArrayEquals(ByteUtils.EMPTY_BYTE_ARRAY, directoryRead);
141         }
142     }
143 
144     @Test
145     public void testExplicitDirectoryEntry() throws Exception {
146         final File archive = createTempFile("test.", ".tar");
147         final long beforeArchiveWrite;
148         final TarArchiveEntry in = new TarArchiveEntry("foo/");
149         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
150             beforeArchiveWrite = getTempDirFile().lastModified();
151             in.setModTime(beforeArchiveWrite);
152             tos.putArchiveEntry(in);
153             tos.closeArchiveEntry();
154         }
155         final TarArchiveEntry out;
156         try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(archive.toPath()))) {
157             out = tis.getNextTarEntry();
158         }
159         assertNotNull(out);
160         assertEquals("foo/", out.getName());
161         assertEquals(TarConstants.LF_DIR, in.getLinkFlag());
162         assertEquals(0, out.getSize());
163         assertEquals(beforeArchiveWrite / 1000, out.getLastModifiedDate().getTime() / 1000);
164         assertTrue(out.isDirectory());
165     }
166 
167     @Test
168     public void testExplicitFileEntry() throws Exception {
169         final File tmp = createTempFile();
170         final File archive = createTempFile("test.", ".tar");
171         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
172             final TarArchiveEntry entryIn = new TarArchiveEntry("foo");
173             entryIn.setModTime(tmp.lastModified());
174             entryIn.setSize(tmp.length());
175             tos.putArchiveEntry(entryIn);
176             final byte[] b = new byte[(int) tmp.length()];
177             try (InputStream fis = Files.newInputStream(tmp.toPath())) {
178                 while (fis.read(b) > 0) {
179                     tos.write(b);
180                 }
181             }
182             tos.closeArchiveEntry();
183         }
184         final TarArchiveEntry entryOut;
185         try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(archive.toPath()))) {
186             entryOut = tis.getNextTarEntry();
187         }
188         assertNotNull(entryOut);
189         assertEquals("foo", entryOut.getName());
190         assertEquals(TarConstants.LF_NORMAL, entryOut.getLinkFlag());
191         assertEquals(tmp.length(), entryOut.getSize());
192         assertEquals(tmp.lastModified() / 1000, entryOut.getLastModifiedDate().getTime() / 1000);
193         assertFalse(entryOut.isDirectory());
194     }
195 
196     @Test
197     public void testFileEntryFromFile() throws Exception {
198         final File tmp = createTempFile();
199         final File archive = createTempFile("test.", ".tar");
200         final TarArchiveEntry in = new TarArchiveEntry(tmp, "foo");
201         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
202             tos.putArchiveEntry(in);
203             final byte[] b = new byte[(int) tmp.length()];
204             try (InputStream fis = Files.newInputStream(tmp.toPath())) {
205                 while (fis.read(b) > 0) {
206                     tos.write(b);
207                 }
208             }
209             tos.closeArchiveEntry();
210         }
211         final TarArchiveEntry out;
212         try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(archive.toPath()))) {
213             out = tis.getNextTarEntry();
214         }
215         assertNotNull(out);
216         assertEquals("foo", out.getName());
217         assertEquals(TarConstants.LF_NORMAL, out.getLinkFlag());
218         assertEquals(tmp.length(), out.getSize());
219         assertEquals(tmp.lastModified() / 1000, out.getLastModifiedDate().getTime() / 1000);
220         assertFalse(out.isDirectory());
221     }
222 
223     @Test
224     public void testLongNameLargerThanBuffer() throws IOException {
225         final List<Integer> nameLength = Arrays.asList(300, 4096);
226 
227         for (final Integer length : nameLength) {
228             final String fileName = createLongName(length);
229             assertEquals(length.intValue(), fileName.length());
230             final byte[] data = createTarWithOneLongNameEntry(fileName);
231             try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
232                     TarArchiveInputStream tis = new TarArchiveInputStream(bis)) {
233                 assertEquals(fileName, tis.getNextTarEntry().getName());
234             }
235         }
236     }
237 
238     @Test
239     public void testTarArchiveCreation() throws Exception {
240         final File output = newTempFile("bla.tar");
241         final File file1 = getFile("test1.xml");
242         try (OutputStream out = Files.newOutputStream(output.toPath());
243                 TarArchiveOutputStream os = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream("tar", out)) {
244             final TarArchiveEntry entry = new TarArchiveEntry("testdata/test1.xml");
245             entry.setModTime(0);
246             entry.setSize(file1.length());
247             entry.setUserId(0);
248             entry.setGroupId(0);
249             entry.setUserName("avalon");
250             entry.setGroupName("excalibur");
251             entry.setMode(0100000);
252             os.putArchiveEntry(entry);
253             Files.copy(file1.toPath(), os);
254             os.closeArchiveEntry();
255         }
256     }
257 
258     @Test
259     public void testTarArchiveLongNameCreation() throws Exception {
260         final String name = "testdata/12345678901234567890123456789012345678901234567890123456789012345678901234567890123456.xml";
261         final byte[] bytes = name.getBytes(UTF_8);
262         assertEquals(bytes.length, 99);
263 
264         final File output = newTempFile("bla.tar");
265         final File file1 = getFile("test1.xml");
266         final TarArchiveEntry entry = new TarArchiveEntry(name);
267         try (OutputStream out = Files.newOutputStream(output.toPath());
268                 TarArchiveOutputStream os = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream("tar", out)) {
269             entry.setModTime(0);
270             entry.setSize(file1.length());
271             entry.setUserId(0);
272             entry.setGroupId(0);
273             entry.setUserName("avalon");
274             entry.setGroupName("excalibur");
275             entry.setMode(0100000);
276             os.putArchiveEntry(entry);
277             Files.copy(file1.toPath(), os);
278             os.closeArchiveEntry();
279         }
280 
281         final String toLongName = "testdata/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567.xml";
282         final File output2 = newTempFile("bla.tar");
283         try (OutputStream out2 = Files.newOutputStream(output2.toPath());
284                 TarArchiveOutputStream os2 = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream("tar", out2);) {
285             final TarArchiveEntry entry2 = new TarArchiveEntry(toLongName);
286             entry2.setModTime(0);
287             entry2.setSize(file1.length());
288             entry2.setUserId(0);
289             entry2.setGroupId(0);
290             entry2.setUserName("avalon");
291             entry2.setGroupName("excalibur");
292             entry2.setMode(0100000);
293             os2.putArchiveEntry(entry);
294             Files.copy(file1.toPath(), os2);
295             os2.closeArchiveEntry();
296         } catch (final IOException e) {
297             assertTrue(true);
298         }
299     }
300 
301     @Test
302     public void testTarFileCOMPRESS114() throws Exception {
303         final File input = getFile("COMPRESS-114.tar");
304         try (TarFile tarFile = new TarFile(input, StandardCharsets.ISO_8859_1.name())) {
305             final List<TarArchiveEntry> entries = tarFile.getEntries();
306             TarArchiveEntry entry = entries.get(0);
307             assertEquals("3\u00b1\u00b1\u00b1F06\u00b1W2345\u00b1ZB\u00b1la\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1BLA", entry.getName());
308             assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
309             entry = entries.get(1);
310             assertEquals("0302-0601-3\u00b1\u00b1\u00b1F06\u00b1W2345\u00b1ZB\u00b1la\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1BLA", entry.getName());
311             assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
312         }
313     }
314 
315     @Test
316     public void testTarFileCOMPRESS178() throws Exception {
317         final File input = getFile("COMPRESS-178-fail.tar");
318         final IOException e = assertThrows(IOException.class, () -> {
319             try (TarFile tarFile = new TarFile(input)) {
320                 // Compared to the TarArchiveInputStream all entries are read when instantiating the tar file
321             }
322         }, "Expected IOException");
323         final Throwable t = e.getCause();
324         assertInstanceOf(IllegalArgumentException.class, t, "Expected cause = IllegalArgumentException");
325     }
326 
327     @Test
328     public void testTarFileCOMPRESS178Lenient() throws Exception {
329         final File input = getFile("COMPRESS-178-fail.tar");
330         try (TarFile tarFile = new TarFile(input, true)) {
331             // Compared to the TarArchiveInputStream all entries are read when instantiating the tar file
332         }
333     }
334 
335     @Test
336     public void testTarFileDirectoryEntryFromFile() throws Exception {
337         final File archive = createTempFile("test.", ".tar");
338         final File dir = getTempDirFile();
339         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
340             final long beforeArchiveWrite = dir.lastModified();
341             final TarArchiveEntry in = new TarArchiveEntry(dir, "foo");
342             tos.putArchiveEntry(in);
343             tos.closeArchiveEntry();
344             tos.close();
345             try (TarFile tarFile = new TarFile(archive)) {
346                 final TarArchiveEntry entry = tarFile.getEntries().get(0);
347                 assertNotNull(entry);
348                 assertEquals("foo/", entry.getName());
349                 assertEquals(TarConstants.LF_DIR, entry.getLinkFlag());
350                 assertEquals(0, entry.getSize());
351                 // TAR stores time with a granularity of 1 second
352                 assertEquals(beforeArchiveWrite / 1000, entry.getLastModifiedDate().getTime() / 1000);
353                 assertTrue(entry.isDirectory());
354             }
355         }
356     }
357 
358     @Test
359     public void testTarFileDirectoryRead() throws IOException {
360         final File input = getFile("directory.tar");
361         try (TarFile tarFile = new TarFile(input)) {
362             final TarArchiveEntry directoryEntry = tarFile.getEntries().get(0);
363             assertEquals("directory/", directoryEntry.getName());
364             assertEquals(TarConstants.LF_DIR, directoryEntry.getLinkFlag());
365             assertTrue(directoryEntry.isDirectory());
366             try (InputStream directoryStream = tarFile.getInputStream(directoryEntry)) {
367                 final byte[] directoryRead = IOUtils.toByteArray(directoryStream);
368                 assertArrayEquals(ByteUtils.EMPTY_BYTE_ARRAY, directoryRead);
369             }
370         }
371     }
372 
373     @Test
374     public void testTarFileEntryFromFile() throws Exception {
375         final File tmp = createTempFile();
376         final File archive = createTempFile("test.", ".tar");
377         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
378             final TarArchiveEntry in = new TarArchiveEntry(tmp, "foo");
379             tos.putArchiveEntry(in);
380             final byte[] b = new byte[(int) tmp.length()];
381             try (InputStream fis = Files.newInputStream(tmp.toPath())) {
382                 while (fis.read(b) > 0) {
383                     tos.write(b);
384                 }
385             }
386             tos.closeArchiveEntry();
387             tos.close();
388             try (TarFile tarFile = new TarFile(archive)) {
389                 final TarArchiveEntry entry = tarFile.getEntries().get(0);
390                 assertNotNull(entry);
391                 assertEquals("foo", entry.getName());
392                 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
393                 assertEquals(tmp.length(), entry.getSize());
394                 assertEquals(tmp.lastModified() / 1000, entry.getLastModifiedDate().getTime() / 1000);
395                 assertFalse(entry.isDirectory());
396             }
397         }
398     }
399 
400     @Test
401     public void testTarFileExplicitDirectoryEntry() throws Exception {
402         final File archive = createTempFile("test.", ".tar");
403         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
404             final long beforeArchiveWrite = getTempDirFile().lastModified();
405             final TarArchiveEntry in = new TarArchiveEntry("foo/");
406             in.setModTime(beforeArchiveWrite);
407             tos.putArchiveEntry(in);
408             tos.closeArchiveEntry();
409             tos.close();
410             try (TarFile tarFile = new TarFile(archive)) {
411                 final TarArchiveEntry entry = tarFile.getEntries().get(0);
412                 assertNotNull(entry);
413                 assertEquals("foo/", entry.getName());
414                 assertEquals(TarConstants.LF_DIR, entry.getLinkFlag());
415                 assertEquals(0, entry.getSize());
416                 assertEquals(beforeArchiveWrite / 1000, entry.getLastModifiedDate().getTime() / 1000);
417                 assertTrue(entry.isDirectory());
418             }
419         }
420     }
421 
422     @Test
423     public void testTarFileExplicitFileEntry() throws Exception {
424         final File tmp = createTempFile();
425         final File archive = createTempFile("test.", ".tar");
426         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(Files.newOutputStream(archive.toPath()))) {
427             final TarArchiveEntry in = new TarArchiveEntry("foo");
428             in.setModTime(tmp.lastModified());
429             in.setSize(tmp.length());
430             tos.putArchiveEntry(in);
431             final byte[] b = new byte[(int) tmp.length()];
432             try (InputStream fis = Files.newInputStream(tmp.toPath())) {
433                 while (fis.read(b) > 0) {
434                     tos.write(b);
435                 }
436             }
437             tos.closeArchiveEntry();
438 
439             try (TarFile tarFile = new TarFile(archive)) {
440                 final TarArchiveEntry entry = tarFile.getEntries().get(0);
441                 assertNotNull(entry);
442                 assertEquals("foo", entry.getName());
443                 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
444                 assertEquals(tmp.length(), entry.getSize());
445                 assertEquals(tmp.lastModified() / 1000, entry.getLastModifiedDate().getTime() / 1000);
446                 assertFalse(entry.isDirectory());
447             }
448         }
449     }
450 
451     @Test
452     public void testTarFileLongNameLargerThanBuffer() throws IOException {
453         final List<Integer> nameLength = Arrays.asList(300, 4096);
454 
455         for (final Integer length : nameLength) {
456             final String fileName = createLongName(length);
457             assertEquals(length.intValue(), fileName.length());
458             final byte[] data = createTarWithOneLongNameEntry(fileName);
459             try (TarFile tarFile = new TarFile(data)) {
460                 final List<TarArchiveEntry> entries = tarFile.getEntries();
461                 assertEquals(fileName, entries.get(0).getName());
462                 assertEquals(TarConstants.LF_NORMAL, entries.get(0).getLinkFlag());
463             }
464         }
465     }
466 
467     @Test
468     public void testTarFileUnarchive() throws Exception {
469         final File file = getFile("bla.tar");
470         try (TarFile tarFile = new TarFile(file)) {
471             final TarArchiveEntry entry = tarFile.getEntries().get(0);
472             try (InputStream inputStream = tarFile.getInputStream(entry)) {
473                 Files.copy(inputStream, newTempFile(entry.getName()).toPath());
474             }
475         }
476     }
477 
478     @Test
479     public void testTarUnarchive() throws Exception {
480         final File input = getFile("bla.tar");
481         try (InputStream is = Files.newInputStream(input.toPath());
482                 TarArchiveInputStream in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("tar", is)) {
483             final TarArchiveEntry entry = in.getNextEntry();
484             Files.copy(in, newTempFile(entry.getName()).toPath());
485         }
486     }
487 }