Fix handling of Zip64 jar files larger than 4,294,967,295 bytes
Previously, a Zip64 jar file was identified by the number of entries in the central directory being 0xFFFF. This value indicates that there the number of entries is too big for the 2-byte field. However, a jar may be in Zip64 format due to it exceeding the Zip format's maximum size rather than its maximum number of entries so this field cannot be used as a reliable indicator. The Zip specification doesn't require any of the fields of the end of central directory record to have a value of 0xFFFF (2-byte fields) or 0xFFFFFFFF (4-byte fields) when using Zip64 format so we need to take a different approach. Additionally, a number of places in the code assumed that an entry's offset would always be available from the central directory file header directly. This assumption did not hold true when the jar was a Zip64 archive due to its size as the offset's value would be 0xFFFFFFF indicating that it should be read from the Zip64 extended information field within the header's extra field instead. This commit updates the Zip64 detection to look for the Zip64 end of central directory locator instead. If present, it begins 20 bytes before the beginning of the end of central directory record. Its first four bytes are always 0x07064b50. The code that reads the local header offset has also been updated to refer to the Zip64 extended information field when the offset is too large to fit in the 4-byte field in the central directory file header. To allow greater-than-4-byte offsets to be handled, a number of fields, method parameters, and local variables have had their type changed from an int to a long. Fixes gh-27822
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -97,7 +97,7 @@ class CentralDirectoryParserTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) {
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
|
||||
this.headers.add(fileHeader.clone());
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class CentralDirectoryParserTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) {
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
|
||||
this.invocations.add("visitFileHeader");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -35,6 +35,7 @@ import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
@@ -46,6 +47,7 @@ import java.util.zip.ZipFile;
|
||||
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -563,7 +565,7 @@ class JarFileTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void zip64JarCanBeRead() throws Exception {
|
||||
void zip64JarThatExceedsZipEntryLimitCanBeRead() throws Exception {
|
||||
File zip64Jar = new File(this.tempDir, "zip64.jar");
|
||||
FileCopyUtils.copy(zip64Jar(), zip64Jar);
|
||||
try (JarFile zip64JarFile = new JarFile(zip64Jar)) {
|
||||
@@ -577,6 +579,39 @@ class JarFileTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void zip64JarThatExceedsZipSizeLimitCanBeRead() throws Exception {
|
||||
Assumptions.assumeTrue(this.tempDir.getFreeSpace() > 6 * 1024 * 1024 * 1024, "Insufficient disk space");
|
||||
File zip64Jar = new File(this.tempDir, "zip64.jar");
|
||||
File entry = new File(this.tempDir, "entry.dat");
|
||||
CRC32 crc32 = new CRC32();
|
||||
try (FileOutputStream entryOut = new FileOutputStream(entry)) {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
new Random().nextBytes(data);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
entryOut.write(data);
|
||||
crc32.update(data);
|
||||
}
|
||||
}
|
||||
try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(zip64Jar))) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
JarEntry storedEntry = new JarEntry("huge-" + i);
|
||||
storedEntry.setSize(entry.length());
|
||||
storedEntry.setCompressedSize(entry.length());
|
||||
storedEntry.setCrc(crc32.getValue());
|
||||
storedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutput.putNextEntry(storedEntry);
|
||||
try (FileInputStream entryIn = new FileInputStream(entry)) {
|
||||
StreamUtils.copy(entryIn, jarOutput);
|
||||
}
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
}
|
||||
try (JarFile zip64JarFile = new JarFile(zip64Jar)) {
|
||||
assertThat(Collections.list(zip64JarFile.entries())).hasSize(6);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedZip64JarCanBeRead() throws Exception {
|
||||
File outer = new File(this.tempDir, "outer.jar");
|
||||
|
||||
Reference in New Issue
Block a user