Merge branch '2.4.x' into 2.5.x

Closes gh-27900
This commit is contained in:
Andy Wilkinson 2021-09-09 10:13:40 +01:00
commit 37240bda3f
8 changed files with 181 additions and 41 deletions

View File

@ -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.
@ -34,8 +34,6 @@ class CentralDirectoryEndRecord {
private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF;
private static final int ZIP64_MAGICCOUNT = 0xFFFF;
private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH;
private static final int SIGNATURE = 0x06054b50;
@ -74,8 +72,9 @@ class CentralDirectoryEndRecord {
}
this.offset = this.block.length - this.size;
}
int startOfCentralDirectoryEndRecord = (int) (data.getSize() - this.size);
this.zip64End = isZip64() ? new Zip64End(data, startOfCentralDirectoryEndRecord) : null;
long startOfCentralDirectoryEndRecord = data.getSize() - this.size;
Zip64Locator zip64Locator = Zip64Locator.find(data, startOfCentralDirectoryEndRecord);
this.zip64End = (zip64Locator != null) ? new Zip64End(data, zip64Locator) : null;
}
private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException {
@ -92,10 +91,6 @@ class CentralDirectoryEndRecord {
return this.size == MINIMUM_SIZE + commentLength;
}
private boolean isZip64() {
return (int) Bytes.littleEndianValue(this.block, this.offset + 10, 2) == ZIP64_MAGICCOUNT;
}
/**
* Returns the location in the data that the archive actually starts. For most files
* the archive data will start at 0, however, it is possible to have prefixed bytes
@ -105,7 +100,8 @@ class CentralDirectoryEndRecord {
*/
long getStartOfArchive(RandomAccessData data) {
long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4);
long specifiedOffset = Bytes.littleEndianValue(this.block, this.offset + 16, 4);
long specifiedOffset = (this.zip64End != null) ? this.zip64End.centralDirectoryOffset
: Bytes.littleEndianValue(this.block, this.offset + 16, 4);
long zip64EndSize = (this.zip64End != null) ? this.zip64End.getSize() : 0L;
int zip64LocSize = (this.zip64End != null) ? Zip64Locator.ZIP64_LOCSIZE : 0;
long actualOffset = data.getSize() - this.size - length - zip64EndSize - zip64LocSize;
@ -145,6 +141,10 @@ class CentralDirectoryEndRecord {
return comment.toString();
}
boolean isZip64() {
return this.zip64End != null;
}
/**
* A Zip64 end of central directory record.
*
@ -167,10 +167,6 @@ class CentralDirectoryEndRecord {
private final int numberOfRecords;
private Zip64End(RandomAccessData data, int centralDirectoryEndOffset) throws IOException {
this(data, new Zip64Locator(data, centralDirectoryEndOffset));
}
private Zip64End(RandomAccessData data, Zip64Locator locator) throws IOException {
this.locator = locator;
byte[] block = data.read(locator.getZip64EndOffset(), 56);
@ -215,16 +211,18 @@ class CentralDirectoryEndRecord {
*/
private static final class Zip64Locator {
static final int SIGNATURE = 0x07064b50;
static final int ZIP64_LOCSIZE = 20; // locator size
static final int ZIP64_LOCOFF = 8; // offset of zip64 end
private final long zip64EndOffset;
private final int offset;
private final long offset;
private Zip64Locator(RandomAccessData data, int centralDirectoryEndOffset) throws IOException {
this.offset = centralDirectoryEndOffset - ZIP64_LOCSIZE;
byte[] block = data.read(this.offset, ZIP64_LOCSIZE);
private Zip64Locator(long offset, byte[] block) throws IOException {
this.offset = offset;
this.zip64EndOffset = Bytes.littleEndianValue(block, ZIP64_LOCOFF, 8);
}
@ -244,6 +242,17 @@ class CentralDirectoryEndRecord {
return this.zip64EndOffset;
}
private static Zip64Locator find(RandomAccessData data, long centralDirectoryEndOffset) throws IOException {
long offset = centralDirectoryEndOffset - ZIP64_LOCSIZE;
if (offset >= 0) {
byte[] block = data.read(offset, ZIP64_LOCSIZE);
if (Bytes.littleEndianValue(block, 0, 4) == SIGNATURE) {
return new Zip64Locator(offset, block);
}
}
return null;
}
}
}

View File

@ -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.
@ -67,15 +67,17 @@ final class CentralDirectoryFileHeader implements FileHeader {
this.localHeaderOffset = localHeaderOffset;
}
void load(byte[] data, int dataOffset, RandomAccessData variableData, int variableOffset, JarEntryFilter filter)
void load(byte[] data, int dataOffset, RandomAccessData variableData, long variableOffset, JarEntryFilter filter)
throws IOException {
// Load fixed part
this.header = data;
this.headerOffset = dataOffset;
long compressedSize = Bytes.littleEndianValue(data, dataOffset + 20, 4);
long uncompressedSize = Bytes.littleEndianValue(data, dataOffset + 24, 4);
long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2);
long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2);
long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2);
this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4);
long localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4);
// Load variable part
dataOffset += 46;
if (variableData != null) {
@ -92,11 +94,37 @@ final class CentralDirectoryFileHeader implements FileHeader {
this.extra = new byte[(int) extraLength];
System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0, this.extra.length);
}
this.localHeaderOffset = getLocalHeaderOffset(compressedSize, uncompressedSize, localHeaderOffset, this.extra);
if (commentLength > 0) {
this.comment = new AsciiBytes(data, (int) (dataOffset + nameLength + extraLength), (int) commentLength);
}
}
private long getLocalHeaderOffset(long compressedSize, long uncompressedSize, long localHeaderOffset, byte[] extra)
throws IOException {
if (localHeaderOffset != 0xFFFFFFFFL) {
return localHeaderOffset;
}
int extraOffset = 0;
while (extraOffset < extra.length - 2) {
int id = (int) Bytes.littleEndianValue(extra, extraOffset, 2);
int length = (int) Bytes.littleEndianValue(extra, extraOffset, 2);
extraOffset += 4;
if (id == 1) {
int localHeaderExtraOffset = 0;
if (compressedSize == 0xFFFFFFFFL) {
localHeaderExtraOffset += 4;
}
if (uncompressedSize == 0xFFFFFFFFL) {
localHeaderExtraOffset += 4;
}
return Bytes.littleEndianValue(extra, extraOffset + localHeaderExtraOffset, 8);
}
extraOffset += length;
}
throw new IOException("Zip64 Extended Information Extra Field not found");
}
AsciiBytes getName() {
return this.name;
}
@ -176,7 +204,7 @@ final class CentralDirectoryFileHeader implements FileHeader {
return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment, this.localHeaderOffset);
}
static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, int offset, JarEntryFilter filter)
static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, long offset, JarEntryFilter filter)
throws IOException {
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
byte[] bytes = data.read(offset, 46);

View File

@ -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.
@ -86,7 +86,7 @@ class CentralDirectoryParser {
}
}
private void visitFileHeader(int dataOffset, CentralDirectoryFileHeader fileHeader) {
private void visitFileHeader(long dataOffset, CentralDirectoryFileHeader fileHeader) {
for (CentralDirectoryVisitor visitor : this.visitors) {
visitor.visitFileHeader(fileHeader, dataOffset);
}

View File

@ -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.
@ -27,7 +27,7 @@ interface CentralDirectoryVisitor {
void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData);
void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset);
void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset);
void visitEnd();

