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