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         final TarArchiveInputStream tar = getTestStream("/COMPRESS-197.tar");
160         try {
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         } finally {
168             tar.close();
169         }
170     }
171 
172     @Test
173     public void shouldUseSpecifiedEncodingWhenReadingGNULongNames()
174         throws Exception {
175         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
176         final String encoding = CharsetNames.UTF_16;
177         final String name = "1234567890123456789012345678901234567890123456789"
178             + "01234567890123456789012345678901234567890123456789"
179             + "01234567890\u00e4";
180         final TarArchiveOutputStream tos =
181             new TarArchiveOutputStream(bos, encoding);
182         tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
183         TarArchiveEntry t = new TarArchiveEntry(name);
184         t.setSize(1);
185         tos.putArchiveEntry(t);
186         tos.write(30);
187         tos.closeArchiveEntry();
188         tos.close();
189         final byte[] data = bos.toByteArray();
190         final ByteArrayInputStream bis = new ByteArrayInputStream(data);
191         final TarArchiveInputStream tis =
192             new TarArchiveInputStream(bis, encoding);
193         t = tis.getNextTarEntry();
194         assertEquals(name, t.getName());
195         tis.close();
196     }
197 
198     @Test
199     public void shouldConsumeArchiveCompletely() throws Exception {
200         final InputStream is = TarArchiveInputStreamTest.class
201             .getResourceAsStream("/archive_with_trailer.tar");
202         final TarArchiveInputStream tar = new TarArchiveInputStream(is);
203         while (tar.getNextTarEntry() != null) {
204             // just consume the archive
205         }
206         final byte[] expected = new byte[] {
207             'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n'
208         };
209         final byte[] actual = new byte[expected.length];
210         is.read(actual);
211         assertArrayEquals(expected, actual);
212         tar.close();
213     }
214 
215     @Test
216     public void readsArchiveCompletely_COMPRESS245() throws Exception {
217         final InputStream is = TarArchiveInputStreamTest.class
218             .getResourceAsStream("/COMPRESS-245.tar.gz");
219         try {
220             final InputStream gin = new GZIPInputStream(is);
221             final TarArchiveInputStream tar = new TarArchiveInputStream(gin);
222             int count = 0;
223             TarArchiveEntry entry = tar.getNextTarEntry();
224             while (entry != null) {
225                 count++;
226                 entry = tar.getNextTarEntry();
227             }
228             assertEquals(31, count);
229             tar.close();
230         } catch (final IOException e) {
231             fail("COMPRESS-245: " + e.getMessage());
232         } finally {
233             is.close();
234         }
235     }
236 
237     @Test(expected = IOException.class)
238     public void shouldThrowAnExceptionOnTruncatedEntries() throws Exception {
239         final File dir = mkdir("COMPRESS-279");
240         final TarArchiveInputStream is = getTestStream("/COMPRESS-279.tar");
241         FileOutputStream out = null;
242         try {
243             TarArchiveEntry entry = is.getNextTarEntry();
244             int count = 0;
245             while (entry != null) {
246                 out = new FileOutputStream(new File(dir, String.valueOf(count)));
247                 IOUtils.copy(is, out);
248                 out.close();
249                 out = null;
250                 count++;
251                 entry = is.getNextTarEntry();
252             }
253         } finally {
254             is.close();
255             if (out != null) {
256                 out.close();
257             }
258             rmdir(dir);
259         }
260     }
261 
262     @Test
263     public void shouldReadBigGid() throws Exception {
264         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
265         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
266         tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
267         TarArchiveEntry t = new TarArchiveEntry("name");
268         t.setGroupId(4294967294l);
269         t.setSize(1);
270         tos.putArchiveEntry(t);
271         tos.write(30);
272         tos.closeArchiveEntry();
273         tos.close();
274         final byte[] data = bos.toByteArray();
275         final ByteArrayInputStream bis = new ByteArrayInputStream(data);
276         final TarArchiveInputStream tis =
277             new TarArchiveInputStream(bis);
278         t = tis.getNextTarEntry();
279         assertEquals(4294967294l, t.getLongGroupId());
280         tis.close();
281     }
282 
283     /**
284      * @link "https://issues.apache.org/jira/browse/COMPRESS-324"
285      */
286     @Test
287     public void shouldReadGNULongNameEntryWithWrongName() throws Exception {
288         final TarArchiveInputStream is = getTestStream("/COMPRESS-324.tar");
289         try {
290             final TarArchiveEntry entry = is.getNextTarEntry();
291             assertEquals("1234567890123456789012345678901234567890123456789012345678901234567890"
292                          + "1234567890123456789012345678901234567890123456789012345678901234567890"
293                          + "1234567890123456789012345678901234567890123456789012345678901234567890"
294                          + "1234567890123456789012345678901234567890.txt",
295                          entry.getName());
296         } finally {
297             is.close();
298         }
299     }
300 
301     /**
302      * @link "https://issues.apache.org/jira/browse/COMPRESS-355"
303      */
304     @Test
305     public void survivesBlankLinesInPaxHeader() throws Exception {
306         final TarArchiveInputStream is = getTestStream("/COMPRESS-355.tar");
307         try {
308             final TarArchiveEntry entry = is.getNextTarEntry();
309             assertEquals("package/package.json", entry.getName());
310             assertNull(is.getNextTarEntry());
311         } finally {
312             is.close();
313         }
314     }
315 
316     /**
317      * @link "https://issues.apache.org/jira/browse/COMPRESS-356"
318      */
319     @Test
320     public void survivesPaxHeaderWithNameEndingInSlash() throws Exception {
321         final TarArchiveInputStream is = getTestStream("/COMPRESS-356.tar");
322         try {
323             final TarArchiveEntry entry = is.getNextTarEntry();
324             assertEquals("package/package.json", entry.getName());
325             assertNull(is.getNextTarEntry());
326         } finally {
327             is.close();
328         }
329     }
330 
331     private TarArchiveInputStream getTestStream(final String name) {
332         return new TarArchiveInputStream(
333                 TarArchiveInputStreamTest.class.getResourceAsStream(name));
334     }
335 
336 }