Merge branch '3.5.x'
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:24], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:24], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
Closes gh-46204
This commit is contained in:
commit
1f03576647
|
@ -22,10 +22,13 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HexFormat;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -350,7 +353,7 @@ class BootZipCopyAction implements CopyAction {
|
||||||
String name = location + library.getName();
|
String name = location + library.getName();
|
||||||
writeEntry(name, ZipEntryContentWriter.fromInputStream(library.openStream()), false, (entry) -> {
|
writeEntry(name, ZipEntryContentWriter.fromInputStream(library.openStream()), false, (entry) -> {
|
||||||
try (InputStream in = library.openStream()) {
|
try (InputStream in = library.openStream()) {
|
||||||
prepareStoredEntry(library.openStream(), entry);
|
prepareStoredEntry(library.openStream(), false, entry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (BootZipCopyAction.this.layerResolver != null) {
|
if (BootZipCopyAction.this.layerResolver != null) {
|
||||||
|
@ -450,14 +453,13 @@ class BootZipCopyAction implements CopyAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
||||||
prepareStoredEntry(details.open(), archiveEntry);
|
prepareStoredEntry(details.open(), BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details),
|
||||||
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
|
archiveEntry);
|
||||||
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException {
|
private void prepareStoredEntry(InputStream input, boolean unpack, ZipArchiveEntry archiveEntry)
|
||||||
new CrcAndSize(input).setUpStoredEntry(archiveEntry);
|
throws IOException {
|
||||||
|
new StoredEntryPreparator(input, unpack).prepareStoredEntry(archiveEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long getTime() {
|
private Long getTime() {
|
||||||
|
@ -569,36 +571,55 @@ class BootZipCopyAction implements CopyAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data holder for CRC and Size.
|
* Prepares a {@link ZipEntry#STORED stored} {@link ZipArchiveEntry entry} with CRC
|
||||||
|
* and size information. Also adds an {@code UNPACK} comment, if needed.
|
||||||
*/
|
*/
|
||||||
private static class CrcAndSize {
|
private static class StoredEntryPreparator {
|
||||||
|
|
||||||
private static final int BUFFER_SIZE = 32 * 1024;
|
private static final int BUFFER_SIZE = 32 * 1024;
|
||||||
|
|
||||||
|
private final MessageDigest messageDigest;
|
||||||
|
|
||||||
private final CRC32 crc = new CRC32();
|
private final CRC32 crc = new CRC32();
|
||||||
|
|
||||||
private long size;
|
private long size;
|
||||||
|
|
||||||
CrcAndSize(InputStream inputStream) throws IOException {
|
StoredEntryPreparator(InputStream inputStream, boolean unpack) throws IOException {
|
||||||
|
this.messageDigest = (unpack) ? sha1Digest() : null;
|
||||||
try (inputStream) {
|
try (inputStream) {
|
||||||
load(inputStream);
|
load(inputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MessageDigest sha1Digest() {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance("SHA-1");
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void load(InputStream inputStream) throws IOException {
|
private void load(InputStream inputStream) throws IOException {
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
this.crc.update(buffer, 0, bytesRead);
|
this.crc.update(buffer, 0, bytesRead);
|
||||||
|
if (this.messageDigest != null) {
|
||||||
|
this.messageDigest.update(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
this.size += bytesRead;
|
this.size += bytesRead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUpStoredEntry(ZipArchiveEntry entry) {
|
void prepareStoredEntry(ZipArchiveEntry 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());
|
||||||
entry.setMethod(ZipEntry.STORED);
|
entry.setMethod(ZipEntry.STORED);
|
||||||
|
if (this.messageDigest != null) {
|
||||||
|
entry.setComment("UNPACK:" + HexFormat.of().formatHex(this.messageDigest.digest()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.gradle.testkit.runner.TaskOutcome;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
|
|
||||||
import org.springframework.boot.gradle.junit.GradleCompatibility;
|
import org.springframework.boot.gradle.junit.GradleCompatibility;
|
||||||
import org.springframework.boot.loader.tools.FileUtils;
|
import org.springframework.boot.testsupport.FileUtils;
|
||||||
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
|
@ -53,8 +53,8 @@ import org.gradle.testkit.runner.TaskOutcome;
|
||||||
import org.junit.jupiter.api.Assumptions;
|
import org.junit.jupiter.api.Assumptions;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
|
|
||||||
import org.springframework.boot.loader.tools.FileUtils;
|
|
||||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||||
|
import org.springframework.boot.testsupport.FileUtils;
|
||||||
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||||
import org.springframework.util.FileSystemUtils;
|
import org.springframework.util.FileSystemUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
|
@ -18,17 +18,18 @@ package org.springframework.boot.loader.tools;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.HexFormat;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
|
@ -40,6 +41,7 @@ import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
|
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.zip.UnixStat;
|
import org.apache.commons.compress.archivers.zip.UnixStat;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for JAR writers.
|
* Abstract base class for JAR writers.
|
||||||
|
@ -97,20 +99,21 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
|
|
||||||
private void writeEntry(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
|
private void writeEntry(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
|
||||||
JarArchiveEntry entry, Library library) throws IOException {
|
JarArchiveEntry entry, Library library) throws IOException {
|
||||||
setUpEntry(jarFile, entry);
|
setUpEntry(jarFile, entry, unpackHandler);
|
||||||
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
|
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
|
||||||
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
|
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
|
||||||
JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
|
JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
|
||||||
if (transformedEntry != null) {
|
if (transformedEntry != null) {
|
||||||
writeEntry(transformedEntry, library, entryWriter, unpackHandler);
|
writeEntry(transformedEntry, library, entryWriter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpEntry(JarFile jarFile, JarArchiveEntry entry) throws IOException {
|
private void setUpEntry(JarFile jarFile, JarArchiveEntry entry, UnpackHandler unpackHandler) throws IOException {
|
||||||
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
|
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
|
||||||
if (inputStream.hasZipHeader() && entry.getMethod() != ZipEntry.STORED) {
|
if (inputStream.hasZipHeader() && entry.getMethod() != ZipEntry.STORED) {
|
||||||
new CrcAndSize(inputStream).setupStoredEntry(entry);
|
new StoredEntryPreparator(inputStream, unpackHandler.requiresUnpack(entry.getName()))
|
||||||
|
.prepareStoredEntry(entry);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
entry.setCompressedSize(-1);
|
entry.setCompressedSize(-1);
|
||||||
|
@ -151,9 +154,10 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
public void writeNestedLibrary(String location, Library library) throws IOException {
|
public void writeNestedLibrary(String location, Library library) throws IOException {
|
||||||
JarArchiveEntry entry = new JarArchiveEntry(location + library.getName());
|
JarArchiveEntry entry = new JarArchiveEntry(location + library.getName());
|
||||||
entry.setTime(getNestedLibraryTime(library));
|
entry.setTime(getNestedLibraryTime(library));
|
||||||
new CrcAndSize(library::openStream).setupStoredEntry(entry);
|
new StoredEntryPreparator(library.openStream(), new LibraryUnpackHandler(library).requiresUnpack(location))
|
||||||
|
.prepareStoredEntry(entry);
|
||||||
try (InputStream inputStream = library.openStream()) {
|
try (InputStream inputStream = library.openStream()) {
|
||||||
writeEntry(entry, library, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library));
|
writeEntry(entry, library, new InputStreamEntryWriter(inputStream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +244,7 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException {
|
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException {
|
||||||
writeEntry(entry, null, entryWriter, UnpackHandler.NEVER);
|
writeEntry(entry, null, entryWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,11 +253,9 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
* @param entry the entry to write
|
* @param entry the entry to write
|
||||||
* @param library the library for the entry or {@code null}
|
* @param library the library for the entry or {@code null}
|
||||||
* @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
|
||||||
* @param unpackHandler handles possible unpacking for the entry
|
|
||||||
* @throws IOException in case of I/O errors
|
* @throws IOException in case of I/O errors
|
||||||
*/
|
*/
|
||||||
private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter,
|
private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter) throws IOException {
|
||||||
UnpackHandler unpackHandler) throws IOException {
|
|
||||||
String name = entry.getName();
|
String name = entry.getName();
|
||||||
if (this.writtenEntries.add(name)) {
|
if (this.writtenEntries.add(name)) {
|
||||||
writeParentDirectoryEntries(name);
|
writeParentDirectoryEntries(name);
|
||||||
|
@ -263,7 +265,6 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
entryWriter = SizeCalculatingEntryWriter.get(entryWriter);
|
entryWriter = SizeCalculatingEntryWriter.get(entryWriter);
|
||||||
entry.setSize(entryWriter.size());
|
entry.setSize(entryWriter.size());
|
||||||
}
|
}
|
||||||
entryWriter = addUnpackCommentIfNecessary(entry, entryWriter, unpackHandler);
|
|
||||||
updateLayerIndex(entry, library);
|
updateLayerIndex(entry, library);
|
||||||
writeToArchive(entry, entryWriter);
|
writeToArchive(entry, entryWriter);
|
||||||
}
|
}
|
||||||
|
@ -283,22 +284,11 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
while (parent.lastIndexOf('/') != -1) {
|
while (parent.lastIndexOf('/') != -1) {
|
||||||
parent = parent.substring(0, parent.lastIndexOf('/'));
|
parent = parent.substring(0, parent.lastIndexOf('/'));
|
||||||
if (!parent.isEmpty()) {
|
if (!parent.isEmpty()) {
|
||||||
writeEntry(new JarArchiveEntry(parent + "/"), null, null, UnpackHandler.NEVER);
|
writeEntry(new JarArchiveEntry(parent + "/"), null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntryWriter addUnpackCommentIfNecessary(JarArchiveEntry entry, EntryWriter entryWriter,
|
|
||||||
UnpackHandler unpackHandler) throws IOException {
|
|
||||||
if (entryWriter == null || !unpackHandler.requiresUnpack(entry.getName())) {
|
|
||||||
return entryWriter;
|
|
||||||
}
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
entryWriter.write(output);
|
|
||||||
entry.setComment("UNPACK:" + unpackHandler.sha1Hash(entry.getName()));
|
|
||||||
return new InputStreamEntryWriter(new ByteArrayInputStream(output.toByteArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link EntryWriter} that writes content from an {@link InputStream}.
|
* {@link EntryWriter} that writes content from an {@link InputStream}.
|
||||||
*/
|
*/
|
||||||
|
@ -323,22 +313,33 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data holder for CRC and Size.
|
* Prepares a {@link ZipEntry#STORED stored} {@link ZipArchiveEntry entry} with CRC
|
||||||
|
* and size information. Also adds an {@code UNPACK} comment, if needed.
|
||||||
*/
|
*/
|
||||||
private static class CrcAndSize {
|
private static class StoredEntryPreparator {
|
||||||
|
|
||||||
|
private static final int BUFFER_SIZE = 32 * 1024;
|
||||||
|
|
||||||
|
private final MessageDigest messageDigest;
|
||||||
|
|
||||||
private final CRC32 crc = new CRC32();
|
private final CRC32 crc = new CRC32();
|
||||||
|
|
||||||
private long size;
|
private long size;
|
||||||
|
|
||||||
CrcAndSize(InputStreamSupplier supplier) throws IOException {
|
StoredEntryPreparator(InputStream inputStream, boolean unpack) throws IOException {
|
||||||
try (InputStream inputStream = supplier.openStream()) {
|
this.messageDigest = (unpack) ? sha1Digest() : null;
|
||||||
|
try (inputStream) {
|
||||||
load(inputStream);
|
load(inputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CrcAndSize(InputStream inputStream) throws IOException {
|
private static MessageDigest sha1Digest() {
|
||||||
load(inputStream);
|
try {
|
||||||
|
return MessageDigest.getInstance("SHA-1");
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load(InputStream inputStream) throws IOException {
|
private void load(InputStream inputStream) throws IOException {
|
||||||
|
@ -346,15 +347,21 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
this.crc.update(buffer, 0, bytesRead);
|
this.crc.update(buffer, 0, bytesRead);
|
||||||
|
if (this.messageDigest != null) {
|
||||||
|
this.messageDigest.update(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
this.size += bytesRead;
|
this.size += bytesRead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupStoredEntry(JarArchiveEntry entry) {
|
void prepareStoredEntry(ZipArchiveEntry 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());
|
||||||
entry.setMethod(ZipEntry.STORED);
|
entry.setMethod(ZipEntry.STORED);
|
||||||
|
if (this.messageDigest != null) {
|
||||||
|
entry.setComment("UNPACK:" + HexFormat.of().formatHex(this.messageDigest.digest()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -381,24 +388,10 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
*/
|
*/
|
||||||
interface UnpackHandler {
|
interface UnpackHandler {
|
||||||
|
|
||||||
UnpackHandler NEVER = new UnpackHandler() {
|
UnpackHandler NEVER = (name) -> false;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresUnpack(String name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String sha1Hash(String name) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
boolean requiresUnpack(String name);
|
boolean requiresUnpack(String name);
|
||||||
|
|
||||||
String sha1Hash(String name) throws IOException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -417,11 +410,6 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
|
||||||
return this.library.isUnpackRequired();
|
return this.library.isUnpackRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String sha1Hash(String name) throws IOException {
|
|
||||||
return Digest.sha1(this.library::openStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,16 +55,6 @@ public abstract class FileUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a SHA-1 Hash for a given file.
|
|
||||||
* @param file the file to hash
|
|
||||||
* @return the hash value as a String
|
|
||||||
* @throws IOException if the file cannot be read
|
|
||||||
*/
|
|
||||||
public static String sha1Hash(File file) throws IOException {
|
|
||||||
return Digest.sha1(InputStreamSupplier.forFile(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if the given jar file has been signed.
|
* Returns {@code true} if the given jar file has been signed.
|
||||||
* @param file the file to check
|
* @param file the file to check
|
||||||
|
|
|
@ -600,13 +600,6 @@ public abstract class Packager {
|
||||||
return library != null && library.isUnpackRequired();
|
return library != null && library.isUnpackRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String sha1Hash(String name) throws IOException {
|
|
||||||
Library library = PackagedLibraries.this.libraries.get(name);
|
|
||||||
Assert.notNull(library, () -> "No library found for entry name '" + name + "'");
|
|
||||||
return Digest.sha1(library::openStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
|
@ -94,15 +93,6 @@ class FileUtilsTests {
|
||||||
assertThat(file).exists();
|
assertThat(file).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void hash() throws Exception {
|
|
||||||
File file = new File(this.tempDir, "file");
|
|
||||||
try (OutputStream outputStream = new FileOutputStream(file)) {
|
|
||||||
outputStream.write(new byte[] { 1, 2, 3 });
|
|
||||||
}
|
|
||||||
assertThat(FileUtils.sha1Hash(file)).isEqualTo("7037807198c22a7d2b0807371d763779a84fdfcf");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void isSignedJarFileWhenSignedReturnsTrue() throws IOException {
|
void isSignedJarFileWhenSignedReturnsTrue() throws IOException {
|
||||||
Manifest manifest = new Manifest(getClass().getResourceAsStream("signed-manifest.mf"));
|
Manifest manifest = new Manifest(getClass().getResourceAsStream("signed-manifest.mf"));
|
||||||
|
|
|
@ -28,8 +28,8 @@ import java.util.jar.JarFile;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.springframework.boot.loader.tools.FileUtils;
|
|
||||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||||
|
import org.springframework.boot.testsupport.FileUtils;
|
||||||
import org.springframework.util.FileSystemUtils;
|
import org.springframework.util.FileSystemUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
|
@ -29,8 +29,8 @@ import java.util.jar.JarFile;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.springframework.boot.loader.tools.FileUtils;
|
|
||||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||||
|
import org.springframework.boot.testsupport.FileUtils;
|
||||||
import org.springframework.util.FileSystemUtils;
|
import org.springframework.util.FileSystemUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
|
@ -14,36 +14,43 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.loader.tools;
|
package org.springframework.boot.testsupport;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.DigestInputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class used to calculate digests.
|
* Utilities when working with {@link File files}.
|
||||||
*
|
*
|
||||||
|
* @author Dave Syer
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
final class Digest {
|
public abstract class FileUtils {
|
||||||
|
|
||||||
private Digest() {
|
private static final int BUFFER_SIZE = 32 * 1024;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the SHA-1 digest from the supplied stream.
|
* Generate a SHA-1 Hash for a given file.
|
||||||
* @param supplier the stream supplier
|
* @param file the file to hash
|
||||||
* @return the SHA-1 digest
|
* @return the hash value as a String
|
||||||
* @throws IOException on IO error
|
* @throws IOException if the file cannot be read
|
||||||
*/
|
*/
|
||||||
static String sha1(InputStreamSupplier supplier) throws IOException {
|
public static String sha1Hash(File file) throws IOException {
|
||||||
try {
|
try {
|
||||||
try (DigestInputStream inputStream = new DigestInputStream(supplier.openStream(),
|
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
|
||||||
MessageDigest.getInstance("SHA-1"))) {
|
try (InputStream inputStream = new FileInputStream(file)) {
|
||||||
inputStream.readAllBytes();
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
return HexFormat.of().formatHex(inputStream.getMessageDigest().digest());
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
messageDigest.update(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return HexFormat.of().formatHex(messageDigest.digest());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (NoSuchAlgorithmException ex) {
|
catch (NoSuchAlgorithmException ex) {
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.testsupport;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link FileUtils}.
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class FileUtilsTests {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
File tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hash() throws Exception {
|
||||||
|
File file = new File(this.tempDir, "file");
|
||||||
|
try (OutputStream outputStream = new FileOutputStream(file)) {
|
||||||
|
outputStream.write(new byte[] { 1, 2, 3 });
|
||||||
|
}
|
||||||
|
assertThat(FileUtils.sha1Hash(file)).isEqualTo("7037807198c22a7d2b0807371d763779a84fdfcf");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue