diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 0e8b8e278e..7a39e10bfb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -47,6 +47,7 @@ import org.gradle.api.tasks.util.PatternSet; * * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick * @see BootJar * @see BootWar */ @@ -124,6 +125,8 @@ class BootArchiveSupport { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); + Integer dirMode = jar.getDirMode(); + Integer fileMode = jar.getFileMode(); boolean includeDefaultLoader = isUsingDefaultLoader(jar); Spec requiresUnpack = this.requiresUnpack.getAsSpec(); Spec exclusions = this.exclusions.getAsExcludeSpec(); @@ -131,9 +134,9 @@ class BootArchiveSupport { Spec librarySpec = this.librarySpec; Function compressionResolver = this.compressionResolver; String encoding = jar.getMetadataCharset(); - CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, includeDefaultLoader, - layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver, - encoding, resolvedDependencies, layerResolver); + CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirMode, fileMode, + includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, + compressionResolver, encoding, resolvedDependencies, layerResolver); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 6075218160..8e3a57e435 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -87,6 +87,10 @@ class BootZipCopyAction implements CopyAction { private final boolean preserveFileTimestamps; + private final Integer dirMode; + + private final Integer fileMode; + private final boolean includeDefaultLoader; private final String layerToolsLocation; @@ -107,14 +111,16 @@ class BootZipCopyAction implements CopyAction { private final LayerResolver layerResolver; - BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, boolean includeDefaultLoader, - String layerToolsLocation, Spec requiresUnpack, Spec exclusions, - LaunchScriptConfiguration launchScript, Spec librarySpec, + BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, Integer dirMode, Integer fileMode, + boolean includeDefaultLoader, String layerToolsLocation, Spec requiresUnpack, + Spec exclusions, LaunchScriptConfiguration launchScript, Spec librarySpec, Function compressionResolver, String encoding, ResolvedDependencies resolvedDependencies, LayerResolver layerResolver) { this.output = output; this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; + this.dirMode = dirMode; + this.fileMode = fileMode; this.includeDefaultLoader = includeDefaultLoader; this.layerToolsLocation = layerToolsLocation; this.requiresUnpack = requiresUnpack; @@ -240,7 +246,7 @@ class BootZipCopyAction implements CopyAction { private void processDirectory(FileCopyDetails details) throws IOException { String name = details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(name + '/'); - prepareEntry(entry, name, getTime(details), UnixStat.FILE_FLAG | details.getMode()); + prepareEntry(entry, name, getTime(details), getFileMode(details)); this.out.putArchiveEntry(entry); this.out.closeArchiveEntry(); this.writtenDirectories.add(name); @@ -249,7 +255,7 @@ class BootZipCopyAction implements CopyAction { private void processFile(FileCopyDetails details) throws IOException { String name = details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(name); - prepareEntry(entry, name, getTime(details), UnixStat.FILE_FLAG | details.getMode()); + prepareEntry(entry, name, getTime(details), getFileMode(details)); ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details); if (compression == ZipCompression.STORED) { prepareStoredEntry(details, entry); @@ -273,7 +279,7 @@ class BootZipCopyAction implements CopyAction { String parentDirectory = getParentDirectory(name); if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) { ZipArchiveEntry entry = new ZipArchiveEntry(parentDirectory + '/'); - prepareEntry(entry, parentDirectory, time, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM); + prepareEntry(entry, parentDirectory, time, getDirMode()); this.out.putArchiveEntry(entry); this.out.closeArchiveEntry(); } @@ -304,7 +310,7 @@ class BootZipCopyAction implements CopyAction { // Always write loader entries after META-INF directory (see gh-16698) return; } - LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime()); + LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode()); this.writtenLoaderEntries = loaderEntries.writeTo(this.out); if (BootZipCopyAction.this.layerResolver != null) { for (String name : this.writtenLoaderEntries.getFiles()) { @@ -393,7 +399,7 @@ class BootZipCopyAction implements CopyAction { private void writeEntry(String name, ZipEntryContentWriter entryWriter, boolean addToLayerIndex, ZipEntryCustomizer entryCustomizer) throws IOException { ZipArchiveEntry entry = new ZipArchiveEntry(name); - prepareEntry(entry, name, getTime(), UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM); + prepareEntry(entry, name, getTime(), getFileMode()); entryCustomizer.customize(entry); this.out.putArchiveEntry(entry); entryWriter.writeTo(this.out); @@ -437,6 +443,21 @@ class BootZipCopyAction implements CopyAction { return null; } + private int getDirMode() { + return (BootZipCopyAction.this.dirMode != null) ? BootZipCopyAction.this.dirMode + : UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; + } + + private int getFileMode() { + return (BootZipCopyAction.this.fileMode != null) ? BootZipCopyAction.this.fileMode + : UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; + } + + private int getFileMode(FileCopyDetails details) { + return (BootZipCopyAction.this.fileMode != null) ? BootZipCopyAction.this.fileMode + : UnixStat.FILE_FLAG | details.getMode(); + } + } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java index 4c17cc1cbf..dd4d50894f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java @@ -24,7 +24,6 @@ import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.apache.commons.compress.archivers.zip.UnixStat; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.file.FileTreeElement; @@ -42,8 +41,14 @@ class LoaderZipEntries { private final Long entryTime; - LoaderZipEntries(Long entryTime) { + private final int dirMode; + + private final int fileMode; + + LoaderZipEntries(Long entryTime, int dirMode, int fileMode) { this.entryTime = entryTime; + this.dirMode = dirMode; + this.fileMode = fileMode; } WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { @@ -67,13 +72,13 @@ class LoaderZipEntries { } private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException { - prepareEntry(entry, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM); + prepareEntry(entry, this.dirMode); out.putArchiveEntry(entry); out.closeArchiveEntry(); } private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException { - prepareEntry(entry, UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM); + prepareEntry(entry, this.fileMode); out.putArchiveEntry(entry); copy(in, out); out.closeArchiveEntry(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index af744b2d81..fa1b22e88a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -29,6 +29,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -44,6 +45,9 @@ import java.util.jar.Manifest; import java.util.stream.Stream; import java.util.zip.ZipEntry; +import org.apache.commons.compress.archivers.zip.UnixStat; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; @@ -61,6 +65,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson * @author Madhura Bhave + * @author Scott Frederick */ abstract class AbstractBootArchiveIntegrationTests { @@ -523,6 +528,48 @@ abstract class AbstractBootArchiveIntegrationTests { } } + @TestTemplate + void defaultDirAndFileModesAreUsed() throws IOException { + BuildResult result = this.gradleBuild.build(this.taskName); + assertThat(result.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + try (ZipFile jarFile = new ZipFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { + Enumeration entries = jarFile.getEntries(); + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + if (entry.getName().startsWith("META-INF/")) { + continue; + } + if (entry.isDirectory()) { + assertEntryMode(entry, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM); + } + else { + assertEntryMode(entry, UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM); + } + } + } + } + + @TestTemplate + void dirModeAndFileModeAreApplied() throws IOException { + BuildResult result = this.gradleBuild.build(this.taskName); + assertThat(result.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + try (ZipFile jarFile = new ZipFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { + Enumeration entries = jarFile.getEntries(); + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + if (entry.getName().startsWith("META-INF/")) { + continue; + } + if (entry.isDirectory()) { + assertEntryMode(entry, 0500); + } + else { + assertEntryMode(entry, 0400); + } + } + } + } + private void copyMainClassApplication() throws IOException { copyApplication("main"); } @@ -660,4 +707,11 @@ abstract class AbstractBootArchiveIntegrationTests { return false; } + private static void assertEntryMode(ZipArchiveEntry entry, int expectedMode) { + assertThat(entry.getUnixMode()) + .withFailMessage(() -> "Expected mode " + Integer.toOctalString(expectedMode) + " for entry " + + entry.getName() + " but actual is " + Integer.toOctalString(entry.getUnixMode())) + .isEqualTo(expectedMode); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-dirModeAndFileModeAreApplied.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-dirModeAndFileModeAreApplied.gradle new file mode 100644 index 0000000000..506858a4da --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-dirModeAndFileModeAreApplied.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +tasks.named("bootJar") { + fileMode = 0400 + dirMode = 0500 + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-dirModeAndFileModeAreApplied.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-dirModeAndFileModeAreApplied.gradle new file mode 100644 index 0000000000..0e352e7393 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-dirModeAndFileModeAreApplied.gradle @@ -0,0 +1,10 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +tasks.named("bootWar") { + fileMode = 0400 + dirMode = 0500 + mainClass = 'com.example.Application' +}