Commit f0b7e7cf authored by Andy Wilkinson's avatar Andy Wilkinson

Ensure that fat jars and wars do not corrupt UTF-8 entry names

Previously, both Repackager and the Grade plugin used the JRE's
standard ZipOutputStream when creating a fat jar or war file. This
resulted in entry names that needed UTF-8 encoding to become
corrupted.

This commit updates both to use Commons Compress'
ZipArchiveOutputStream and to configure the stream's encoding and
each entry's Unix mode. This ensures that names are encoded using
UTF-8 and can be read back in correctly by common zip tools.

Closes gh-9405
parent 885e2993
...@@ -85,6 +85,11 @@ ...@@ -85,6 +85,11 @@
<artifactId>jopt-simple</artifactId> <artifactId>jopt-simple</artifactId>
<version>4.6</version> <version>4.6</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.14</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.ivy</groupId> <groupId>org.apache.ivy</groupId>
<artifactId>ivy</artifactId> <artifactId>ivy</artifactId>
......
...@@ -38,7 +38,6 @@ dependencies { ...@@ -38,7 +38,6 @@ dependencies {
compile localGroovy() compile localGroovy()
compile gradleApi() compile gradleApi()
testCompile gradleTestKit() testCompile gradleTestKit()
testCompile 'org.apache.commons:commons-compress:1.13'
} }
jar { jar {
......
...@@ -29,6 +29,10 @@ ...@@ -29,6 +29,10 @@
<groupId>io.spring.gradle</groupId> <groupId>io.spring.gradle</groupId>
<artifactId>dependency-management-plugin</artifactId> <artifactId>dependency-management-plugin</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
......
...@@ -83,7 +83,7 @@ class BootArchiveSupport { ...@@ -83,7 +83,7 @@ class BootArchiveSupport {
CopyAction copyAction = new BootZipCopyAction(jar.getArchivePath(), CopyAction copyAction = new BootZipCopyAction(jar.getArchivePath(),
jar.isPreserveFileTimestamps(), isUsingDefaultLoader(jar), jar.isPreserveFileTimestamps(), isUsingDefaultLoader(jar),
this.requiresUnpack.getAsSpec(), this.exclusions.getAsExcludeSpec(), this.requiresUnpack.getAsSpec(), this.exclusions.getAsExcludeSpec(),
this.launchScript, this.compressionResolver); this.launchScript, this.compressionResolver, jar.getMetadataCharset());
if (!jar.isReproducibleFileOrder()) { if (!jar.isReproducibleFileOrder()) {
return copyAction; return copyAction;
} }
......
...@@ -24,10 +24,11 @@ import java.util.HashSet; ...@@ -24,10 +24,11 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
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.GradleException; import org.gradle.api.GradleException;
import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileCopyDetails;
import org.gradle.api.file.FileTreeElement; import org.gradle.api.file.FileTreeElement;
...@@ -65,10 +66,13 @@ class BootZipCopyAction implements CopyAction { ...@@ -65,10 +66,13 @@ class BootZipCopyAction implements CopyAction {
private final Function<FileCopyDetails, ZipCompression> compressionResolver; private final Function<FileCopyDetails, ZipCompression> compressionResolver;
private final String encoding;
BootZipCopyAction(File output, boolean preserveFileTimestamps, BootZipCopyAction(File output, boolean preserveFileTimestamps,
boolean includeDefaultLoader, Spec<FileTreeElement> requiresUnpack, boolean includeDefaultLoader, Spec<FileTreeElement> requiresUnpack,
Spec<FileTreeElement> exclusions, LaunchScriptConfiguration launchScript, Spec<FileTreeElement> exclusions, LaunchScriptConfiguration launchScript,
Function<FileCopyDetails, ZipCompression> compressionResolver) { Function<FileCopyDetails, ZipCompression> compressionResolver,
String encoding) {
this.output = output; this.output = output;
this.preserveFileTimestamps = preserveFileTimestamps; this.preserveFileTimestamps = preserveFileTimestamps;
this.includeDefaultLoader = includeDefaultLoader; this.includeDefaultLoader = includeDefaultLoader;
...@@ -76,16 +80,20 @@ class BootZipCopyAction implements CopyAction { ...@@ -76,16 +80,20 @@ class BootZipCopyAction implements CopyAction {
this.exclusions = exclusions; this.exclusions = exclusions;
this.launchScript = launchScript; this.launchScript = launchScript;
this.compressionResolver = compressionResolver; this.compressionResolver = compressionResolver;
this.encoding = encoding;
} }
@Override @Override
public WorkResult execute(CopyActionProcessingStream stream) { public WorkResult execute(CopyActionProcessingStream stream) {
ZipOutputStream zipStream; ZipArchiveOutputStream zipStream;
Spec<FileTreeElement> loaderEntries; Spec<FileTreeElement> loaderEntries;
try { try {
FileOutputStream fileStream = new FileOutputStream(this.output); FileOutputStream fileStream = new FileOutputStream(this.output);
writeLaunchScriptIfNecessary(fileStream); writeLaunchScriptIfNecessary(fileStream);
zipStream = new ZipOutputStream(fileStream); zipStream = new ZipArchiveOutputStream(fileStream);
if (this.encoding != null) {
zipStream.setEncoding(this.encoding);
}
loaderEntries = writeLoaderClassesIfNecessary(zipStream); loaderEntries = writeLoaderClassesIfNecessary(zipStream);
} }
catch (IOException ex) { catch (IOException ex) {
...@@ -113,25 +121,26 @@ class BootZipCopyAction implements CopyAction { ...@@ -113,25 +121,26 @@ class BootZipCopyAction implements CopyAction {
return Specs.union(loaderEntries, this.exclusions); return Specs.union(loaderEntries, this.exclusions);
} }
private Spec<FileTreeElement> writeLoaderClassesIfNecessary(ZipOutputStream out) { private Spec<FileTreeElement> writeLoaderClassesIfNecessary(
ZipArchiveOutputStream out) {
if (!this.includeDefaultLoader) { if (!this.includeDefaultLoader) {
return Specs.satisfyNone(); return Specs.satisfyNone();
} }
return writeLoaderClasses(out); return writeLoaderClasses(out);
} }
private Spec<FileTreeElement> writeLoaderClasses(ZipOutputStream out) { private Spec<FileTreeElement> writeLoaderClasses(ZipArchiveOutputStream out) {
try (ZipInputStream in = new ZipInputStream(getClass() try (ZipInputStream in = new ZipInputStream(getClass()
.getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) { .getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
Set<String> entries = new HashSet<String>(); Set<String> entries = new HashSet<String>();
ZipEntry entry; java.util.zip.ZipEntry entry;
while ((entry = in.getNextEntry()) != null) { while ((entry = in.getNextEntry()) != null) {
if (entry.isDirectory() && !entry.getName().startsWith("META-INF/")) { if (entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
writeDirectory(entry, out); writeDirectory(new ZipArchiveEntry(entry), out);
entries.add(entry.getName()); entries.add(entry.getName());
} }
else if (entry.getName().endsWith(".class")) { else if (entry.getName().endsWith(".class")) {
writeClass(entry, in, out); writeClass(new ZipArchiveEntry(entry), in, out);
} }
} }
return (element) -> { return (element) -> {
...@@ -147,26 +156,29 @@ class BootZipCopyAction implements CopyAction { ...@@ -147,26 +156,29 @@ class BootZipCopyAction implements CopyAction {
} }
} }
private void writeDirectory(ZipEntry entry, ZipOutputStream out) throws IOException { private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out)
throws IOException {
if (!this.preserveFileTimestamps) { if (!this.preserveFileTimestamps) {
entry.setTime(GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES); entry.setTime(GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES);
} }
out.putNextEntry(entry); entry.setUnixMode(UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
out.closeEntry(); out.putArchiveEntry(entry);
out.closeArchiveEntry();
} }
private void writeClass(ZipEntry entry, ZipInputStream in, ZipOutputStream out) private void writeClass(ZipArchiveEntry entry, ZipInputStream in,
throws IOException { ZipArchiveOutputStream out) throws IOException {
if (!this.preserveFileTimestamps) { if (!this.preserveFileTimestamps) {
entry.setTime(GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES); entry.setTime(GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES);
} }
out.putNextEntry(entry); entry.setUnixMode(UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
out.putArchiveEntry(entry);
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int read; int read;
while ((read = in.read(buffer)) > 0) { while ((read = in.read(buffer)) > 0) {
out.write(buffer, 0, read); out.write(buffer, 0, read);
} }
out.closeEntry(); out.closeArchiveEntry();
} }
private void writeLaunchScriptIfNecessary(FileOutputStream fileStream) { private void writeLaunchScriptIfNecessary(FileOutputStream fileStream) {
...@@ -185,7 +197,7 @@ class BootZipCopyAction implements CopyAction { ...@@ -185,7 +197,7 @@ class BootZipCopyAction implements CopyAction {
private static final class ZipStreamAction private static final class ZipStreamAction
implements CopyActionProcessingStreamAction { implements CopyActionProcessingStreamAction {
private final ZipOutputStream zipStream; private final ZipArchiveOutputStream zipStream;
private final File output; private final File output;
...@@ -197,7 +209,7 @@ class BootZipCopyAction implements CopyAction { ...@@ -197,7 +209,7 @@ class BootZipCopyAction implements CopyAction {
private final Function<FileCopyDetails, ZipCompression> compressionType; private final Function<FileCopyDetails, ZipCompression> compressionType;
private ZipStreamAction(ZipOutputStream zipStream, File output, private ZipStreamAction(ZipArchiveOutputStream zipStream, File output,
boolean preserveFileTimestamps, Spec<FileTreeElement> requiresUnpack, boolean preserveFileTimestamps, Spec<FileTreeElement> requiresUnpack,
Spec<FileTreeElement> exclusions, Spec<FileTreeElement> exclusions,
Function<FileCopyDetails, ZipCompression> compressionType) { Function<FileCopyDetails, ZipCompression> compressionType) {
...@@ -229,29 +241,31 @@ class BootZipCopyAction implements CopyAction { ...@@ -229,29 +241,31 @@ class BootZipCopyAction implements CopyAction {
} }
private void createDirectory(FileCopyDetailsInternal details) throws IOException { private void createDirectory(FileCopyDetailsInternal details) throws IOException {
ZipEntry archiveEntry = new ZipEntry( ZipArchiveEntry archiveEntry = new ZipArchiveEntry(
details.getRelativePath().getPathString() + '/'); details.getRelativePath().getPathString() + '/');
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
archiveEntry.setTime(getTime(details)); archiveEntry.setTime(getTime(details));
this.zipStream.putNextEntry(archiveEntry); this.zipStream.putArchiveEntry(archiveEntry);
this.zipStream.closeEntry(); this.zipStream.closeArchiveEntry();
} }
private void createFile(FileCopyDetailsInternal details) throws IOException { private void createFile(FileCopyDetailsInternal details) throws IOException {
String relativePath = details.getRelativePath().getPathString(); String relativePath = details.getRelativePath().getPathString();
ZipEntry archiveEntry = new ZipEntry(relativePath); ZipArchiveEntry archiveEntry = new ZipArchiveEntry(relativePath);
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
archiveEntry.setTime(getTime(details)); archiveEntry.setTime(getTime(details));
ZipCompression compression = this.compressionType.apply(details); ZipCompression compression = this.compressionType.apply(details);
if (compression == ZipCompression.STORED) { if (compression == ZipCompression.STORED) {
prepareStoredEntry(details, archiveEntry); prepareStoredEntry(details, archiveEntry);
} }
this.zipStream.putNextEntry(archiveEntry); this.zipStream.putArchiveEntry(archiveEntry);
details.copyTo(this.zipStream); details.copyTo(this.zipStream);
this.zipStream.closeEntry(); this.zipStream.closeArchiveEntry();
} }
private void prepareStoredEntry(FileCopyDetailsInternal details, private void prepareStoredEntry(FileCopyDetailsInternal details,
ZipEntry archiveEntry) throws IOException { ZipArchiveEntry archiveEntry) throws IOException {
archiveEntry.setMethod(ZipEntry.STORED); archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
archiveEntry.setSize(details.getSize()); archiveEntry.setSize(details.getSize());
archiveEntry.setCompressedSize(details.getSize()); archiveEntry.setCompressedSize(details.getSize());
Crc32OutputStream crcStream = new Crc32OutputStream(); Crc32OutputStream crcStream = new Crc32OutputStream();
......
...@@ -27,6 +27,8 @@ import java.util.List; ...@@ -27,6 +27,8 @@ import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.bundling.Jar;
...@@ -306,6 +308,27 @@ public abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> { ...@@ -306,6 +308,27 @@ public abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
} }
} }
@Test
public void allEntriesUseUnixPlatformAndUtf8NameEncoding() throws IOException {
this.task.setMainClass("com.example.Main");
this.task.setMetadataCharset("UTF-8");
File classpathFolder = this.temp.newFolder();
File resource = new File(classpathFolder, "some-resource.xml");
resource.getParentFile().mkdirs();
resource.createNewFile();
this.task.classpath(classpathFolder);
this.task.execute();
File archivePath = this.task.getArchivePath();
try (ZipFile zip = new ZipFile(archivePath)) {
Enumeration<ZipArchiveEntry> entries = zip.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
assertThat(entry.getPlatform()).isEqualTo(ZipArchiveEntry.PLATFORM_UNIX);
assertThat(entry.getGeneralPurposeBit().usesUTF8ForNames()).isTrue();
}
}
}
private T configure(T task) throws IOException { private T configure(T task) throws IOException {
AbstractArchiveTask archiveTask = task; AbstractArchiveTask archiveTask = task;
archiveTask.setBaseName("test"); archiveTask.setBaseName("test");
......
...@@ -30,6 +30,7 @@ import javax.xml.xpath.XPathExpression; ...@@ -30,6 +30,7 @@ import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin; import io.spring.gradle.dependencymanagement.DependencyManagementPlugin;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.GradleRunner;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
...@@ -114,7 +115,8 @@ public class GradleBuild implements TestRule { ...@@ -114,7 +115,8 @@ public class GradleBuild implements TestRule {
+ absolutePath("build/resources/main") + "," + absolutePath("build/resources/main") + ","
+ pathOfJarContaining(LaunchScript.class) + "," + pathOfJarContaining(LaunchScript.class) + ","
+ pathOfJarContaining(ClassVisitor.class) + "," + pathOfJarContaining(ClassVisitor.class) + ","
+ pathOfJarContaining(DependencyManagementPlugin.class); + pathOfJarContaining(DependencyManagementPlugin.class) + ","
+ pathOfJarContaining(ArchiveEntry.class);
} }
private String absolutePath(String path) { private String absolutePath(String path) {
......
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId> <artifactId>spring-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<!-- Provided --> <!-- Provided -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
......
...@@ -37,11 +37,14 @@ import java.util.Set; ...@@ -37,11 +37,14 @@ import java.util.Set;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarInputStream; import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.UnixStat;
/** /**
* Writes JAR content, ensuring valid directory entries are always create and duplicate * Writes JAR content, ensuring valid directory entries are always create and duplicate
* items are ignored. * items are ignored.
...@@ -55,7 +58,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -55,7 +58,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
private static final int BUFFER_SIZE = 32 * 1024; private static final int BUFFER_SIZE = 32 * 1024;
private final JarOutputStream jarOutput; private final JarArchiveOutputStream jarOutput;
private final Set<String> writtenEntries = new HashSet<>(); private final Set<String> writtenEntries = new HashSet<>();
...@@ -83,7 +86,8 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -83,7 +86,8 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
fileOutputStream.write(launchScript.toByteArray()); fileOutputStream.write(launchScript.toByteArray());
setExecutableFilePermission(file); setExecutableFilePermission(file);
} }
this.jarOutput = new JarOutputStream(fileOutputStream); this.jarOutput = new JarArchiveOutputStream(fileOutputStream);
this.jarOutput.setEncoding("UTF-8");
} }
private void setExecutableFilePermission(File file) { private void setExecutableFilePermission(File file) {
...@@ -105,7 +109,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -105,7 +109,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
* @throws IOException of the manifest cannot be written * @throws IOException of the manifest cannot be written
*/ */
public void writeManifest(final Manifest manifest) throws IOException { public void writeManifest(final Manifest manifest) throws IOException {
JarEntry entry = new JarEntry("META-INF/MANIFEST.MF"); JarArchiveEntry entry = new JarArchiveEntry("META-INF/MANIFEST.MF");
writeEntry(entry, new EntryWriter() { writeEntry(entry, new EntryWriter() {
@Override @Override
public void write(OutputStream outputStream) throws IOException { public void write(OutputStream outputStream) throws IOException {
...@@ -127,12 +131,12 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -127,12 +131,12 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
throws IOException { throws IOException {
Enumeration<JarEntry> entries = jarFile.entries(); Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement(); JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement());
setUpStoredEntryIfNecessary(jarFile, entry); setUpStoredEntryIfNecessary(jarFile, entry);
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream( try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(
jarFile.getInputStream(entry))) { jarFile.getInputStream(entry))) {
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream, true); EntryWriter entryWriter = new InputStreamEntryWriter(inputStream, true);
JarEntry transformedEntry = entryTransformer.transform(entry); JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
if (transformedEntry != null) { if (transformedEntry != null) {
writeEntry(transformedEntry, entryWriter); writeEntry(transformedEntry, entryWriter);
} }
...@@ -140,7 +144,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -140,7 +144,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
} }
} }
private void setUpStoredEntryIfNecessary(JarFile jarFile, JarEntry entry) private void setUpStoredEntryIfNecessary(JarFile jarFile, JarArchiveEntry entry)
throws IOException { throws IOException {
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream( try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(
jarFile.getInputStream(entry))) { jarFile.getInputStream(entry))) {
...@@ -158,7 +162,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -158,7 +162,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
*/ */
@Override @Override
public void writeEntry(String entryName, InputStream inputStream) throws IOException { public void writeEntry(String entryName, InputStream inputStream) throws IOException {
JarEntry entry = new JarEntry(entryName); JarArchiveEntry entry = new JarArchiveEntry(entryName);
writeEntry(entry, new InputStreamEntryWriter(inputStream, true)); writeEntry(entry, new InputStreamEntryWriter(inputStream, true));
} }
...@@ -171,7 +175,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -171,7 +175,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
public void writeNestedLibrary(String destination, Library library) public void writeNestedLibrary(String destination, Library library)
throws IOException { throws IOException {
File file = library.getFile(); File file = library.getFile();
JarEntry entry = new JarEntry(destination + library.getName()); JarArchiveEntry entry = new JarArchiveEntry(destination + library.getName());
entry.setTime(getNestedLibraryTime(file)); entry.setTime(getNestedLibraryTime(file));
if (library.isUnpackRequired()) { if (library.isUnpackRequired()) {
entry.setComment("UNPACK:" + FileUtils.sha1Hash(file)); entry.setComment("UNPACK:" + FileUtils.sha1Hash(file));
...@@ -225,7 +229,8 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -225,7 +229,8 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
JarEntry entry; JarEntry entry;
while ((entry = inputStream.getNextJarEntry()) != null) { while ((entry = inputStream.getNextJarEntry()) != null) {
if (entry.getName().endsWith(".class")) { if (entry.getName().endsWith(".class")) {
writeEntry(entry, new InputStreamEntryWriter(inputStream, false)); writeEntry(new JarArchiveEntry(entry),
new InputStreamEntryWriter(inputStream, false));
} }
} }
inputStream.close(); inputStream.close();
...@@ -247,24 +252,29 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -247,24 +252,29 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
* @param entryWriter the entry writer or {@code null} if there is no content * @param entryWriter the entry writer or {@code null} if there is no content
* @throws IOException in case of I/O errors * @throws IOException in case of I/O errors
*/ */
private void writeEntry(JarEntry entry, EntryWriter entryWriter) throws IOException { private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter)
throws IOException {
String parent = entry.getName(); String parent = entry.getName();
if (parent.endsWith("/")) { if (parent.endsWith("/")) {
parent = parent.substring(0, parent.length() - 1); parent = parent.substring(0, parent.length() - 1);
entry.setUnixMode(UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
}
else {
entry.setUnixMode(UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
} }
if (parent.lastIndexOf("/") != -1) { if (parent.lastIndexOf("/") != -1) {
parent = parent.substring(0, parent.lastIndexOf("/") + 1); parent = parent.substring(0, parent.lastIndexOf("/") + 1);
if (parent.length() > 0) { if (parent.length() > 0) {
writeEntry(new JarEntry(parent), null); writeEntry(new JarArchiveEntry(parent), null);
} }
} }
if (this.writtenEntries.add(entry.getName())) { if (this.writtenEntries.add(entry.getName())) {
this.jarOutput.putNextEntry(entry); this.jarOutput.putArchiveEntry(entry);
if (entryWriter != null) { if (entryWriter != null) {
entryWriter.write(this.jarOutput); entryWriter.write(this.jarOutput);
} }
this.jarOutput.closeEntry(); this.jarOutput.closeArchiveEntry();
} }
} }
...@@ -393,7 +403,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -393,7 +403,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
} }
} }
public void setupStoredEntry(JarEntry entry) { public void setupStoredEntry(JarArchiveEntry entry) {
entry.setSize(this.size); entry.setSize(this.size);
entry.setCompressedSize(this.size); entry.setCompressedSize(this.size);
entry.setCrc(this.crc.getValue()); entry.setCrc(this.crc.getValue());
...@@ -408,7 +418,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -408,7 +418,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
*/ */
interface EntryTransformer { interface EntryTransformer {
JarEntry transform(JarEntry jarEntry); JarArchiveEntry transform(JarArchiveEntry jarEntry);
} }
...@@ -418,7 +428,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { ...@@ -418,7 +428,7 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
private static final class IdentityEntryTransformer implements EntryTransformer { private static final class IdentityEntryTransformer implements EntryTransformer {
@Override @Override
public JarEntry transform(JarEntry jarEntry) { public JarArchiveEntry transform(JarArchiveEntry jarEntry) {
return jarEntry; return jarEntry;
} }
......
...@@ -25,10 +25,11 @@ import java.util.HashSet; ...@@ -25,10 +25,11 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.springframework.boot.loader.tools.JarWriter.EntryTransformer; import org.springframework.boot.loader.tools.JarWriter.EntryTransformer;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -407,7 +408,7 @@ public class Repackager { ...@@ -407,7 +408,7 @@ public class Repackager {
} }
@Override @Override
public JarEntry transform(JarEntry entry) { public JarArchiveEntry transform(JarArchiveEntry entry) {
if (entry.getName().equals("META-INF/INDEX.LIST")) { if (entry.getName().equals("META-INF/INDEX.LIST")) {
return null; return null;
} }
...@@ -416,7 +417,8 @@ public class Repackager { ...@@ -416,7 +417,8 @@ public class Repackager {
|| entry.getName().startsWith("BOOT-INF/")) { || entry.getName().startsWith("BOOT-INF/")) {
return entry; return entry;
} }
JarEntry renamedEntry = new JarEntry(this.namePrefix + entry.getName()); JarArchiveEntry renamedEntry = new JarArchiveEntry(
this.namePrefix + entry.getName());
renamedEntry.setTime(entry.getTime()); renamedEntry.setTime(entry.getTime());
renamedEntry.setSize(entry.getSize()); renamedEntry.setSize(entry.getSize());
renamedEntry.setMethod(entry.getMethod()); renamedEntry.setMethod(entry.getMethod());
......
...@@ -22,12 +22,15 @@ import java.io.IOException; ...@@ -22,12 +22,15 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermission;
import java.util.Calendar; import java.util.Calendar;
import java.util.Enumeration;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
...@@ -595,6 +598,23 @@ public class RepackagerTests { ...@@ -595,6 +598,23 @@ public class RepackagerTests {
} }
} }
@Test
public void allEntriesUseUnixPlatformAndUtf8NameEncoding() throws IOException {
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
File source = this.testJarFile.getFile();
File dest = this.temporaryFolder.newFile("dest.jar");
Repackager repackager = new Repackager(source);
repackager.repackage(dest, NO_LIBRARIES);
try (ZipFile zip = new ZipFile(dest)) {
Enumeration<ZipArchiveEntry> entries = zip.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
assertThat(entry.getPlatform()).isEqualTo(ZipArchiveEntry.PLATFORM_UNIX);
assertThat(entry.getGeneralPurposeBit().usesUTF8ForNames()).isTrue();
}
}
}
private boolean hasLauncherClasses(File file) throws IOException { private boolean hasLauncherClasses(File file) throws IOException {
return hasEntry(file, "org/springframework/boot/") return hasEntry(file, "org/springframework/boot/")
&& hasEntry(file, "org/springframework/boot/loader/JarLauncher.class"); && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment