Skip to content

Commit 2d18de5

Browse files
committed
Do not transfer mountable file when tar name is "." or ".." (docker is unable to use this files) or transferring into directory
1 parent 0654ef6 commit 2d18de5

File tree

4 files changed

+185
-43
lines changed

4 files changed

+185
-43
lines changed

core/src/main/java/org/testcontainers/images/builder/traits/FilesTrait.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.testcontainers.utility.MountableFile;
44

55
import java.io.File;
6+
import java.nio.file.Files;
67
import java.nio.file.Path;
78

89
/**
@@ -50,6 +51,17 @@ default SELF withFileFromFile(String path, File file, Integer mode) {
5051
* @return self
5152
*/
5253
default SELF withFileFromPath(String path, Path filePath, Integer mode) {
54+
final boolean fileStoredToDir = Files.isRegularFile(filePath) &&
55+
(path.endsWith("/")
56+
|| path.endsWith("/.")
57+
|| path.endsWith("/..")
58+
|| path.endsWith("./")
59+
|| path.endsWith("../")
60+
|| ".".equals(path)
61+
|| "..".equals(path));
62+
if (fileStoredToDir) {
63+
throw new IllegalArgumentException("Unable to store file '" + filePath + "' to docker path '" + path + "'");
64+
}
5365
final MountableFile mountableFile = MountableFile.forHostPath(filePath, mode);
5466
return ((SELF) this).withFileFromTransferable(path, mountableFile);
5567
}

