Fail fast when a Zip64 jar is encountered
Previously, jars (either top-level or nested) in Zip64 format were treated as normal jar files. This would lead to a failure later on when an attempt was made to read an entry from the file. This commit updates the loader to fail fast when it encounters a Zip64 jar file. Such files are identified by the number of entries in the central directory end record being 0xFFFF. Closes gh-8735
This commit is contained in:
parent
b280e3092d
commit
456327260b
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2016 the original author or authors.
|
* Copyright 2012-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -100,8 +100,14 @@ public class JarFileArchive implements Archive {
|
||||||
if (jarEntry.getComment().startsWith(UNPACK_MARKER)) {
|
if (jarEntry.getComment().startsWith(UNPACK_MARKER)) {
|
||||||
return getUnpackedNestedArchive(jarEntry);
|
return getUnpackedNestedArchive(jarEntry);
|
||||||
}
|
}
|
||||||
JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
|
try {
|
||||||
return new JarFileArchive(jarFile);
|
JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
|
||||||
|
return new JarFileArchive(jarFile);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Failed to get nested archive for entry " + entry.getName(), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException {
|
private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2015 the original author or authors.
|
* Copyright 2012-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,6 +24,7 @@ import org.springframework.boot.loader.data.RandomAccessData;
|
||||||
* A ZIP File "End of central directory record" (EOCD).
|
* A ZIP File "End of central directory record" (EOCD).
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Andy Wilkinson
|
||||||
* @see <a href="http://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
|
* @see <a href="http://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
|
||||||
*/
|
*/
|
||||||
class CentralDirectoryEndRecord {
|
class CentralDirectoryEndRecord {
|
||||||
|
@ -118,7 +119,11 @@ class CentralDirectoryEndRecord {
|
||||||
* @return the number of records in the zip
|
* @return the number of records in the zip
|
||||||
*/
|
*/
|
||||||
public int getNumberOfRecords() {
|
public int getNumberOfRecords() {
|
||||||
return (int) Bytes.littleEndianValue(this.block, this.offset + 10, 2);
|
long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2);
|
||||||
|
if (numberOfRecords == 0xFFFF) {
|
||||||
|
throw new IllegalStateException("Zip64 archives are not supported");
|
||||||
|
}
|
||||||
|
return (int) numberOfRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2016 the original author or authors.
|
* Copyright 2012-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,20 +16,30 @@
|
||||||
|
|
||||||
package org.springframework.boot.loader.archive;
|
package org.springframework.boot.loader.archive;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
import org.springframework.boot.loader.TestJarCreator;
|
import org.springframework.boot.loader.TestJarCreator;
|
||||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link JarFileArchive}.
|
* Tests for {@link JarFileArchive}.
|
||||||
|
@ -42,6 +52,9 @@ public class JarFileArchiveTests {
|
||||||
@Rule
|
@Rule
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
private File rootJarFile;
|
private File rootJarFile;
|
||||||
|
|
||||||
private JarFileArchive archive;
|
private JarFileArchive archive;
|
||||||
|
@ -120,6 +133,48 @@ public class JarFileArchiveTests {
|
||||||
assertThat(nested.getParent()).isEqualTo(anotherNested.getParent());
|
assertThat(nested.getParent()).isEqualTo(anotherNested.getParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void zip64ArchivesAreHandledGracefully() throws IOException {
|
||||||
|
File file = this.temporaryFolder.newFile("test.jar");
|
||||||
|
FileCopyUtils.copy(writeZip64Jar(), file);
|
||||||
|
this.thrown.expectMessage(equalTo("Zip64 archives are not supported"));
|
||||||
|
new JarFileArchive(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nestedZip64ArchivesAreHandledGracefully() throws IOException {
|
||||||
|
File file = this.temporaryFolder.newFile("test.jar");
|
||||||
|
JarOutputStream output = new JarOutputStream(new FileOutputStream(file));
|
||||||
|
JarEntry zip64JarEntry = new JarEntry("nested/zip64.jar");
|
||||||
|
output.putNextEntry(zip64JarEntry);
|
||||||
|
byte[] zip64JarData = writeZip64Jar();
|
||||||
|
zip64JarEntry.setSize(zip64JarData.length);
|
||||||
|
zip64JarEntry.setCompressedSize(zip64JarData.length);
|
||||||
|
zip64JarEntry.setMethod(ZipEntry.STORED);
|
||||||
|
CRC32 crc32 = new CRC32();
|
||||||
|
crc32.update(zip64JarData);
|
||||||
|
zip64JarEntry.setCrc(crc32.getValue());
|
||||||
|
output.write(zip64JarData);
|
||||||
|
output.closeEntry();
|
||||||
|
output.close();
|
||||||
|
JarFileArchive jarFileArchive = new JarFileArchive(file);
|
||||||
|
this.thrown.expectMessage(
|
||||||
|
equalTo("Failed to get nested archive for entry nested/zip64.jar"));
|
||||||
|
jarFileArchive
|
||||||
|
.getNestedArchive(getEntriesMap(jarFileArchive).get("nested/zip64.jar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] writeZip64Jar() throws IOException {
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
JarOutputStream jarOutput = new JarOutputStream(bytes);
|
||||||
|
for (int i = 0; i < 65537; i++) {
|
||||||
|
jarOutput.putNextEntry(new JarEntry(i + ".dat"));
|
||||||
|
jarOutput.closeEntry();
|
||||||
|
}
|
||||||
|
jarOutput.close();
|
||||||
|
return bytes.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
|
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||||
Map<String, Archive.Entry> entries = new HashMap<String, Archive.Entry>();
|
Map<String, Archive.Entry> entries = new HashMap<String, Archive.Entry>();
|
||||||
for (Archive.Entry entry : archive) {
|
for (Archive.Entry entry : archive) {
|
||||||
|
|
Loading…
Reference in New Issue