View File

@ -169,7 +169,7 @@ public class JarFile extends AbstractJarFile implements Iterable<java.util.jar.J
}
@Override
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) {
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
AsciiBytes name = fileHeader.getName();
if (name.startsWith(META_INF) && name.endsWith(SIGNATURE_FILE_EXTENSION)) {
JarFile.this.signed = true;

View File

@ -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.
@ -89,7 +89,7 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
private int[] hashCodes;
private int[] centralDirectoryOffsets;
private Offsets centralDirectoryOffsets;
private int[] positions;
@ -120,21 +120,21 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
int maxSize = endRecord.getNumberOfRecords();
this.centralDirectoryData = centralDirectoryData;
this.hashCodes = new int[maxSize];
this.centralDirectoryOffsets = new int[maxSize];
this.centralDirectoryOffsets = Offsets.of(endRecord);
this.positions = new int[maxSize];
}
@Override
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) {
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
AsciiBytes name = applyFilter(fileHeader.getName());
if (name != null) {
add(name, dataOffset);
}
}
private void add(AsciiBytes name, int dataOffset) {
private void add(AsciiBytes name, long dataOffset) {
this.hashCodes[this.size] = name.hashCode();
this.centralDirectoryOffsets[this.size] = dataOffset;
this.centralDirectoryOffsets.set(this.size, dataOffset);
this.positions[this.size] = this.size;
this.size++;
}
@ -183,11 +183,11 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
private void swap(int i, int j) {
swap(this.hashCodes, i, j);
swap(this.centralDirectoryOffsets, i, j);
this.centralDirectoryOffsets.swap(i, j);
swap(this.positions, i, j);
}
private void swap(int[] array, int i, int j) {
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
@ -316,9 +316,10 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
@SuppressWarnings("unchecked")
private <T extends FileHeader> T getEntry(int index, Class<T> type, boolean cacheEntry, AsciiBytes nameAlias) {
try {
long offset = this.centralDirectoryOffsets.get(index);
FileHeader cached = this.entriesCache.get(index);
FileHeader entry = (cached != null) ? cached : CentralDirectoryFileHeader
.fromRandomAccessData(this.centralDirectoryData, this.centralDirectoryOffsets[index], this.filter);
FileHeader entry = (cached != null) ? cached
: CentralDirectoryFileHeader.fromRandomAccessData(this.centralDirectoryData, offset, this.filter);
if (CentralDirectoryFileHeader.class.equals(entry.getClass()) && type.equals(JarEntry.class)) {
entry = new JarEntry(this.jarFile, index, (CentralDirectoryFileHeader) entry, nameAlias);
}
@ -420,4 +421,71 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
}
private interface Offsets {
void set(int index, long value);
long get(int index);
void swap(int i, int j);
static Offsets of(CentralDirectoryEndRecord endRecord) {
return endRecord.isZip64() ? new Zip64Offsets(endRecord.getNumberOfRecords())
: new ZipOffsets(endRecord.getNumberOfRecords());
}
}
private static final class ZipOffsets implements Offsets {
private final int[] offsets;
private ZipOffsets(int size) {
this.offsets = new int[size];
}
@Override
public void swap(int i, int j) {
JarFileEntries.swap(this.offsets, i, j);
}
@Override
public void set(int index, long value) {
this.offsets[index] = (int) value;
}
@Override
public long get(int index) {
return this.offsets[index];
}
}
private static final class Zip64Offsets implements Offsets {
private final long[] offsets;
private Zip64Offsets(int size) {
this.offsets = new long[size];
}
@Override
public void swap(int i, int j) {
long temp = this.offsets[i];
this.offsets[i] = this.offsets[j];
this.offsets[j] = temp;
}
@Override
public void set(int index, long value) {
this.offsets[index] = value;
}
@Override
public long get(int index) {
return this.offsets[index];
}
}
}

View File

@ -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");
}

View File

@ -36,6 +36,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;
@ -47,6 +48,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");