Commit ab18901a authored by Andy Wilkinson's avatar Andy Wilkinson

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
parent 2f64cdfa
...@@ -18,7 +18,6 @@ package org.springframework.boot.gradle.tasks.bundling; ...@@ -18,7 +18,6 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.function.Function; import java.util.function.Function;
...@@ -221,26 +220,29 @@ class BootZipCopyAction implements CopyAction { ...@@ -221,26 +220,29 @@ class BootZipCopyAction implements CopyAction {
String relativePath = details.getRelativePath().getPathString(); String relativePath = details.getRelativePath().getPathString();
ZipEntry archiveEntry = new ZipEntry(relativePath); ZipEntry archiveEntry = new ZipEntry(relativePath);
archiveEntry.setTime(getTime(details)); archiveEntry.setTime(getTime(details));
this.zipStream.putNextEntry(archiveEntry);
ZipCompression compression = this.compressionType.apply(details); ZipCompression compression = this.compressionType.apply(details);
if (compression == ZipCompression.STORED) { if (compression == ZipCompression.STORED) {
archiveEntry.setMethod(ZipEntry.STORED); prepareStoredEntry(details, archiveEntry);
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);
} }
this.zipStream.putNextEntry(archiveEntry);
details.copyTo(this.zipStream);
this.zipStream.closeEntry(); 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) { private long getTime(FileCopyDetails details) {
return this.preserveFileTimestamps ? details.getLastModified() return this.preserveFileTimestamps ? details.getLastModified()
: GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES; : GUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES;
...@@ -249,33 +251,25 @@ class BootZipCopyAction implements CopyAction { ...@@ -249,33 +251,25 @@ class BootZipCopyAction implements CopyAction {
} }
/** /**
* A {@code FilterOutputStream} that provides a CRC-32 of the data that is written to * An {@code OutputStream} that provides a CRC-32 of the data that is written to it.
* it.
*/ */
private static final class Crc32OutputStream extends FilterOutputStream { private static final class Crc32OutputStream extends OutputStream {
private final CRC32 crc32 = new CRC32(); private final CRC32 crc32 = new CRC32();
private Crc32OutputStream(OutputStream out) {
super(out);
}
@Override @Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
this.crc32.update(b); this.crc32.update(b);
this.out.write(b);
} }
@Override @Override
public void write(byte[] b) throws IOException { public void write(byte[] b) throws IOException {
this.crc32.update(b); this.crc32.update(b);
this.out.write(b);
} }
@Override @Override
public void write(byte[] b, int off, int len) throws IOException { public void write(byte[] b, int off, int len) throws IOException {
this.crc32.update(b, off, len); this.crc32.update(b, off, len);
this.out.write(b, off, len);
} }
private long getCrc() { private long getCrc() {
......
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