This is an automated email from the ASF dual-hosted git repository. peterlee pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-compress.git
commit f0aaab65408f53684efa9fc450819fff51c69436 Author: theobisproject <[email protected]> AuthorDate: Fri Nov 13 14:08:27 2020 +0100 COMPRESS-540: Fix problems with long filenames Includes fix for COMPRESS-558 If an archive contains many files with long names or a name which is larger than the buffer until know the name was wrong Add missing tests from TarArchiveInputStreamTest to TarFileTest --- .../commons/compress/archivers/tar/TarFile.java | 16 +- .../commons/compress/archivers/TarTestCase.java | 54 ++++- .../compress/archivers/tar/TarFileTest.java | 233 +++++++++++++++++++++ 3 files changed, 295 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java index d8e517d..f512d29 100644 --- a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java @@ -43,14 +43,14 @@ import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; /** - * The TarFile provides random access to UNIX to archives. + * The TarFile provides random access to UNIX archives. * @since 1.21 */ public class TarFile implements Closeable { private static final int SMALL_BUFFER_SIZE = 256; - private final ByteBuffer smallBuf = ByteBuffer.allocate(SMALL_BUFFER_SIZE); + private final byte[] smallBuf = new byte[SMALL_BUFFER_SIZE]; private final SeekableByteChannel archive; @@ -273,11 +273,11 @@ public class TarFile implements Closeable { } // COMPRESS-509 : the name of directories should end with '/' - String name = zipEncoding.decode(longNameData); + final String name = zipEncoding.decode(longNameData); + currEntry.setName(name); if (currEntry.isDirectory() && !name.endsWith("/")) { - name += "/"; + currEntry.setName(name + "/"); } - currEntry.setName(name); } if (currEntry.isGlobalPaxHeader()) { // Process Global Pax headers @@ -474,8 +474,10 @@ public class TarFile implements Closeable { private byte[] getLongNameData() throws IOException { final ByteArrayOutputStream longName = new ByteArrayOutputStream(); int length; - while ((length = archive.read(smallBuf)) > 0) { - longName.write(smallBuf.array(), 0, length); + try (final InputStream in = getInputStream(currEntry)) { + while ((length = in.read(smallBuf)) >= 0) { + longName.write(smallBuf, 0, length); + } } getNextTarEntry(); if (currEntry == null) { diff --git a/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java b/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java index f5dc104..e4fd699 100644 --- a/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java +++ b/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java @@ -18,14 +18,17 @@ */ package org.apache.commons.compress.archivers; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.List; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; import org.apache.commons.compress.AbstractTestCase; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; @@ -534,4 +537,53 @@ public final class TarTestCase extends AbstractTestCase { } } } + + @Test + public void testLongNameLargerThanBuffer() throws IOException { + final List<Integer> nameLength = Arrays.asList(300, 4096); + + for (final Integer length : nameLength) { + final String fileName = createLongName(length); + assertEquals(length.intValue(), fileName.length()); + final byte[] data = createTarWithOneLongNameEntry(fileName); + try (final ByteArrayInputStream bis = new ByteArrayInputStream(data); + final TarArchiveInputStream tis = new TarArchiveInputStream(bis)) { + assertEquals(fileName, tis.getNextTarEntry().getName()); + } + } + } + + @Test + public void testTarFileLongNameLargerThanBuffer() throws IOException { + final List<Integer> nameLength = Arrays.asList(300, 4096); + + for (final Integer length : nameLength) { + final String fileName = createLongName(length); + assertEquals(length.intValue(), fileName.length()); + final byte[] data = createTarWithOneLongNameEntry(fileName); + try (final TarFile tarFile = new TarFile(data)) { + List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(fileName, entries.get(0).getName()); + } + } + } + + private String createLongName(final int nameLength) { + final StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < nameLength; i++) { + buffer.append('a'); + } + return buffer.toString(); + } + + private byte[] createTarWithOneLongNameEntry(final String longName) throws IOException { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) { + tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); + TarArchiveEntry longFileNameEntry = new TarArchiveEntry(longName); + tos.putArchiveEntry(longFileNameEntry); + tos.closeArchiveEntry(); + } + return bos.toByteArray(); + } } diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java index d953417..1360320 100644 --- a/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java @@ -18,20 +18,221 @@ package org.apache.commons.compress.archivers.tar; import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.zip.GZIPInputStream; import org.apache.commons.compress.AbstractTestCase; import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.utils.CharsetNames; +import org.apache.commons.compress.utils.IOUtils; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class TarFileTest extends AbstractTestCase { @Test + public void workaroundForBrokenTimeHeader() throws IOException { + try (final TarFile tarFile = new TarFile(getPath("simple-aix-native-tar.tar"))) { + final List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(3, entries.size()); + final TarArchiveEntry entry = entries.get(1); + assertEquals("sample/link-to-txt-file.lnk", entry.getName()); + assertEquals(new Date(0), entry.getLastModifiedDate()); + assertTrue(entry.isSymbolicLink()); + assertTrue(entry.isCheckSumOK()); + } + } + + @Test + public void datePriorToEpochInGNUFormat() throws Exception { + datePriorToEpoch("preepoch-star.tar"); + } + + @Test + public void datePriorToEpochInPAXFormat() throws Exception { + datePriorToEpoch("preepoch-posix.tar"); + } + + private void datePriorToEpoch(final String archive) throws Exception { + try (final TarFile tarFile = new TarFile(getPath(archive))) { + TarArchiveEntry entry = tarFile.getEntries().get(0); + assertEquals("foo", entry.getName()); + final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + cal.set(1969, 11, 31, 23, 59, 59); + cal.set(Calendar.MILLISECOND, 0); + assertEquals(cal.getTime(), entry.getLastModifiedDate()); + assertTrue(entry.isCheckSumOK()); + } + } + + @Test + public void testCompress197() throws Exception { + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-197.tar"))) { + } catch (final IOException e) { + fail("COMPRESS-197: " + e.getMessage()); + } + } + + @Test + public void shouldUseSpecifiedEncodingWhenReadingGNULongNames() + throws Exception { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final String encoding = CharsetNames.UTF_16; + final String name = "1234567890123456789012345678901234567890123456789" + + "01234567890123456789012345678901234567890123456789" + + "01234567890\u00e4"; + try (final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, encoding)) { + tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); + TarArchiveEntry t = new TarArchiveEntry(name); + t.setSize(1); + tos.putArchiveEntry(t); + tos.write(30); + tos.closeArchiveEntry(); + } + final byte[] data = bos.toByteArray(); + try (final TarFile tarFile = new TarFile(data, encoding)) { + List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(1, entries.size()); + assertEquals(name, entries.get(0).getName()); + } + } + + @Test + public void readsArchiveCompletely_COMPRESS245() throws Exception { + try { + final Path tempTar = resultDir.toPath().resolve("COMPRESS-245.tar"); + try (final GZIPInputStream gin = new GZIPInputStream( + Files.newInputStream(getPath("COMPRESS-245.tar.gz")))) { + Files.copy(gin, tempTar); + } + try (final TarFile tarFile = new TarFile(tempTar)) { + assertEquals(31, tarFile.getEntries().size()); + } + } catch (final IOException e) { + fail("COMPRESS-245: " + e.getMessage()); + } + } + + @Test(expected = IOException.class) + public void shouldThrowAnExceptionOnTruncatedEntries() throws Exception { + final File dir = mkdir("COMPRESS-279"); + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-279.tar"))) { + int count = 0; + for (final TarArchiveEntry entry : tarFile.getEntries()) { + Files.copy(tarFile.getInputStream(entry), dir.toPath().resolve(String.valueOf(count))); + count++; + } + } finally { + rmdir(dir); + } + } + + @Test + public void shouldReadBigGid() throws Exception { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) { + tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX); + TarArchiveEntry t = new TarArchiveEntry("name"); + t.setGroupId(4294967294L); + t.setSize(1); + tos.putArchiveEntry(t); + tos.write(30); + tos.closeArchiveEntry(); + } + final byte[] data = bos.toByteArray(); + try (final TarFile tarFile = new TarFile(data)) { + List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(4294967294L, entries.get(0).getLongGroupId()); + } + } + + /** + * @link "https://issues.apache.org/jira/browse/COMPRESS-324" + */ + @Test + public void shouldReadGNULongNameEntryWithWrongName() throws Exception { + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-324.tar"))) { + List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals("1234567890123456789012345678901234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890.txt", + entries.get(0).getName()); + } + } + + /** + * @link "https://issues.apache.org/jira/browse/COMPRESS-355" + */ + @Test + public void survivesBlankLinesInPaxHeader() throws Exception { + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-355.tar"))) { + List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(1, entries.size()); + assertEquals("package/package.json", entries.get(0).getName()); + } + } + + /** + * @link "https://issues.apache.org/jira/browse/COMPRESS-356" + */ + @Test + public void survivesPaxHeaderWithNameEndingInSlash() throws Exception { + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-356.tar"))) { + final List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(1, entries.size()); + assertEquals("package/package.json", entries.get(0).getName()); + } + } + + /** + * @link "https://issues.apache.org/jira/browse/COMPRESS-417" + */ + @Test + public void skipsDevNumbersWhenEntryIsNoDevice() throws Exception { + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-417.tar"))) { + final List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(2, entries.size()); + assertEquals("test1.xml", entries.get(0).getName()); + assertEquals("test2.xml", entries.get(1).getName()); + } + } + + @Test + public void singleByteReadConsistentlyReturnsMinusOneAtEof() throws Exception { + try (final TarFile tarFile = new TarFile(getPath("bla.tar")); + final InputStream input = tarFile.getInputStream(tarFile.getEntries().get(0))) { + IOUtils.toByteArray(input); + assertEquals(-1, input.read()); + assertEquals(-1, input.read()); + } + } + + @Test + public void multiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception { + final byte[] buf = new byte[2]; + try (final TarFile tarFile = new TarFile(getPath("bla.tar")); + final InputStream input = tarFile.getInputStream(tarFile.getEntries().get(0))) { + IOUtils.toByteArray(input); + assertEquals(-1, input.read(buf)); + assertEquals(-1, input.read(buf)); + } + } + + @Test public void testDirectoryWithLongNameEndsWithSlash() throws IOException, ArchiveException { final String rootPath = dir.getAbsolutePath(); final String dirDirectory = "COMPRESS-509"; @@ -79,6 +280,12 @@ public class TarFileTest extends AbstractTestCase { } @Test(expected = IOException.class) + public void testParseTarWithSpecialPaxHeaders() throws IOException { + try (final TarFile tarFile = new TarFile(getPath("COMPRESS-530.tar"))) { + } + } + + @Test(expected = IOException.class) public void testParseTarWithNonNumberPaxHeaders() throws IOException { try (TarFile tarFile = new TarFile(getPath("COMPRESS-529.tar"))) { } @@ -107,4 +314,30 @@ public class TarFileTest extends AbstractTestCase { try (TarFile tarFile = new TarFile(getPath("COMPRESS-553.tar"))) { } } + + @Test + public void testCompress558() throws IOException { + final String folderName = "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/"; + final String consumerJavaName = "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/java/example/queue/exclusive/Consumer.java"; + final String producerJavaName = "apache-activemq-5.16.0/examples/openwire/advanced-scenarios/jms-example-exclusive-consumer/src/main/java/example/queue/exclusive/Producer.java"; + + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) { + tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); + TarArchiveEntry rootfolder = new TarArchiveEntry(folderName); + tos.putArchiveEntry(rootfolder); + TarArchiveEntry consumerJava = new TarArchiveEntry(consumerJavaName); + tos.putArchiveEntry(consumerJava); + TarArchiveEntry producerJava = new TarArchiveEntry(producerJavaName); + tos.putArchiveEntry(producerJava); + tos.closeArchiveEntry(); + } + final byte[] data = bos.toByteArray(); + try (final TarFile tarFile = new TarFile(data)) { + List<TarArchiveEntry> entries = tarFile.getEntries(); + assertEquals(folderName, entries.get(0).getName()); + assertEquals(consumerJavaName, entries.get(1).getName()); + assertEquals(producerJavaName, entries.get(2).getName()); + } + } }
