1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.commons.compress.archivers.tar;
21
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.assertNotNull;
25 import static org.junit.jupiter.api.Assertions.assertNotSame;
26 import static org.junit.jupiter.api.Assertions.assertNull;
27 import static org.junit.jupiter.api.Assertions.assertSame;
28 import static org.junit.jupiter.api.Assertions.assertThrows;
29 import static org.junit.jupiter.api.Assertions.assertTrue;
30 import static org.junit.jupiter.api.Assertions.fail;
31
32 import java.io.BufferedInputStream;
33 import java.io.BufferedOutputStream;
34 import java.io.ByteArrayInputStream;
35 import java.io.ByteArrayOutputStream;
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.charset.StandardCharsets;
41 import java.nio.file.Files;
42 import java.nio.file.Path;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Calendar;
46 import java.util.Date;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.TimeZone;
50 import java.util.concurrent.ExecutorService;
51 import java.util.concurrent.Executors;
52 import java.util.concurrent.Future;
53 import java.util.concurrent.atomic.AtomicInteger;
54 import java.util.stream.Collectors;
55 import java.util.stream.IntStream;
56 import java.util.zip.GZIPInputStream;
57
58 import org.apache.commons.compress.AbstractTest;
59 import org.apache.commons.compress.archivers.ArchiveException;
60 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
61 import org.apache.commons.io.IOUtils;
62 import org.apache.commons.io.function.IOConsumer;
63 import org.junit.jupiter.api.Test;
64 import org.junit.jupiter.params.ParameterizedTest;
65 import org.junit.jupiter.params.provider.ValueSource;
66
67 class TarArchiveInputStreamTest extends AbstractTest {
68
69 private void datePriorToEpoch(final String archive) throws Exception {
70 try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(getFile(archive).toPath()))) {
71 final TarArchiveEntry tae = in.getNextTarEntry();
72 assertEquals("foo", tae.getName());
73 assertEquals(TarConstants.LF_NORMAL, tae.getLinkFlag());
74 final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
75 cal.set(1969, 11, 31, 23, 59, 59);
76 cal.set(Calendar.MILLISECOND, 0);
77 assertEquals(cal.getTime(), tae.getLastModifiedDate());
78 assertTrue(tae.isCheckSumOK());
79 }
80 }
81
82 private void getNextEntryUntilIOException(final TarArchiveInputStream archive) {
83 assertThrows(IOException.class, () -> archive.forEach(IOConsumer.noop()));
84 }
85
86 @SuppressWarnings("resource")
87 private TarArchiveInputStream getTestStream(final String name) {
88 return new TarArchiveInputStream(TarArchiveInputStreamTest.class.getResourceAsStream(name));
89 }
90
91 @Test
92 void testCompress197() throws IOException {
93 try (TarArchiveInputStream tar = getTestStream("/COMPRESS-197.tar")) {
94 TarArchiveEntry entry = tar.getNextTarEntry();
95 assertNotNull(entry);
96 while (entry != null) {
97 assertTrue(entry.isTypeFlagUstar());
98 entry = tar.getNextTarEntry();
99 }
100 }
101 }
102
103 @Test
104 void testCompress197ForEach() throws IOException {
105 try (TarArchiveInputStream tar = getTestStream("/COMPRESS-197.tar")) {
106 tar.forEach(IOConsumer.noop());
107 }
108 }
109
110 @Test
111 void testCompress558() throws IOException {
112 final String folderName = "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/";
113
114 final String consumerJavaName =
115 "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/java/example/queue/exclusive/Consumer.java";
116 final String producerJavaName =
117 "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/java/example/queue/exclusive/Producer.java";
118
119
120 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
121 try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
122 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
123 final TarArchiveEntry rootfolder = new TarArchiveEntry(folderName);
124 tos.putArchiveEntry(rootfolder);
125 final TarArchiveEntry consumerJava = new TarArchiveEntry(consumerJavaName);
126 tos.putArchiveEntry(consumerJava);
127 final TarArchiveEntry producerJava = new TarArchiveEntry(producerJavaName);
128 tos.putArchiveEntry(producerJava);
129 tos.closeArchiveEntry();
130 }
131 final byte[] data = bos.toByteArray();
132 try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
133 TarArchiveInputStream tis = new TarArchiveInputStream(bis)) {
134 assertEquals(folderName, tis.getNextTarEntry().getName());
135 assertEquals(TarConstants.LF_DIR, tis.getCurrentEntry().getLinkFlag());
136 assertEquals(consumerJavaName, tis.getNextTarEntry().getName());
137 assertEquals(TarConstants.LF_NORMAL, tis.getCurrentEntry().getLinkFlag());
138 assertEquals(producerJavaName, tis.getNextTarEntry().getName());
139 assertEquals(TarConstants.LF_NORMAL, tis.getCurrentEntry().getLinkFlag());
140 }
141 }
142
143 private void testCompress666(final int factor, final boolean bufferInputStream, final String localPath) {
144 final ExecutorService executorService = Executors.newFixedThreadPool(10);
145 try {
146 final List<Future<?>> tasks = IntStream.range(0, 200).mapToObj(index -> executorService.submit(() -> {
147 TarArchiveEntry tarEntry = null;
148 try (InputStream inputStream = getClass().getResourceAsStream(localPath);
149 TarArchiveInputStream tarInputStream = new TarArchiveInputStream(
150 bufferInputStream ? new BufferedInputStream(new GZIPInputStream(inputStream)) : new GZIPInputStream(inputStream),
151 TarConstants.DEFAULT_RCDSIZE * factor, TarConstants.DEFAULT_RCDSIZE)) {
152 while ((tarEntry = tarInputStream.getNextEntry()) != null) {
153 assertNotNull(tarEntry);
154 }
155 } catch (final IOException e) {
156 fail(Objects.toString(tarEntry), e);
157 }
158 })).collect(Collectors.toList());
159 final List<Exception> list = new ArrayList<>();
160 for (final Future<?> future : tasks) {
161 try {
162 future.get();
163 } catch (final Exception e) {
164 list.add(e);
165 }
166 }
167
168 if (!list.isEmpty()) {
169 fail(list.get(0));
170 }
171
172
173 } finally {
174 executorService.shutdownNow();
175 }
176 }
177
178
179
180
181
182
183 @ParameterizedTest
184 @ValueSource(ints = { 1, 2, 4, 8, 16, 20, 32, 64, 128 })
185 void testCompress666Buffered(final int factor) {
186 testCompress666(factor, true, "/COMPRESS-666/compress-666.tar.gz");
187 }
188
189
190
191
192
193
194 @ParameterizedTest
195 @ValueSource(ints = { 1, 2, 4, 8, 16, 20, 32, 64, 128 })
196 void testCompress666Unbuffered(final int factor) {
197 testCompress666(factor, false, "/COMPRESS-666/compress-666.tar.gz");
198 }
199
200 @Test
201 void testDatePriorToEpochInGNUFormat() throws Exception {
202 datePriorToEpoch("preepoch-star.tar");
203 }
204
205 @Test
206 void testDatePriorToEpochInPAXFormat() throws Exception {
207 datePriorToEpoch("preepoch-posix.tar");
208 }
209
210 @Test
211 void testDirectoryWithLongNameEndsWithSlash() throws IOException, ArchiveException {
212 final String rootPath = getTempDirFile().getAbsolutePath();
213 final String dirDirectory = "COMPRESS-509";
214 final int count = 100;
215 final File root = new File(rootPath + "/" + dirDirectory);
216 root.mkdirs();
217 for (int i = 1; i < count; i++) {
218
219 String subDir = "";
220 for (int j = 0; j < i; j++) {
221 subDir += "a";
222 }
223 final File dir = new File(rootPath + "/" + dirDirectory, "/" + subDir);
224 dir.mkdir();
225
226
227 final String fileName = "/" + dirDirectory + "/" + subDir;
228 final File tarF = new File(rootPath + "/tar" + i + ".tar");
229 try (OutputStream dest = Files.newOutputStream(tarF.toPath())) {
230 final TarArchiveOutputStream out = new TarArchiveOutputStream(new BufferedOutputStream(dest));
231 out.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
232 out.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
233
234 final File file = new File(rootPath, fileName);
235 final TarArchiveEntry entry = new TarArchiveEntry(file);
236 entry.setName(fileName);
237 out.putArchiveEntry(entry);
238 out.closeArchiveEntry();
239 out.flush();
240 }
241
242
243 try (InputStream is = Files.newInputStream(tarF.toPath());
244 TarArchiveInputStream debInputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("tar", is)) {
245 TarArchiveEntry outEntry;
246 while ((outEntry = debInputStream.getNextEntry()) != null) {
247 assertTrue(outEntry.getName().endsWith("/"), outEntry.getName());
248 }
249 }
250 }
251 }
252
253 @Test
254 void testGetAndSetOfPaxEntry() throws Exception {
255 try (TarArchiveInputStream is = getTestStream("/COMPRESS-356.tar")) {
256 final TarArchiveEntry entry = is.getNextTarEntry();
257 assertEquals("package/package.json", entry.getName());
258 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
259 assertEquals(is.getCurrentEntry(), entry);
260 final TarArchiveEntry weaselEntry = new TarArchiveEntry(entry.getName());
261 weaselEntry.setSize(entry.getSize());
262 is.setCurrentEntry(weaselEntry);
263 assertEquals(entry, is.getCurrentEntry());
264 assertNotSame(entry, is.getCurrentEntry());
265 assertSame(weaselEntry, is.getCurrentEntry());
266 assertThrows(IllegalStateException.class, () -> {
267 is.setCurrentEntry(null);
268 is.read();
269 }, "should abort because current entry is nulled");
270 is.setCurrentEntry(entry);
271 is.read();
272 }
273 }
274
275 @Test
276 void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
277 final byte[] buf = new byte[2];
278 try (InputStream in = newInputStream("bla.tar");
279 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
280 assertNotNull(archive.getNextEntry());
281 IOUtils.toByteArray(archive);
282 assertEquals(-1, archive.read(buf));
283 assertEquals(-1, archive.read(buf));
284 }
285 }
286
287 @Test
288 void testParseTarTruncatedInContent() throws IOException {
289 try (InputStream in = newInputStream("COMPRESS-544_truncated_in_content-fail.tar");
290 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
291 getNextEntryUntilIOException(archive);
292 }
293 }
294
295 @Test
296 void testParseTarTruncatedInPadding() throws IOException {
297 try (InputStream in = newInputStream("COMPRESS-544_truncated_in_padding-fail.tar");
298 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
299 getNextEntryUntilIOException(archive);
300 }
301 }
302
303 @Test
304 void testParseTarWithNonNumberPaxHeaders() throws IOException {
305 try (InputStream in = newInputStream("COMPRESS-529-fail.tar");
306 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
307 assertThrows(IOException.class, () -> archive.getNextEntry());
308 }
309 }
310
311 @Test
312 void testParseTarWithSpecialPaxHeaders() throws IOException {
313 try (InputStream in = newInputStream("COMPRESS-530-fail.tar");
314 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
315 assertThrows(IOException.class, () -> archive.getNextEntry());
316 assertThrows(IOException.class, () -> IOUtils.toByteArray(archive));
317 }
318 }
319
320 @Test
321 void testReadsArchiveCompletely_COMPRESS245() {
322 try (InputStream is = TarArchiveInputStreamTest.class.getResourceAsStream("/COMPRESS-245.tar.gz")) {
323 final InputStream gin = new GZIPInputStream(is);
324 try (TarArchiveInputStream tar = new TarArchiveInputStream(gin)) {
325 int count = 0;
326 TarArchiveEntry entry = tar.getNextTarEntry();
327 while (entry != null) {
328 count++;
329 entry = tar.getNextTarEntry();
330 }
331 assertEquals(31, count);
332 }
333 } catch (final IOException e) {
334 fail("COMPRESS-245: " + e.getMessage());
335 }
336 }
337
338 @Test
339 void testRejectsArchivesWithNegativeSizes() throws Exception {
340 try (InputStream in = newInputStream("COMPRESS-569-fail.tar");
341 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
342 getNextEntryUntilIOException(archive);
343 }
344 }
345
346
347
348
349 @Test
350 void testShouldConsumeArchiveCompletely() throws Exception {
351 try (InputStream is = TarArchiveInputStreamTest.class.getResourceAsStream("/archive_with_trailer.tar");
352 TarArchiveInputStream tar = new TarArchiveInputStream(is)) {
353 while (tar.getNextTarEntry() != null) {
354
355 }
356 final byte[] expected = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n' };
357 final byte[] actual = new byte[expected.length];
358 is.read(actual);
359 assertArrayEquals(expected, actual, () -> Arrays.toString(actual));
360 }
361 }
362
363 @Test
364 void testShouldReadBigGid() throws Exception {
365 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
366 try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
367 tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
368 final TarArchiveEntry t = new TarArchiveEntry("name");
369 t.setGroupId(4294967294L);
370 t.setSize(1);
371 tos.putArchiveEntry(t);
372 tos.write(30);
373 tos.closeArchiveEntry();
374 }
375 final byte[] data = bos.toByteArray();
376 final ByteArrayInputStream bis = new ByteArrayInputStream(data);
377 try (TarArchiveInputStream tis = new TarArchiveInputStream(bis)) {
378 final TarArchiveEntry t = tis.getNextTarEntry();
379 assertEquals(4294967294L, t.getLongGroupId());
380 }
381 }
382
383
384
385
386 @Test
387 void testShouldReadGNULongNameEntryWithWrongName() throws Exception {
388 try (TarArchiveInputStream is = getTestStream("/COMPRESS-324.tar")) {
389 final TarArchiveEntry entry = is.getNextTarEntry();
390 assertEquals(
391 "1234567890123456789012345678901234567890123456789012345678901234567890"
392 + "1234567890123456789012345678901234567890123456789012345678901234567890"
393 + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890.txt",
394 entry.getName());
395 }
396 }
397
398 @Test
399 void testShouldThrowAnExceptionOnTruncatedEntries() throws Exception {
400 final Path dir = createTempDirectory("COMPRESS-279");
401 try (TarArchiveInputStream is = getTestStream("/COMPRESS-279-fail.tar")) {
402 assertThrows(IOException.class, () -> {
403 TarArchiveEntry entry = is.getNextTarEntry();
404 int count = 0;
405 while (entry != null) {
406 Files.copy(is, dir.resolve(String.valueOf(count)));
407 count++;
408 entry = is.getNextTarEntry();
409 }
410 });
411 }
412 }
413
414 @Test
415 void testShouldThrowAnExceptionOnTruncatedStream() throws Exception {
416 final Path dir = createTempDirectory("COMPRESS-279");
417 try (TarArchiveInputStream is = getTestStream("/COMPRESS-279-fail.tar")) {
418 final AtomicInteger count = new AtomicInteger();
419 assertThrows(IOException.class, () -> is.forEach(entry -> Files.copy(is, dir.resolve(String.valueOf(count.getAndIncrement())))));
420 }
421 }
422
423 @Test
424 void testShouldUseSpecifiedEncodingWhenReadingGNULongNames() throws Exception {
425 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
426 final String encoding = StandardCharsets.UTF_16.name();
427 final String name = "1234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890\u00e4";
428 try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, encoding)) {
429 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
430 final TarArchiveEntry t = new TarArchiveEntry(name);
431 t.setSize(1);
432 tos.putArchiveEntry(t);
433 tos.write(30);
434 tos.closeArchiveEntry();
435 }
436 final byte[] data = bos.toByteArray();
437 final ByteArrayInputStream bis = new ByteArrayInputStream(data);
438 try (TarArchiveInputStream tis = new TarArchiveInputStream(bis, encoding)) {
439 final TarArchiveEntry t = tis.getNextTarEntry();
440 assertEquals(name, t.getName());
441 }
442 }
443
444 @Test
445 void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws Exception {
446 try (InputStream in = newInputStream("bla.tar");
447 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
448 assertNotNull(archive.getNextEntry());
449 IOUtils.toByteArray(archive);
450 assertEquals(-1, archive.read());
451 assertEquals(-1, archive.read());
452 }
453 }
454
455
456
457
458 @Test
459 void testSkipsDevNumbersWhenEntryIsNoDevice() throws Exception {
460 try (TarArchiveInputStream is = getTestStream("/COMPRESS-417.tar")) {
461 assertEquals("test1.xml", is.getNextTarEntry().getName());
462 assertEquals(TarConstants.LF_NORMAL, is.getCurrentEntry().getLinkFlag());
463 assertEquals("test2.xml", is.getNextTarEntry().getName());
464 assertEquals(TarConstants.LF_NORMAL, is.getCurrentEntry().getLinkFlag());
465 assertNull(is.getNextTarEntry());
466 }
467 }
468
469
470
471
472 @Test
473 void testSurvivesBlankLinesInPaxHeader() throws Exception {
474 try (TarArchiveInputStream is = getTestStream("/COMPRESS-355.tar")) {
475 final TarArchiveEntry entry = is.getNextTarEntry();
476 assertEquals("package/package.json", entry.getName());
477 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
478 assertNull(is.getNextTarEntry());
479 }
480 }
481
482
483
484
485 @Test
486 void testSurvivesPaxHeaderWithNameEndingInSlash() throws Exception {
487 try (TarArchiveInputStream is = getTestStream("/COMPRESS-356.tar")) {
488 final TarArchiveEntry entry = is.getNextTarEntry();
489 assertEquals("package/package.json", entry.getName());
490 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
491 assertNull(is.getNextTarEntry());
492 }
493 }
494
495 @Test
496 void testThrowException() throws IOException {
497 try (InputStream in = newInputStream("COMPRESS-553-fail.tar");
498 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
499 getNextEntryUntilIOException(archive);
500 }
501 }
502
503 @Test
504 void testThrowExceptionWithNullEntry() throws IOException {
505 try (InputStream in = newInputStream("COMPRESS-554-fail.tar");
506 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
507 getNextEntryUntilIOException(archive);
508 }
509 }
510
511 @Test
512 void testWorkaroundForBrokenTimeHeader() throws Exception {
513 try (TarArchiveInputStream in = new TarArchiveInputStream(newInputStream("simple-aix-native-tar.tar"))) {
514 TarArchiveEntry tae = in.getNextTarEntry();
515 tae = in.getNextTarEntry();
516 assertEquals("sample/link-to-txt-file.lnk", tae.getName());
517 assertEquals(TarConstants.LF_SYMLINK, tae.getLinkFlag());
518 assertEquals(new Date(0), tae.getLastModifiedDate());
519 assertTrue(tae.isSymbolicLink());
520 assertTrue(tae.isCheckSumOK());
521 }
522 }
523 }