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.tar;
20  
21  import static org.apache.commons.compress.AbstractTestCase.getFile;
22  import static org.apache.commons.compress.AbstractTestCase.mkdir;
23  import static org.apache.commons.compress.AbstractTestCase.rmdir;
24  import static org.junit.Assert.assertArrayEquals;
25  import static org.junit.Assert.assertEquals;
26  import static org.junit.Assert.assertNull;
27  import static org.junit.Assert.assertTrue;
28  import static org.junit.Assert.fail;
29  
30  import java.io.ByteArrayInputStream;
31  import java.io.ByteArrayOutputStream;
32  import java.io.File;
33  import java.io.FileInputStream;
34  import java.io.FileOutputStream;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.util.Calendar;
38  import java.util.Date;
39  import java.util.Map;
40  import java.util.TimeZone;
41  import java.util.zip.GZIPInputStream;
42  
43  import org.apache.commons.compress.utils.CharsetNames;
44  import org.apache.commons.compress.utils.IOUtils;
45  import org.junit.Test;
46  
47  public class TarArchiveInputStreamTest {
48  
49      @Test
50      public void readSimplePaxHeader() throws Exception {
51          final InputStream is = new ByteArrayInputStream(new byte[1]);
52          final TarArchiveInputStream tais = new TarArchiveInputStream(is);
53          final Map<String, String> headers = tais
54              .parsePaxHeaders(new ByteArrayInputStream("30 atime=1321711775.972059463\n"
55                                                        .getBytes(CharsetNames.UTF_8)));
56          assertEquals(1, headers.size());
57          assertEquals("1321711775.972059463", headers.get("atime"));
58          tais.close();
59      }
60  
61      @Test
62      public void secondEntryWinsWhenPaxHeaderContainsDuplicateKey() throws Exception {
63          final InputStream is = new ByteArrayInputStream(new byte[1]);
64          final TarArchiveInputStream tais = new TarArchiveInputStream(is);
65          final Map<String, String> headers = tais
66              .parsePaxHeaders(new ByteArrayInputStream("11 foo=bar\n11 foo=baz\n"
67                                                        .getBytes(CharsetNames.UTF_8)));
68          assertEquals(1, headers.size());
69          assertEquals("baz", headers.get("foo"));
70          tais.close();
71      }
72  
73      @Test
74      public void paxHeaderEntryWithEmptyValueRemovesKey() throws Exception {
75          final InputStream is = new ByteArrayInputStream(new byte[1]);
76          final TarArchiveInputStream tais = new TarArchiveInputStream(is);
77          final Map<String, String> headers = tais
78              .parsePaxHeaders(new ByteArrayInputStream("11 foo=bar\n7 foo=\n"
79                                                        .getBytes(CharsetNames.UTF_8)));
80          assertEquals(0, headers.size());
81          tais.close();
82      }
83  
84      @Test
85      public void readPaxHeaderWithEmbeddedNewline() throws Exception {
86          final InputStream is = new ByteArrayInputStream(new byte[1]);
87          final TarArchiveInputStream tais = new TarArchiveInputStream(is);
88          final Map<String, String> headers = tais
89              .parsePaxHeaders(new ByteArrayInputStream("28 comment=line1\nline2\nand3\n"
90                                                        .getBytes(CharsetNames.UTF_8)));
91          assertEquals(1, headers.size());
92          assertEquals("line1\nline2\nand3", headers.get("comment"));
93          tais.close();
94      }
95  
96      @Test
97      public void readNonAsciiPaxHeader() throws Exception {
98          final String ae = "\u00e4";
99          final String line = "11 path="+ ae + "\n";
100         assertEquals(11, line.getBytes(CharsetNames.UTF_8).length);
101         final InputStream is = new ByteArrayInputStream(new byte[1]);
102         final TarArchiveInputStream tais = new TarArchiveInputStream(is);
103         final Map<String, String> headers = tais
104             .parsePaxHeaders(new ByteArrayInputStream(line.getBytes(CharsetNames.UTF_8)));
105         assertEquals(1, headers.size());
106         assertEquals(ae, headers.get("path"));
107         tais.close();
108     }
109 
110     @Test
111     public void workaroundForBrokenTimeHeader() throws Exception {
112         TarArchiveInputStream in = null;
113         try {
114             in = new TarArchiveInputStream(new FileInputStream(getFile("simple-aix-native-tar.tar")));
115             TarArchiveEntry tae = in.getNextTarEntry();
116             tae = in.getNextTarEntry();
117             assertEquals("sample/link-to-txt-file.lnk", tae.getName());
118             assertEquals(new Date(0), tae.getLastModifiedDate());
119             assertTrue(tae.isSymbolicLink());
120             assertTrue(tae.isCheckSumOK());
121         } finally {
122             if (in != null) {
123                 in.close();
124             }
125         }
126     }
127 
128     @Test
129     public void datePriorToEpochInGNUFormat() throws Exception {
130         datePriorToEpoch("preepoch-star.tar");
131     }
132 
133 
134     @Test
135     public void datePriorToEpochInPAXFormat() throws Exception {
136         datePriorToEpoch("preepoch-posix.tar");
137     }
138 
139     private void datePriorToEpoch(final String archive) throws Exception {
140         TarArchiveInputStream in = null;
141         try {
142             in = new TarArchiveInputStream(new FileInputStream(getFile(archive)));
143             final TarArchiveEntry tae = in.getNextTarEntry();
144             assertEquals("foo", tae.getName());
145             final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
146             cal.set(1969, 11, 31, 23, 59, 59);
147             cal.set(Calendar.MILLISECOND, 0);
148             assertEquals(cal.getTime(), tae.getLastModifiedDate());
149             assertTrue(tae.isCheckSumOK());
150         } finally {
151             if (in != null) {
152                 in.close();
153             }
154         }
155     }
156 
157     @Test
158     public void testCompress197() throws Exception {
159         try (TarArchiveInputStream tar = getTestStream("/COMPRESS-197.tar")) {
160             TarArchiveEntry entry = tar.getNextTarEntry();
161             while (entry != null) {
162                 entry = tar.getNextTarEntry();
163             }
164         } catch (final IOException e) {
165             fail("COMPRESS-197: " + e.getMessage());
166         }
167     }
168 
169     @Test
170     public void shouldUseSpecifiedEncodingWhenReadingGNULongNames()
171         throws Exception {
172         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
173         final String encoding = CharsetNames.UTF_16;
174         final String name = "1234567890123456789012345678901234567890123456789"
175             + "01234567890123456789012345678901234567890123456789"
176             + "01234567890\u00e4";
177         final TarArchiveOutputStream tos =
178             new TarArchiveOutputStream(bos, encoding);
179         tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
180         TarArchiveEntry t = new TarArchiveEntry(name);
181         t.setSize(1);
182         tos.putArchiveEntry(t);
183         tos.write(30);
184         tos.closeArchiveEntry();
185         tos.close();
186         final byte[] data = bos.toByteArray();
187         final ByteArrayInputStream bis = new ByteArrayInputStream(data);
188         final TarArchiveInputStream tis =
189             new TarArchiveInputStream(bis, encoding);
190         t = tis.getNextTarEntry();
191         assertEquals(name, t.getName());
192         tis.close();
193     }
194 
195     @Test
196     public void shouldConsumeArchiveCompletely() throws Exception {
197         final InputStream is = TarArchiveInputStreamTest.class
198             .getResourceAsStream("/archive_with_trailer.tar");
199         final TarArchiveInputStream tar = new TarArchiveInputStream(is);
200         while (tar.getNextTarEntry() != null) {
201             // just consume the archive
202         }
203         final byte[] expected = new byte[] {
204             'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n'
205         };
206         final byte[] actual = new byte[expected.length];
207         is.read(actual);
208         assertArrayEquals(expected, actual);
209         tar.close();
210     }
211 
212     @Test
213     public void readsArchiveCompletely_COMPRESS245() throws Exception {
214         try (InputStream is = TarArchiveInputStreamTest.class
215                 .getResourceAsStream("/COMPRESS-245.tar.gz")) {
216             final InputStream gin = new GZIPInputStream(is);
217             final TarArchiveInputStream tar = new TarArchiveInputStream(gin);
218             int count = 0;
219             TarArchiveEntry entry = tar.getNextTarEntry();
220             while (entry != null) {
221                 count++;
222                 entry = tar.getNextTarEntry();
223             }
224             assertEquals(31, count);
225             tar.close();
226         } catch (final IOException e) {
227             fail("COMPRESS-245: " + e.getMessage());
228         }
229     }
230 
231     @Test(expected = IOException.class)
232     public void shouldThrowAnExceptionOnTruncatedEntries() throws Exception {
233         final File dir = mkdir("COMPRESS-279");
234         final TarArchiveInputStream is = getTestStream("/COMPRESS-279.tar");
235         FileOutputStream out = null;
236         try {
237             TarArchiveEntry entry = is.getNextTarEntry();
238             int count = 0;
239             while (entry != null) {
240                 out = new FileOutputStream(new File(dir, String.valueOf(count)));
241                 IOUtils.copy(is, out);
242                 out.close();
243                 out = null;
244                 count++;
245                 entry = is.getNextTarEntry();
246             }
247         } finally {
248             is.close();
249             if (out != null) {
250                 out.close();
251             }
252             rmdir(dir);
253         }
254     }
255 
256     @Test
257     public void shouldReadBigGid() throws Exception {
258         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
259         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
260         tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
261         TarArchiveEntry t = new TarArchiveEntry("name");
262         t.setGroupId(4294967294l);
263         t.setSize(1);
264         tos.putArchiveEntry(t);
265         tos.write(30);
266         tos.closeArchiveEntry();
267         tos.close();
268         final byte[] data = bos.toByteArray();
269         final ByteArrayInputStream bis = new ByteArrayInputStream(data);
270         final TarArchiveInputStream tis =
271             new TarArchiveInputStream(bis);
272         t = tis.getNextTarEntry();
273         assertEquals(4294967294l, t.getLongGroupId());
274         tis.close();
275     }
276 
277     /**
278      * @link "https://issues.apache.org/jira/browse/COMPRESS-324"
279      */
280     @Test
281     public void shouldReadGNULongNameEntryWithWrongName() throws Exception {
282         try (TarArchiveInputStream is = getTestStream("/COMPRESS-324.tar")) {
283             final TarArchiveEntry entry = is.getNextTarEntry();
284             assertEquals("1234567890123456789012345678901234567890123456789012345678901234567890"
285                             + "1234567890123456789012345678901234567890123456789012345678901234567890"
286                             + "1234567890123456789012345678901234567890123456789012345678901234567890"
287                             + "1234567890123456789012345678901234567890.txt",
288                     entry.getName());
289         }
290     }
291 
292     /**
293      * @link "https://issues.apache.org/jira/browse/COMPRESS-355"
294      */
295     @Test
296     public void survivesBlankLinesInPaxHeader() throws Exception {
297         try (TarArchiveInputStream is = getTestStream("/COMPRESS-355.tar")) {
298             final TarArchiveEntry entry = is.getNextTarEntry();
299             assertEquals("package/package.json", entry.getName());
300             assertNull(is.getNextTarEntry());
301         }
302     }
303 
304     /**
305      * @link "https://issues.apache.org/jira/browse/COMPRESS-356"
306      */
307     @Test
308     public void survivesPaxHeaderWithNameEndingInSlash() throws Exception {
309         try (TarArchiveInputStream is = getTestStream("/COMPRESS-356.tar")) {
310             final TarArchiveEntry entry = is.getNextTarEntry();
311             assertEquals("package/package.json", entry.getName());
312             assertNull(is.getNextTarEntry());
313         }
314     }
315 
316     private TarArchiveInputStream getTestStream(final String name) {
317         return new TarArchiveInputStream(
318                 TarArchiveInputStreamTest.class.getResourceAsStream(name));
319     }
320 
321 }