From ab18901ad0ceac37476f1c02849ed151401f255b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 5 Apr 2017 12:52:58 +0100 Subject: [PATCH] Ensure that entry is completely configured before putting to the stream Previously, BootZipCopyAction would put the next entry to the stream and then, in the case of a stored entry, configure its size, CRC32 etc. This had the benefit of being able to copy the entry into the zip once, capturing its bytes for the calculation of the CRC32 as it was copied. Unfortunately, while this produced zip files that could be read by the JVM, other zip tools failed. For example, Go's zip support that's used by CloudFoundry could not unzip the archive. This commit updates BootZipCopy action to completely configure the entry before putting it to the stream. This has the downside of copying the file twice (once for the CRC32 and once to actually write it to the zip stream) but this appears to be unavoidable as we have to produce archives that can be unzipped without problems on all platforms. Closes gh-8816 --- .../tasks/bundling/BootZipCopyAction.java | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 65a4076fb1..008fe90164 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -18,7 +18,6 @@ package org.springframework.boot.gradle.tasks.bundling; import java.io.File; import java.io.FileOutputStream; -import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.function.Function; @@ -221,26 +220,29 @@ class BootZipCopyAction implements CopyAction { String relativePath = details.getRelativePath().getPathString(); ZipEntry archiveEntry = new ZipEntry(relativePath); archiveEntry.setTime(getTime(details)); - this.zipStream.putNextEntry(archiveEntry); ZipCompression compression = this.compressionType.apply(details); if (compression == ZipCompression.STORED) { - archiveEntry.setMethod(ZipEntry.STORED); - archiveEntry.setSize(details.getSize()); - archiveEntry.setCompressedSize(details.getSize()); - Crc32OutputStream crcStream = new Crc32OutputStream(this.zipStream); - details.copyTo(crcStream); - archiveEntry.setCrc(crcStream.getCrc()); - if (this.requiresUnpack.isSatisfiedBy(details)) { - archiveEntry.setComment( - "UNPACK:" + FileUtils.sha1Hash(details.getFile())); - } - } - else { - details.copyTo(this.zipStream); + prepareStoredEntry(details, archiveEntry); } + this.zipStream.putNextEntry(archiveEntry); + details.copyTo(this.zipStream); this.zipStream.closeEntry(); } + private void prepareStoredEntry(FileCopyDetailsInternal details, + ZipEntry archiveEntry) throws IOException { + archiveEntry.setMethod(ZipEntry.STORED); + archiveEntry.setSize(details.getSize()); + archiveEntry.setCompressedSize(details.getSize()); + Crc32OutputStream crcStream = new Crc32OutputStream(); + details.copyTo(crcStream); + archiveEntry.setCrc(crcStream.getCrc()); + if (this.requiresUnpack.isSatisfiedBy(details)) { + archiveEntry + .setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile())); + } + } + private long getTime(FileCopyDetails details) { return this.preserveFileTimestamps ? details.getLastModified() : GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES; @@ -249,33 +251,25 @@ class BootZipCopyAction implements CopyAction { } /** - * A {@code FilterOutputStream} that provides a CRC-32 of the data that is written to - * it. + * An {@code OutputStream} that provides a CRC-32 of the data that is written to it. */ - private static final class Crc32OutputStream extends FilterOutputStream { + private static final class Crc32OutputStream extends OutputStream { private final CRC32 crc32 = new CRC32(); - private Crc32OutputStream(OutputStream out) { - super(out); - } - @Override public void write(int b) throws IOException { this.crc32.update(b); - this.out.write(b); } @Override public void write(byte[] b) throws IOException { this.crc32.update(b); - this.out.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { this.crc32.update(b, off, len); - this.out.write(b, off, len); } private long getCrc() {