diff --git a/core/src/main/java/org/testcontainers/utility/MountableFile.java b/core/src/main/java/org/testcontainers/utility/MountableFile.java index 795917fffc4..cd83a81781f 100644 --- a/core/src/main/java/org/testcontainers/utility/MountableFile.java +++ b/core/src/main/java/org/testcontainers/utility/MountableFile.java @@ -301,7 +301,14 @@ private void recursiveTar(String entryFilename, String rootPath, String itemPath final File sourceRootFile = new File(rootPath).getCanonicalFile(); // e.g. /foo final String relativePathToSourceFile = sourceRootFile.toPath().relativize(sourceFile.toPath()).toFile().toString(); // e.g. /bar/baz - final TarArchiveEntry tarEntry = new TarArchiveEntry(sourceFile, entryFilename + "/" + relativePathToSourceFile); // entry filename e.g. /xyz/bar/baz + final String tarEntryFilename; + if (relativePathToSourceFile.isEmpty()) { + tarEntryFilename = entryFilename; // entry filename e.g. xyz => xyz + } else { + tarEntryFilename = entryFilename + "/" + relativePathToSourceFile; // entry filename e.g. /xyz/bar/baz => /foo/bar/baz + } + + final TarArchiveEntry tarEntry = new TarArchiveEntry(sourceFile, tarEntryFilename.replaceAll("^/", "")); // TarArchiveEntry automatically sets the mode for file/directory, but we can update to ensure that the mode is set exactly (inc executable bits) tarEntry.setMode(getUnixFileMode(itemPath)); diff --git a/core/src/test/java/org/testcontainers/utility/MountableFileTest.java b/core/src/test/java/org/testcontainers/utility/MountableFileTest.java index 9b1fbb55a53..c7dcaad2908 100644 --- a/core/src/test/java/org/testcontainers/utility/MountableFileTest.java +++ b/core/src/test/java/org/testcontainers/utility/MountableFileTest.java @@ -1,16 +1,21 @@ package org.testcontainers.utility; +import lombok.Cleanup; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.jetbrains.annotations.NotNull; import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.function.Consumer; -import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; -import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse; -import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; +import static org.rnorth.visibleassertions.VisibleAssertions.*; public class MountableFileTest { @@ -79,7 +84,7 @@ public void forHostPathWithPlus() throws Exception { @Test public void forClasspathResourceWithPermission() throws Exception { final MountableFile mountableFile = MountableFile.forClasspathResource("mappable-resource/test-resource.txt", - TEST_FILE_MODE); + TEST_FILE_MODE); performChecks(mountableFile); assertEquals("Valid file mode.", BASE_FILE_MODE | TEST_FILE_MODE, mountableFile.getFileMode()); @@ -101,6 +106,31 @@ public void forHostDirPathWithPermission() throws Exception { assertEquals("Valid dir mode.", BASE_DIR_MODE | TEST_FILE_MODE, mountableFile.getFileMode()); } + @Test + public void noTrailingSlashesInTarEntryNames() throws Exception { + final MountableFile mountableFile = MountableFile.forClasspathResource("mappable-resource/test-resource.txt"); + + @Cleanup final TarArchiveInputStream tais = intoTarArchive((taos) -> { + mountableFile.transferTo(taos, "/some/path.txt"); + mountableFile.transferTo(taos, "/path.txt"); + mountableFile.transferTo(taos, "path.txt"); + }); + + ArchiveEntry entry; + while ((entry = tais.getNextEntry()) != null) { + assertFalse("no entries should have a trailing slash", entry.getName().endsWith("/")); + } + } + + private TarArchiveInputStream intoTarArchive(Consumer consumer) throws IOException { + @Cleanup final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + @Cleanup final TarArchiveOutputStream taos = new TarArchiveOutputStream(baos); + consumer.accept(taos); + taos.close(); + + return new TarArchiveInputStream(new ByteArrayInputStream(baos.toByteArray())); + } + @SuppressWarnings("ResultOfMethodCallIgnored") @NotNull private Path createTempFile(final String name) throws IOException {