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