Skip to content
Snippets Groups Projects
Commit 2e3ca314 authored by Andrew Lu's avatar Andrew Lu
Browse files

8303891: Speed up Zip64SizeTest using a small ZIP64 file

8259866: two java.util tests failed with "IOException: There is not enough space on the disk"

Backport-of: 842b895f093e15ecd8aa0153d712f5f81cf1cf67
parent 27c92b6d
No related branches found
No related tags found
No related merge requests found
/* /*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -20,117 +20,174 @@ ...@@ -20,117 +20,174 @@
* or visit www.oracle.com if you need additional information or have any * or visit www.oracle.com if you need additional information or have any
* questions. * questions.
*/ */
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.*; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import static org.testng.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals;
/** /**
* @test * @test
* @bug 8226530 * @bug 8226530 8303891
* @summary ZIP File System tests that leverage DirectoryStream * @summary Verify that ZipFile reads size fields using the Zip64 extra
* field when only the 'uncompressed size' field has the ZIP64 "magic value" 0xFFFFFFFF
* @compile Zip64SizeTest.java * @compile Zip64SizeTest.java
* @run testng Zip64SizeTest * @run junit Zip64SizeTest
*/ */
public class Zip64SizeTest { public class Zip64SizeTest {
private static final int BUFFER_SIZE = 2048;
// ZIP file to create // ZIP file to create
private static final String ZIP_FILE_NAME = "Zip64SizeTest.zip"; private static final Path ZIP_FILE = Path.of("Zip64SizeTest.zip");
// File that will be created with a size greater than 0xFFFFFFFF // Contents to write to ZIP entries
private static final String LARGE_FILE_NAME = "LargeZipEntry.txt"; private static final byte[] CONTENT = "Hello".getBytes(StandardCharsets.UTF_8);
// File that will be created with a size less than 0xFFFFFFFF // This opaque tag will be ignored by ZipEntry.setExtra0
private static final String SMALL_FILE_NAME = "SmallZipEntry.txt"; private static final int UNKNOWN_TAG = 0x9902;
// List of files to be added to the ZIP file // Tag used when converting the extra field to a real ZIP64 extra field
private static final List<String> ZIP_ENTRIES = List.of(LARGE_FILE_NAME, private static final short ZIP64_TAG = 0x1;
SMALL_FILE_NAME); // Marker value to indicate that the actual value is stored in the ZIP64 extra field
private static final long LARGE_FILE_SIZE = 5L * 1024L * 1024L * 1024L; // 5GB private static final int ZIP64_MAGIC_VALUE = 0xFFFFFFFF;
private static final long SMALL_FILE_SIZE = 0x100000L; // 1024L x 1024L;
/** /**
* Validate that if the size of a ZIP entry exceeds 0xFFFFFFFF, that the * Validate that if the 'uncompressed size' of a ZIP CEN header is 0xFFFFFFFF, then the
* correct size is returned from the ZIP64 Extended information. * actual size is retrieved from the corresponding ZIP64 Extended information field.
* @throws IOException *
* @throws IOException if an unexpected IOException occurs
*/ */
@Test @Test
private static void validateZipEntrySizes() throws IOException { public void validateZipEntrySizes() throws IOException {
createFiles();
createZipFile(); createZipFile();
System.out.println("Validating Zip Entry Sizes"); System.out.println("Validating Zip Entry Sizes");
try (ZipFile zip = new ZipFile(ZIP_FILE_NAME)) { try (ZipFile zip = new ZipFile(ZIP_FILE.toFile())) {
ZipEntry ze = zip.getEntry(LARGE_FILE_NAME); ZipEntry ze = zip.getEntry("first");
System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize()); System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
assertTrue(ze.getSize() == LARGE_FILE_SIZE); assertEquals(CONTENT.length, ze.getSize());
ze = zip.getEntry(SMALL_FILE_NAME); ze = zip.getEntry("second");
System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize()); System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
assertTrue(ze.getSize() == SMALL_FILE_SIZE); assertEquals(CONTENT.length, ze.getSize());
}
} }
/**
* Delete the files created for use by the test
* @throws IOException if an error occurs deleting the files
*/
private static void deleteFiles() throws IOException {
Files.deleteIfExists(Path.of(ZIP_FILE_NAME));
Files.deleteIfExists(Path.of(LARGE_FILE_NAME));
Files.deleteIfExists(Path.of(SMALL_FILE_NAME));
} }
/** /**
* Create the ZIP file adding an entry whose size exceeds 0xFFFFFFFF * Create a ZIP file with a CEN entry where the 'uncompressed size' is stored in
* the ZIP64 field, but the 'compressed size' is in the CEN field. This makes the
* ZIP64 data block 8 bytes long, which triggers the regression described in 8226530.
*
* The CEN entry for the "first" entry will have the following structure:
* (Note the CEN 'Uncompressed Length' being 0xFFFFFFFF and the ZIP64
* 'Uncompressed Size' being 5)
*
* 0081 CENTRAL HEADER #1 02014B50
* 0085 Created Zip Spec 14 '2.0'
* 0086 Created OS 00 'MS-DOS'
* [...] Omitted for brevity
* 0091 CRC F7D18982
* 0095 Compressed Length 00000007
* 0099 Uncompressed Length FFFFFFFF
* [...] Omitted for brevity
* 00AF Filename 'first'
* 00B4 Extra ID #0001 0001 'ZIP64'
* 00B6 Length 0008
* 00B8 Uncompressed Size 0000000000000005
*
* @throws IOException if an error occurs creating the ZIP File * @throws IOException if an error occurs creating the ZIP File
*/ */
private static void createZipFile() throws IOException { private static void createZipFile() throws IOException {
try (FileOutputStream fos = new FileOutputStream(ZIP_FILE_NAME); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(fos)) { try (ZipOutputStream zos = new ZipOutputStream(baos)) {
System.out.printf("Creating Zip file: %s%n", ZIP_FILE_NAME);
for (String srcFile : ZIP_ENTRIES) { // The 'first' entry will store 'uncompressed size' in the Zip64 format
System.out.printf("...Adding Entry: %s%n", srcFile); ZipEntry e1 = new ZipEntry("first");
File fileToZip = new File(srcFile);
try (FileInputStream fis = new FileInputStream(fileToZip)) { // Make an extra field with the correct size for an 8-byte 'uncompressed size'
ZipEntry zipEntry = new ZipEntry(fileToZip.getName()); // Zip64 field. Temporarily use the 'unknown' tag 0x9902 to make
zipEntry.setSize(fileToZip.length()); // ZipEntry.setExtra0 skip parsing this as a Zip64.
zos.putNextEntry(zipEntry); // See APPNOTE.TXT, 4.6.1 Third Party Mappings
byte[] bytes = new byte[BUFFER_SIZE]; byte[] opaqueExtra = createBlankExtra((short) UNKNOWN_TAG, (short) Long.BYTES);
int length; e1.setExtra(opaqueExtra);
while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length); zos.putNextEntry(e1);
} zos.write(CONTENT);
}
} // A second entry, not in Zip64 format
ZipEntry e2 = new ZipEntry("second");
zos.putNextEntry(e2);
zos.write(CONTENT);
} }
byte[] zip = baos.toByteArray();
// Update the CEN of 'first' to use the Zip64 format
updateCENHeaderToZip64(zip);
Files.write(ZIP_FILE, zip);
} }
/** /**
* Create the files that will be added to the ZIP file * Update the CEN entry of the "first" entry to use ZIP64 format for the
* @throws IOException if there is a problem creating the files * 'uncompressed size' field. The updated extra field will have the following
* structure:
*
* 00B4 Extra ID #0001 0001 'ZIP64'
* 00B6 Length 0008
* 00B8 Uncompressed Size 0000000000000005
*
* @param zip the ZIP file to update to ZIP64
*/ */
private static void createFiles() throws IOException { private static void updateCENHeaderToZip64(byte[] zip) {
try (RandomAccessFile largeFile = new RandomAccessFile(LARGE_FILE_NAME, "rw"); ByteBuffer buffer = ByteBuffer.wrap(zip).order(ByteOrder.LITTLE_ENDIAN);
RandomAccessFile smallFile = new RandomAccessFile(SMALL_FILE_NAME, "rw")) { // Find the offset of the first CEN header
System.out.printf("Creating %s%n", LARGE_FILE_NAME); int cenOffset = buffer.getInt(zip.length- ZipFile.ENDHDR + ZipFile.ENDOFF);
largeFile.setLength(LARGE_FILE_SIZE); // Find the offset of the extra field
System.out.printf("Creating %s%n", SMALL_FILE_NAME); int nlen = buffer.getShort(cenOffset + ZipFile.CENNAM);
smallFile.setLength(SMALL_FILE_SIZE); int extraOffset = cenOffset + ZipFile.CENHDR + nlen;
// Change the header ID from 'unknown' to ZIP64
buffer.putShort(extraOffset, ZIP64_TAG);
// Update the 'uncompressed size' ZIP64 value to the actual uncompressed length
int fieldOffset = extraOffset
+ Short.BYTES // TAG
+ Short.BYTES; // data size
buffer.putLong(fieldOffset, CONTENT.length);
// Set the 'uncompressed size' field of the CEN to 0xFFFFFFFF
buffer.putInt(cenOffset + ZipFile.CENLEN, ZIP64_MAGIC_VALUE);
} }
/**
* Create an extra field with the given tag and data block size, and a
* blank data block.
* @return an extra field with the specified tag and size
* @param tag the header id of the extra field
* @param blockSize the size of the extra field's data block
*/
private static byte[] createBlankExtra(short tag, short blockSize) {
int size = Short.BYTES // tag
+ Short.BYTES // data block size
+ blockSize; // data block;
byte[] extra = new byte[size];
ByteBuffer.wrap(extra).order(ByteOrder.LITTLE_ENDIAN)
.putShort(0, tag)
.putShort(Short.BYTES, blockSize);
return extra;
} }
/** /**
* Make sure the needed test files do not exist prior to executing the test * Make sure the needed test files do not exist prior to executing the test
* @throws IOException * @throws IOException
*/ */
@BeforeMethod @BeforeEach
public void setUp() throws IOException { public void setUp() throws IOException {
deleteFiles(); deleteFiles();
} }
...@@ -139,8 +196,16 @@ public class Zip64SizeTest { ...@@ -139,8 +196,16 @@ public class Zip64SizeTest {
* Remove the files created for the test * Remove the files created for the test
* @throws IOException * @throws IOException
*/ */
@AfterMethod @AfterEach
public void tearDown() throws IOException { public void tearDown() throws IOException {
deleteFiles(); deleteFiles();
} }
/**
* Delete the files created for use by the test
* @throws IOException if an error occurs deleting the files
*/
private static void deleteFiles() throws IOException {
Files.deleteIfExists(ZIP_FILE);
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment