Skip to content

Commit ac258a9

Browse files
committed
Merge branch '2.3.x' into 2.4.x
Closes gh-25508
2 parents 2682b38 + 1ac9b3f commit ac258a9

File tree

2 files changed

+40
-11
lines changed
  • spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src

2 files changed

+40
-11
lines changed

spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ExtractCommand.java

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,6 @@
2929

3030
import org.springframework.util.Assert;
3131
import org.springframework.util.StreamUtils;
32-
import org.springframework.util.StringUtils;
3332

3433
/**
3534
* The {@code 'extract'} tools command.
@@ -86,15 +85,18 @@ protected void run(Map<Option, String> options, List<String> parameters) {
8685
}
8786

8887
private void write(ZipInputStream zip, ZipEntry entry, File destination) throws IOException {
89-
String path = StringUtils.cleanPath(entry.getName());
90-
File file = new File(destination, path);
91-
if (file.getAbsolutePath().startsWith(destination.getAbsolutePath())) {
92-
mkParentDirs(file);
93-
try (OutputStream out = new FileOutputStream(file)) {
94-
StreamUtils.copy(zip, out);
95-
}
96-
Files.setAttribute(file.toPath(), "creationTime", entry.getCreationTime());
88+
String canonicalOutputPath = destination.getCanonicalPath() + File.separator;
89+
File file = new File(destination, entry.getName());
90+
String canonicalEntryPath = file.getCanonicalPath();
91+
Assert.state(canonicalEntryPath.startsWith(canonicalOutputPath),
92+
() -> "Entry '" + entry.getName() + "' would be written to '" + canonicalEntryPath
93+
+ "'. This is outside the output location of '" + canonicalOutputPath
94+
+ "'. Verify the contents of your archive.");
95+
mkParentDirs(file);
96+
try (OutputStream out = new FileOutputStream(file)) {
97+
StreamUtils.copy(zip, out);
9798
}
99+
Files.setAttribute(file.toPath(), "creationTime", entry.getCreationTime());
98100
}
99101

100102
private void mkParentDirs(File file) throws IOException {

spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ExtractCommandTests.java

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.Arrays;
2424
import java.util.Collections;
2525
import java.util.Iterator;
26+
import java.util.function.Consumer;
2627
import java.util.zip.ZipEntry;
2728
import java.util.zip.ZipOutputStream;
2829

@@ -41,6 +42,7 @@
4142
* Tests for {@link ExtractCommand}.
4243
*
4344
* @author Phillip Webb
45+
* @author Andy Wilkinson
4446
*/
4547
@ExtendWith(MockitoExtension.class)
4648
class ExtractCommandTests {
@@ -77,6 +79,7 @@ void runExtractsLayers() throws Exception {
7779
assertThat(new File(this.extract, "b/b/b.jar")).exists();
7880
assertThat(new File(this.extract, "c/c/c.jar")).exists();
7981
assertThat(new File(this.extract, "d")).isDirectory();
82+
assertThat(new File(this.extract.getParentFile(), "e.jar")).doesNotExist();
8083
}
8184

8285
@Test
@@ -99,6 +102,7 @@ void runWhenHasLayerParamsExtractsLimitedLayers() {
99102
assertThat(this.extract.list()).containsOnly("a", "c");
100103
assertThat(new File(this.extract, "a/a/a.jar")).exists();
101104
assertThat(new File(this.extract, "c/c/c.jar")).exists();
105+
assertThat(new File(this.extract.getParentFile(), "e.jar")).doesNotExist();
102106
}
103107

104108
@Test
@@ -114,7 +118,29 @@ void runWithJarFileContainingNoEntriesFails() throws IOException {
114118
.withMessageContaining("not compatible with layertools");
115119
}
116120

121+
@Test
122+
void runWithJarFileThatWouldWriteEntriesOutsideDestinationFails() throws IOException {
123+
this.jarFile = createJarFile("test.jar", (out) -> {
124+
try {
125+
out.putNextEntry(new ZipEntry("e/../../e.jar"));
126+
out.closeEntry();
127+
}
128+
catch (IOException ex) {
129+
throw new IllegalStateException(ex);
130+
}
131+
});
132+
given(this.context.getJarFile()).willReturn(this.jarFile);
133+
assertThatIllegalStateException()
134+
.isThrownBy(() -> this.command.run(Collections.emptyMap(), Collections.emptyList()))
135+
.withMessageContaining("Entry 'e/../../e.jar' would be written");
136+
}
137+
117138
private File createJarFile(String name) throws IOException {
139+
return createJarFile(name, (out) -> {
140+
});
141+
}
142+
143+
private File createJarFile(String name, Consumer<ZipOutputStream> streamHandler) throws IOException {
118144
File file = new File(this.temp, name);
119145
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) {
120146
out.putNextEntry(new ZipEntry("a/"));
@@ -131,6 +157,7 @@ private File createJarFile(String name) throws IOException {
131157
out.closeEntry();
132158
out.putNextEntry(new ZipEntry("d/"));
133159
out.closeEntry();
160+
streamHandler.accept(out);
134161
}
135162
return file;
136163
}

0 commit comments

Comments
 (0)