From aeeb5cf1f8d75cae59068a91ae9d0b055d28054f Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 26 Sep 2023 13:49:19 -0500 Subject: [PATCH] Apply Gradle fileMode and dirMode consistently in jar and war archives Fixes gh-37496 --- .../tasks/bundling/BootArchiveSupport.java | 11 ++-- .../tasks/bundling/BootZipCopyAction.java | 37 ++++++++++--- .../tasks/bundling/LoaderZipEntries.java | 13 +++-- .../AbstractBootArchiveIntegrationTests.java | 54 +++++++++++++++++++ ...nTests-dirModeAndFileModeAreApplied.gradle | 10 ++++ ....gradle => BootJarIntegrationTests.gradle} | 0 ...nTests-dirModeAndFileModeAreApplied.gradle | 10 ++++ ....gradle => BootWarIntegrationTests.gradle} | 0 8 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-dirModeAndFileModeAreApplied.gradle rename spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/{BootJarIntegrationTests-basicBuild.gradle => BootJarIntegrationTests.gradle} (100%) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-dirModeAndFileModeAreApplied.gradle rename spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/{BootWarIntegrationTests-basicBuild.gradle => BootWarIntegrationTests.gradle} (100%) 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 a143a63647f..87224d48208 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,7 @@ import org.gradle.api.tasks.util.PatternSet; * * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick * @see BootJar * @see BootWar */ @@ -113,6 +114,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(); @@ -120,9 +123,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, layerResolver); + CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirMode, fileMode, + includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, + compressionResolver, encoding, 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 335e0eb1223..6945329e780 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 @@ -77,6 +77,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; @@ -95,14 +99,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, 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; @@ -225,7 +231,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); @@ -234,7 +240,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); @@ -255,7 +261,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(); } @@ -285,7 +291,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()) { @@ -350,7 +356,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); @@ -394,6 +400,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 4c17cc1cbff..dd4d50894ff 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 8da60564b66..8afde490273 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; @@ -45,6 +46,9 @@ import java.util.stream.Collectors; 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; @@ -62,6 +66,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson * @author Madhura Bhave + * @author Scott Frederick */ abstract class AbstractBootArchiveIntegrationTests { @@ -515,6 +520,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"); } @@ -650,4 +697,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 00000000000..506858a4da9 --- /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/BootJarIntegrationTests-basicBuild.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-basicBuild.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle 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 00000000000..0e352e73933 --- /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' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-basicBuild.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-basicBuild.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle