diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 644c8b642689..a31b4a93e970 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -76,6 +76,7 @@ jobs: RVT_RELEASE_TYPE: ${{ vars.COMMERCIAL && 'commercial' || 'oss' }} RVT_STAGING: ${{ inputs.staging }} RVT_VERSION: ${{ inputs.version }} + HOMEBREW_NO_SANDBOX_LINUX: "1" run: ./gradlew spring-boot-release-verification-tests:test - name: Upload Build Reports on Failure if: failure() diff --git a/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java b/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java index 41fc37b57a0f..988c52af5547 100644 --- a/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java +++ b/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java @@ -26,6 +26,7 @@ import java.io.PrintStream; import java.io.UncheckedIOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; @@ -57,6 +58,7 @@ * The {@code 'extract'} tools command. * * @author Moritz Halbritter + * @author Dongliang Xie */ class ExtractCommand extends Command { @@ -363,14 +365,18 @@ private static void withJarEntries(File file, @Nullable ManfiestWriter manfiestW } private static File assertFileIsContainedInDirectory(File directory, File file, String name) throws IOException { - String canonicalOutputPath = directory.getCanonicalPath() + File.separator; - String canonicalEntryPath = file.getCanonicalPath(); - Assert.state(canonicalEntryPath.startsWith(canonicalOutputPath), + Path canonicalOutputPath = directory.getCanonicalFile().toPath(); + Path canonicalEntryPath = file.getCanonicalFile().toPath(); + Assert.state(isFileContainedInDirectory(canonicalOutputPath, canonicalEntryPath), () -> "Entry '%s' would be written to '%s'. This is outside the output location of '%s'. Verify the contents of your archive." .formatted(name, canonicalEntryPath, canonicalOutputPath)); return file; } + private static boolean isFileContainedInDirectory(Path directory, Path file) { + return !file.equals(directory) && file.startsWith(directory); + } + @FunctionalInterface private interface EntryNameTransformer { @@ -515,9 +521,9 @@ private boolean shouldExtractLayer(String 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), + Path canonicalOutputPath = this.directory.getCanonicalFile().toPath(); + Path canonicalLayerPath = layerDirectory.getCanonicalFile().toPath(); + Assert.state(isFileContainedInDirectory(canonicalOutputPath, canonicalLayerPath), () -> "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; diff --git a/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractCommandTests.java b/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractCommandTests.java index 2f25c094d1a5..911318f9e694 100644 --- a/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractCommandTests.java +++ b/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractCommandTests.java @@ -20,11 +20,13 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.jar.Manifest; @@ -45,6 +47,7 @@ * Tests for {@link ExtractCommand}. * * @author Moritz Halbritter + * @author Dongliang Xie */ class ExtractCommandTests extends AbstractJarModeTests { @@ -370,6 +373,38 @@ void extractsOnlySelectedLayers() throws IOException { .doesNotContain("test/spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class"); } + @Test + void extractWhenDestinationIsFileSystemRoot() throws IOException { + Path layerDirectory = ExtractCommandTests.this.tempDir.toPath() + .resolve("root-output") + .resolve("dependencies") + .toAbsolutePath() + .normalize(); + Path outputRoot = layerDirectory.getRoot(); + String layerName = outputRoot.relativize(layerDirectory).toString().replace(File.separatorChar, '/'); + Layers layers = new Layers() { + + @Override + public Iterator iterator() { + return List.of(layerName).iterator(); + } + + @Override + public String getLayer(String entryName) { + return layerName; + } + + @Override + public String getApplicationLayerName() { + return layerName; + } + + }; + runCommand((context) -> new ExtractCommand(context, layers), ExtractCommandTests.this.archive, + "--destination", outputRoot.toString(), "--force", "--launcher", "--layers"); + assertThat(layerDirectory.resolve("BOOT-INF/lib/dependency-1.jar")).exists(); + } + } } diff --git a/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/LettuceConnectionConfiguration.java b/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/LettuceConnectionConfiguration.java index adf02ebb0eb7..f2f2b1e3f0bb 100644 --- a/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/LettuceConnectionConfiguration.java +++ b/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/LettuceConnectionConfiguration.java @@ -173,15 +173,13 @@ private void applyProperties(LettuceClientConfigurationBuilder builder, @Nullabl if (getProperties().getTimeout() != null) { builder.commandTimeout(getProperties().getTimeout()); } - if (getProperties().getLettuce() != null) { - DataRedisProperties.Lettuce lettuce = getProperties().getLettuce(); - if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) { - builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout()); - } - String readFrom = lettuce.getReadFrom(); - if (readFrom != null) { - builder.readFrom(getReadFrom(readFrom)); - } + DataRedisProperties.Lettuce lettuce = getProperties().getLettuce(); + if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) { + builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout()); + } + String readFrom = lettuce.getReadFrom(); + if (readFrom != null) { + builder.readFrom(getReadFrom(readFrom)); } if (StringUtils.hasText(getProperties().getClientName())) { builder.clientName(getProperties().getClientName());