Merge branch '4.0.x'

Closes gh-50132
This commit is contained in:
Andy Wilkinson
2026-04-20 10:09:21 +01:00
2 changed files with 44 additions and 7 deletions
@@ -489,8 +489,8 @@ class ExtractCommand extends Command {
public @Nullable File resolve(String originalName, String newName) throws IOException {
String layer = this.layers.getLayer(originalName);
if (shouldExtractLayer(layer)) {
File directory = getLayerDirectory(layer);
return assertFileIsContainedInDirectory(directory, new File(directory, newName), newName);
File layerDirectory = getLayerDirectory(layer);
return assertFileIsContainedInDirectory(layerDirectory, new File(layerDirectory, newName), newName);
}
return null;
}
@@ -499,21 +499,30 @@ class ExtractCommand extends Command {
public @Nullable File resolveApplication() throws IOException {
String layer = this.layers.getApplicationLayerName();
if (shouldExtractLayer(layer)) {
File directory = getLayerDirectory(layer);
return assertFileIsContainedInDirectory(directory, new File(directory, this.applicationFilename),
this.applicationFilename);
File layerDirectory = getLayerDirectory(layer);
return assertFileIsContainedInDirectory(layerDirectory,
new File(layerDirectory, this.applicationFilename), this.applicationFilename);
}
return null;
}
private File getLayerDirectory(String layer) {
return new File(this.directory, layer);
private File getLayerDirectory(String layer) throws IOException {
return assertLayerDirectoryLocation(new File(this.directory, layer), layer);
}
private boolean shouldExtractLayer(String layer) {
return this.layersToExtract.isEmpty() || this.layersToExtract.contains(layer);
}
private File assertLayerDirectoryLocation(File layerDirectory, String layerName) throws IOException {
String canonicalOutputPath = this.directory.getCanonicalPath() + File.separator;
String canonicalLayerPath = layerDirectory.getCanonicalPath();
Assert.state(canonicalLayerPath.startsWith(canonicalOutputPath),
() -> "Layer '%s' would be written to '%s'. This is outside the output location of '%s'. Verify the contents of your archive."
.formatted(layerName, canonicalLayerPath, canonicalOutputPath));
return layerDirectory;
}
}
}
@@ -174,6 +174,34 @@ class ExtractLayersCommandTests {
.withMessageContaining("Entry 'e/../../e.jar' would be written");
}
@Test
void runWithLayerNameThatWouldWriteEntriesOutsideDestinationFails() throws Exception {
this.command = new ExtractLayersCommand(this.context, new Layers() {
@Override
public Iterator<String> iterator() {
return Arrays.asList("a", "b", "c").iterator();
}
@Override
public String getLayer(String entryName) {
return "../../outside-layer";
}
@Override
public String getApplicationLayerName() {
return "application";
}
});
this.jarFile = createJarFile("test.jar");
given(this.context.getArchiveFile()).willReturn(this.jarFile);
given(this.context.getWorkingDir()).willReturn(this.extract);
assertThatIllegalStateException()
.isThrownBy(() -> this.command.run(System.out, Collections.emptyMap(), Collections.emptyList()))
.withMessageContaining("Layer '../../outside-layer' would be written to");
}
private File createJarFile(String name) throws Exception {
return createJarFile(name, (out) -> {
});