core/src/main/java/org/testcontainers/utility/MountableFile.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ private void recursiveTar(String entryFilename, String rootPath, String itemPath
326326
tarEntry.setMode(getUnixFileMode(itemPath));
327327
tarArchive.putArchiveEntry(tarEntry);
328328

329+
log.trace("Transferring {} '{}'", sourceFile.isFile() ? "file" : "directory", sourceFile);
329330
if (sourceFile.isFile()) {
330331
Files.copy(sourceFile.toPath(), tarArchive);
331332
}
@@ -381,7 +382,7 @@ public static int getUnixFileMode(final Path path) {
381382
// Truncate mode bits for z/OS
382383
if ("OS/390".equals(SystemUtils.OS_NAME) ||
383384
"z/OS".equals(SystemUtils.OS_NAME) ||
384-
"zOS".equals(SystemUtils.OS_NAME) ) {
385+
"zOS".equals(SystemUtils.OS_NAME)) {
385386
unixMode &= TarConstants.MAXID;
386387
unixMode |= Files.isDirectory(path) ? 040000 : 0100000;
387388
}

core/src/test/java/org/testcontainers/images/builder/ImageFromDockerfileTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,63 @@
77

88
import static org.assertj.core.api.Assertions.assertThat;
99

10+
import org.junit.Rule;
11+
import org.junit.Test;
12+
import org.junit.experimental.runners.Enclosed;
13+
import org.junit.rules.ExpectedException;
14+
import org.junit.runner.RunWith;
15+
import org.junit.runners.Parameterized;
16+
17+
import java.io.File;
18+
import java.nio.file.Paths;
19+
20+
@RunWith(Enclosed.class)
1021
public class ImageFromDockerfileTest {
22+
@RunWith(Parameterized.class)
23+
public static class InvalidTarPathTests {
24+
@Rule
25+
public final ExpectedException expectedException = ExpectedException.none();
26+
27+
@Parameterized.Parameters(name = "{index} - {0}")
28+
public static String[] getTestCases() {
29+
return new String[]{
30+
"..",
31+
".",
32+
"../",
33+
"./",
34+
"xxx/",
35+
"yyy/xxx/",
36+
"/xxx/",
37+
"/yyy/xxx/",
38+
"/..",
39+
"/.",
40+
"/../",
41+
"/./",
42+
".",
43+
"..",
44+
"aa/.",
45+
"aa/..",
46+
"bb/./",
47+
"bb/../",
48+
"cc./",
49+
"cc../"
50+
};
51+
}
52+
53+
@Parameterized.Parameter
54+
public String tarPath;
55+
56+
@Test
57+
public void unableToTransferFileWithDotsToDockerDaemon() {
58+
expectedException.expect(IllegalArgumentException.class);
59+
expectedException.expectMessage("Unable to store file '" +
60+
Paths.get("src", "test", "resources", "mappable-resource", "test-resource.txt") +
61+
"' to docker path '" + tarPath + "'");
62+
63+
new ImageFromDockerfile()
64+
.withFileFromFile(tarPath, new File("src/test/resources/mappable-resource/test-resource.txt"));
65+
}
66+
}
1167

1268
@Test
1369
public void shouldAddDefaultLabels() {

core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java

Lines changed: 115 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.testcontainers.utility;
22

3+
import org.apache.commons.lang.StringUtils;
34
import org.hamcrest.Description;
45
import org.hamcrest.Matcher;
56
import org.hamcrest.core.IsCollectionContaining;
@@ -9,78 +10,150 @@
910
import org.testcontainers.images.builder.ImageFromDockerfile;
1011

1112
import java.io.File;
13+
import java.io.IOException;
14+
import java.util.ArrayList;
1215
import java.util.Arrays;
16+
import java.util.List;
1317

18+
import static org.apache.commons.lang.StringUtils.isEmpty;
1419
import static org.hamcrest.CoreMatchers.allOf;
1520
import static org.hamcrest.CoreMatchers.containsString;
21+
import static org.hamcrest.CoreMatchers.endsWith;
22+
import static org.hamcrest.Matchers.equalTo;
1623
import static org.rnorth.visibleassertions.VisibleAssertions.assertThat;
1724
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
1825

1926
public class DirectoryTarResourceTest {
20-
2127
@Test
2228
public void simpleRecursiveFileTest() {
2329
// 'src' is expected to be the project base directory, so all source code/resources should be copied in
2430
File directory = new File("src");
2531

26-
GenericContainer container = new GenericContainer(
27-
new ImageFromDockerfile()
28-
.withDockerfileFromBuilder(builder ->
29-
builder.from("alpine:3.14")
30-
.copy("/tmp/foo", "/foo")
31-
.cmd("cat /foo/test/resources/test-recursive-file.txt")
32-
.build()
33-
).withFileFromFile("/tmp/foo", directory))
34-
.withStartupCheckStrategy(new OneShotStartupCheckStrategy());
32+
try (GenericContainer container = new GenericContainer(
33+
new ImageFromDockerfile()
34+
.withDockerfileFromBuilder(builder ->
35+
builder.from("alpine:3.14")
36+
.copy("/tmp/foo", "/foo")
37+
.cmd("cat /foo/test/resources/test-recursive-file.txt")
38+
.build()
39+
).withFileFromFile("/tmp/foo", directory))
40+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())) {
3541

36-
container.start();
42+
container.start();
3743

38-
final String results = container.getLogs();
44+
final String results = container.getLogs();
3945

40-
assertTrue("The container has a file that was copied in via a recursive copy", results.contains("Used for DirectoryTarResourceTest"));
46+
assertTrue("The container has a file that was copied in via a recursive copy", results.contains("Used for DirectoryTarResourceTest"));
47+
}
4148
}
4249

4350
@Test
4451
public void simpleRecursiveFileWithPermissionTest() {
45-
GenericContainer container = new GenericContainer(
46-
new ImageFromDockerfile()
47-
.withDockerfileFromBuilder(builder ->
48-
builder.from("alpine:3.14")
49-
.copy("/tmp/foo", "/foo")
50-
.cmd("ls", "-al", "/")
51-
.build()
52-
).withFileFromFile("/tmp/foo", new File("/mappable-resource/test-resource.txt"),
53-
0754))
54-
.withStartupCheckStrategy(new OneShotStartupCheckStrategy());
55-
56-
container.start();
57-
String listing = container.getLogs();
58-
59-
assertThat("Listing shows that file is copied with mode requested.",
52+
try (GenericContainer container = new GenericContainer(
53+
new ImageFromDockerfile()
54+
.withDockerfileFromBuilder(builder ->
55+
builder.from("alpine:3.14")
56+
.copy("/tmp/foo", "/foo")
57+
.cmd("ls", "-al", "/")
58+
.build()
59+
).withFileFromFile("/tmp/foo", new File("/mappable-resource/test-resource.txt"),
60+
0754))
61+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())) {
62+
63+
container.start();
64+
String listing = container.getLogs();
65+
66+
assertThat("Listing shows that file is copied with mode requested.",
6067
Arrays.asList(listing.split("\\n")),
61-
exactlyNItems(1, allOf(containsString("-rwxr-xr--"), containsString("foo"))));
68+
exactlyOnce(allOf(containsString("-rwxr-xr--"), containsString("foo"))));
69+
}
70+
}
71+
72+
@Test
73+
public void transferFileDockerDaemon() {
74+
final File theFile = new File("src/test/resources/mappable-resource/test-resource.txt");
75+
try (GenericContainer container = new GenericContainer(
76+
new ImageFromDockerfile()
77+
.withDockerfileFromBuilder(builder ->
78+
builder.from("alpine:3.3")
79+
.copy(".", "/foo/")
80+
.cmd("ls", "-lapR", "/foo")
81+
.build()
82+
)
83+
.withFileFromFile("bar1", theFile)
84+
.withFileFromFile("./bar2", theFile)
85+
.withFileFromFile("../bar3", theFile)
86+
.withFileFromFile(".bar4", theFile)
87+
.withFileFromFile("..bar5", theFile)
88+
.withFileFromFile("xxx/../bar6", theFile)
89+
.withFileFromFile("x7/./bar7", theFile)
90+
.withFileFromFile("x8/././bar8", theFile)
91+
.withFileFromFile("x9/../../bar9", theFile))
92+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())) {
93+
94+
container.start();
95+
96+
final List<String> logLines = Arrays.asList(container.getLogs().split("\\n"));
97+
assertThat("Three groups of dirs", logLines.stream()
98+
.filter(StringUtils::isEmpty)
99+
.count(), equalTo(2L));
100+
101+
// parent dir + 2 sub dirs
102+
List<String> parentDir = null;
103+
List<String> subDir1 = null;
104+
int start = 0;
105+
for (int i = 0; i < logLines.size(); i++) {
106+
if (isEmpty(logLines.get(i))) {
107+
if (parentDir == null) {
108+
parentDir = new ArrayList<>(logLines.subList(start, i));
109+
start = i;
110+
} else if (subDir1 == null) {
111+
subDir1 = new ArrayList<>(logLines.subList(start, i));
112+
start = i;
113+
}
114+
}
115+
}
116+
List<String> subDir2 = new ArrayList<>(logLines.subList(start, logLines.size()));
117+
118+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" bar1")));
119+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" bar2")));
120+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" bar3")));
121+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" .bar4")));
122+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" ..bar5")));
123+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" bar6")));
124+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" x7/")));
125+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" x8/")));
126+
assertThat("Listing shows that file is copied.", parentDir, exactlyOnce(endsWith(" bar9")));
127+
assertThat("Listing shows that file is copied.", subDir1, exactlyOnce(endsWith(" bar7")));
128+
assertThat("Listing shows that file is copied.", subDir2, exactlyOnce(endsWith(" bar8")));
129+
}
62130
}
63131

