Copy zip data descriptor records when creating virtual zip data
The zip specification states that when 'bit 3' of the general purpose flags is set then a data descriptor record must be present. Prior to this commit, our `VirtualZipDataBlock` ignored such records and would create invalid data. Although the generated data would work for zip parsers that read the central directory records, it causes problems with streaming reader implementations such as `JarInputStream`. This commit updates the code so that it now copies the data descriptor records. It support both blocks that have a signature and those that don't. It also updates the generation logic to correctly deal with any extra data bytes present after the local file header record. Fixes gh-38063
This commit is contained in:
@@ -25,6 +25,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -95,4 +97,42 @@ class VirtualZipDataBlockTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test // gh-38063
|
||||
void createWithDescriptorRecordContainsValidZipContent() throws Exception {
|
||||
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(this.file))) {
|
||||
ZipEntry entry = new ZipEntry("META-INF/");
|
||||
entry.setMethod(ZipEntry.DEFLATED);
|
||||
zip.putNextEntry(entry);
|
||||
zip.write(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
|
||||
zip.closeEntry();
|
||||
}
|
||||
byte[] bytes = Files.readAllBytes(this.file.toPath());
|
||||
CloseableDataBlock data = new ByteArrayDataBlock(bytes);
|
||||
List<ZipCentralDirectoryFileHeaderRecord> centralRecords = new ArrayList<>();
|
||||
List<Long> centralRecordPositions = new ArrayList<>();
|
||||
ZipEndOfCentralDirectoryRecord eocd = ZipEndOfCentralDirectoryRecord.load(data).endOfCentralDirectoryRecord();
|
||||
long pos = eocd.offsetToStartOfCentralDirectory();
|
||||
for (int i = 0; i < eocd.totalNumberOfCentralDirectoryEntries(); i++) {
|
||||
ZipCentralDirectoryFileHeaderRecord centralRecord = ZipCentralDirectoryFileHeaderRecord.load(data, pos);
|
||||
centralRecords.add(centralRecord);
|
||||
centralRecordPositions.add(pos);
|
||||
pos += centralRecord.size();
|
||||
}
|
||||
NameOffsetLookups nameOffsetLookups = new NameOffsetLookups(0, centralRecords.size());
|
||||
for (int i = 0; i < centralRecords.size(); i++) {
|
||||
nameOffsetLookups.enable(i, true);
|
||||
}
|
||||
nameOffsetLookups.enable(0, true);
|
||||
File outputFile = new File(this.tempDir, "out.jar");
|
||||
try (VirtualZipDataBlock block = new VirtualZipDataBlock(data, nameOffsetLookups,
|
||||
centralRecords.toArray(ZipCentralDirectoryFileHeaderRecord[]::new),
|
||||
centralRecordPositions.stream().mapToLong(Long::longValue).toArray())) {
|
||||
try (FileOutputStream out = new FileOutputStream(outputFile)) {
|
||||
block.asInputStream().transferTo(out);
|
||||
}
|
||||
}
|
||||
byte[] virtualBytes = Files.readAllBytes(outputFile.toPath());
|
||||
assertThat(bytes).isEqualTo(virtualBytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.loader.zip;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ZipDataDescriptorRecord}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ZipDataDescriptorRecordTests {
|
||||
|
||||
private static final short S0 = 0;
|
||||
|
||||
@Test
|
||||
void loadWhenHasSignatureLoadsData() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x07, 0x08, //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, //
|
||||
0x03, 0x00, 0x00, 0x00 }); //
|
||||
ZipDataDescriptorRecord record = ZipDataDescriptorRecord.load(dataBlock, 0);
|
||||
assertThat(record.includeSignature()).isTrue();
|
||||
assertThat(record.crc32()).isEqualTo(1);
|
||||
assertThat(record.compressedSize()).isEqualTo(2);
|
||||
assertThat(record.uncompressedSize()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenHasNoSignatureLoadsData() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, //
|
||||
0x03, 0x00, 0x00, 0x00 }); //
|
||||
ZipDataDescriptorRecord record = ZipDataDescriptorRecord.load(dataBlock, 0);
|
||||
assertThat(record.includeSignature()).isFalse();
|
||||
assertThat(record.crc32()).isEqualTo(1);
|
||||
assertThat(record.compressedSize()).isEqualTo(2);
|
||||
assertThat(record.uncompressedSize()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeWhenIncludeSignatureReturnsSize() {
|
||||
ZipDataDescriptorRecord record = new ZipDataDescriptorRecord(true, 0, 0, 0);
|
||||
assertThat(record.size()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeWhenNotIncludeSignatureReturnsSize() {
|
||||
ZipDataDescriptorRecord record = new ZipDataDescriptorRecord(false, 0, 0, 0);
|
||||
assertThat(record.size()).isEqualTo(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asByteArrayWhenIncludeSignatureReturnsByteArray() throws Exception {
|
||||
byte[] bytes = new byte[] { //
|
||||
0x50, 0x4b, 0x07, 0x08, //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, //
|
||||
0x03, 0x00, 0x00, 0x00 }; //
|
||||
ZipDataDescriptorRecord record = ZipDataDescriptorRecord.load(new ByteArrayDataBlock(bytes), 0);
|
||||
assertThat(record.asByteArray()).isEqualTo(bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asByteArrayWhenNotIncludeSignatureReturnsByteArray() throws Exception {
|
||||
byte[] bytes = new byte[] { //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, //
|
||||
0x03, 0x00, 0x00, 0x00 }; //
|
||||
ZipDataDescriptorRecord record = ZipDataDescriptorRecord.load(new ByteArrayDataBlock(bytes), 0);
|
||||
assertThat(record.asByteArray()).isEqualTo(bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isPresentBasedOnFlagWhenPresentReturnsTrue() {
|
||||
testIsPresentBasedOnFlag((short) 0x8, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isPresentBasedOnFlagWhenNotPresentReturnsFalse() {
|
||||
testIsPresentBasedOnFlag((short) 0x0, false);
|
||||
}
|
||||
|
||||
private void testIsPresentBasedOnFlag(short flag, boolean expected) {
|
||||
ZipCentralDirectoryFileHeaderRecord centralRecord = new ZipCentralDirectoryFileHeaderRecord(S0, S0, flag, S0,
|
||||
S0, S0, S0, S0, S0, S0, S0, S0, S0, S0, S0, S0);
|
||||
ZipLocalFileHeaderRecord localRecord = new ZipLocalFileHeaderRecord(S0, flag, S0, S0, S0, S0, S0, S0, S0, S0);
|
||||
assertThat(ZipDataDescriptorRecord.isPresentBasedOnFlag(flag)).isEqualTo(expected);
|
||||
assertThat(ZipDataDescriptorRecord.isPresentBasedOnFlag(centralRecord)).isEqualTo(expected);
|
||||
assertThat(ZipDataDescriptorRecord.isPresentBasedOnFlag(localRecord)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user