64132
@Test
65133
public void simpleRecursiveClasspathResourceTest() {
66134
// This test combines the copying of classpath resources from JAR files with the recursive TAR approach, to allow JARed classpath resources to be copied in to an image
67135

68-
GenericContainer container = new GenericContainer(
69-
new ImageFromDockerfile()
70-
.withDockerfileFromBuilder(builder ->
71-
builder.from("alpine:3.14")
72-
.copy("/tmp/foo", "/foo")
73-
.cmd("ls -lRt /foo")
74-
.build()
75-
).withFileFromClasspath("/tmp/foo", "/recursive/dir")) // here we use /org/junit as a directory that really should exist on the classpath
76-
.withStartupCheckStrategy(new OneShotStartupCheckStrategy());
136+
try (GenericContainer container = new GenericContainer(
137+
new ImageFromDockerfile()
138+
.withDockerfileFromBuilder(builder ->
139+
builder.from("alpine:3.14")
140+
.copy("/tmp/foo", "/foo")
141+
.cmd("ls -lRt /foo")
142+
.build()
143+
).withFileFromClasspath("/tmp/foo", "/recursive/dir")) // here we use /org/junit as a directory that really should exist on the classpath
144+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())) {
77145

78-
container.start();
146+
container.start();
79147

80-
final String results = container.getLogs();
148+
final String results = container.getLogs();
149+
150+
// ExternalResource.class is known to exist in a subdirectory of /org/junit so should be successfully copied in
151+
assertTrue("The container has a file that was copied in via a recursive copy from a JAR resource", results.contains("content.txt"));
152+
}
153+
}
81154

82-
// ExternalResource.class is known to exist in a subdirectory of /org/junit so should be successfully copied in
83-
assertTrue("The container has a file that was copied in via a recursive copy from a JAR resource", results.contains("content.txt"));
155+
public static <T> Matcher<Iterable<? super T>> exactlyOnce(Matcher<? super T> elementMatcher) {
156+
return exactlyNItems(1, elementMatcher);
84157
}
85158

86159
public static <T> Matcher<Iterable<? super T>> exactlyNItems(final int n, Matcher<? super T> elementMatcher) {

0 commit comments

Comments
 (0)