Skip to content

Commit 2e3ca31

Browse files
author
Andrew Lu
committed
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
1 parent 27c92b6 commit 2e3ca31

File tree

1 file changed

+137
-72
lines changed

1 file changed

+137
-72
lines changed
Lines changed: 137 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -20,117 +20,174 @@
2020
* or visit www.oracle.com if you need additional information or have any
2121
* questions.
2222
*/
23-
import org.testng.annotations.AfterMethod;
24-
import org.testng.annotations.BeforeMethod;
25-
import org.testng.annotations.Test;
2623

27-
import java.io.*;
24+
import org.junit.jupiter.api.AfterEach;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
28+
import java.io.ByteArrayOutputStream;
29+
import java.io.IOException;
30+
import java.nio.ByteBuffer;
31+
import java.nio.ByteOrder;
32+
import java.nio.charset.StandardCharsets;
2833
import java.nio.file.Files;
2934
import java.nio.file.Path;
30-
import java.util.List;
3135
import java.util.zip.ZipEntry;
3236
import java.util.zip.ZipFile;
3337
import java.util.zip.ZipOutputStream;
3438

35-
import static org.testng.Assert.assertTrue;
39+
import static org.junit.jupiter.api.Assertions.assertEquals;
40+
3641

3742
/**
3843
* @test
39-
* @bug 8226530
40-
* @summary ZIP File System tests that leverage DirectoryStream
44+
* @bug 8226530 8303891
45+
* @summary Verify that ZipFile reads size fields using the Zip64 extra
46+
* field when only the 'uncompressed size' field has the ZIP64 "magic value" 0xFFFFFFFF
4147
* @compile Zip64SizeTest.java
42-
* @run testng Zip64SizeTest
48+
* @run junit Zip64SizeTest
4349
*/
4450
public class Zip64SizeTest {
45-
46-
private static final int BUFFER_SIZE = 2048;
4751
// ZIP file to create
48-
private static final String ZIP_FILE_NAME = "Zip64SizeTest.zip";
49-
// File that will be created with a size greater than 0xFFFFFFFF
50-
private static final String LARGE_FILE_NAME = "LargeZipEntry.txt";
51-
// File that will be created with a size less than 0xFFFFFFFF
52-
private static final String SMALL_FILE_NAME = "SmallZipEntry.txt";
53-
// List of files to be added to the ZIP file
54-
private static final List<String> ZIP_ENTRIES = List.of(LARGE_FILE_NAME,
55-
SMALL_FILE_NAME);
56-
private static final long LARGE_FILE_SIZE = 5L * 1024L * 1024L * 1024L; // 5GB
57-
private static final long SMALL_FILE_SIZE = 0x100000L; // 1024L x 1024L;
52+
private static final Path ZIP_FILE = Path.of("Zip64SizeTest.zip");
53+
// Contents to write to ZIP entries
54+
private static final byte[] CONTENT = "Hello".getBytes(StandardCharsets.UTF_8);
55+
// This opaque tag will be ignored by ZipEntry.setExtra0
56+
private static final int UNKNOWN_TAG = 0x9902;
57+
// Tag used when converting the extra field to a real ZIP64 extra field
58+
private static final short ZIP64_TAG = 0x1;
59+
// Marker value to indicate that the actual value is stored in the ZIP64 extra field
60+
private static final int ZIP64_MAGIC_VALUE = 0xFFFFFFFF;
5861

5962
/**
60-
* Validate that if the size of a ZIP entry exceeds 0xFFFFFFFF, that the
61-
* correct size is returned from the ZIP64 Extended information.
62-
* @throws IOException
63+
* Validate that if the 'uncompressed size' of a ZIP CEN header is 0xFFFFFFFF, then the
64+
* actual size is retrieved from the corresponding ZIP64 Extended information field.
65+
*
66+
* @throws IOException if an unexpected IOException occurs
6367
*/
6468
@Test
65-
private static void validateZipEntrySizes() throws IOException {
66-
createFiles();
69+
public void validateZipEntrySizes() throws IOException {
6770
createZipFile();
6871
System.out.println("Validating Zip Entry Sizes");
69-
try (ZipFile zip = new ZipFile(ZIP_FILE_NAME)) {
70-
ZipEntry ze = zip.getEntry(LARGE_FILE_NAME);
72+
try (ZipFile zip = new ZipFile(ZIP_FILE.toFile())) {
73+
ZipEntry ze = zip.getEntry("first");
7174
System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
72-
assertTrue(ze.getSize() == LARGE_FILE_SIZE);
73-
ze = zip.getEntry(SMALL_FILE_NAME);
75+
assertEquals(CONTENT.length, ze.getSize());
76+
ze = zip.getEntry("second");
7477
System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
75-
assertTrue(ze.getSize() == SMALL_FILE_SIZE);
76-
78+
assertEquals(CONTENT.length, ze.getSize());
7779
}
7880
}
7981

8082
/**
81-
* Delete the files created for use by the test
82-
* @throws IOException if an error occurs deleting the files
83+
* Create a ZIP file with a CEN entry where the 'uncompressed size' is stored in
84+
* the ZIP64 field, but the 'compressed size' is in the CEN field. This makes the
85+
* ZIP64 data block 8 bytes long, which triggers the regression described in 8226530.
86+
*
87+
* The CEN entry for the "first" entry will have the following structure:
88+
* (Note the CEN 'Uncompressed Length' being 0xFFFFFFFF and the ZIP64
89+
* 'Uncompressed Size' being 5)
90+
*
91+
* 0081 CENTRAL HEADER #1 02014B50
92+
* 0085 Created Zip Spec 14 '2.0'
93+
* 0086 Created OS 00 'MS-DOS'
94+
* [...] Omitted for brevity
95+
* 0091 CRC F7D18982
96+
* 0095 Compressed Length 00000007
97+
* 0099 Uncompressed Length FFFFFFFF
98+
* [...] Omitted for brevity
99+
* 00AF Filename 'first'
100+
* 00B4 Extra ID #0001 0001 'ZIP64'
101+
* 00B6 Length 0008
102+
* 00B8 Uncompressed Size 0000000000000005
103+
*
104+
* @throws IOException if an error occurs creating the ZIP File
83105
*/
84-
private static void deleteFiles() throws IOException {
85-
Files.deleteIfExists(Path.of(ZIP_FILE_NAME));
86-
Files.deleteIfExists(Path.of(LARGE_FILE_NAME));
87-
Files.deleteIfExists(Path.of(SMALL_FILE_NAME));
106+
private static void createZipFile() throws IOException {
107+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
108+
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
109+
110+
// The 'first' entry will store 'uncompressed size' in the Zip64 format
111+
ZipEntry e1 = new ZipEntry("first");
112+
113+
// Make an extra field with the correct size for an 8-byte 'uncompressed size'
114+
// Zip64 field. Temporarily use the 'unknown' tag 0x9902 to make
115+
// ZipEntry.setExtra0 skip parsing this as a Zip64.
116+
// See APPNOTE.TXT, 4.6.1 Third Party Mappings
117+
byte[] opaqueExtra = createBlankExtra((short) UNKNOWN_TAG, (short) Long.BYTES);
118+
e1.setExtra(opaqueExtra);
119+
120+
zos.putNextEntry(e1);
121+
zos.write(CONTENT);
122+
123+
// A second entry, not in Zip64 format
124+
ZipEntry e2 = new ZipEntry("second");
125+
zos.putNextEntry(e2);
126+
zos.write(CONTENT);
127+
}
128+
129+
byte[] zip = baos.toByteArray();
130+
131+
// Update the CEN of 'first' to use the Zip64 format
132+
updateCENHeaderToZip64(zip);
133+
Files.write(ZIP_FILE, zip);
88134
}
89135

90136
/**
91-
* Create the ZIP file adding an entry whose size exceeds 0xFFFFFFFF
92-
* @throws IOException if an error occurs creating the ZIP File
137+
* Update the CEN entry of the "first" entry to use ZIP64 format for the
138+
* 'uncompressed size' field. The updated extra field will have the following
139+
* structure:
140+
*
141+
* 00B4 Extra ID #0001 0001 'ZIP64'
142+
* 00B6 Length 0008
143+
* 00B8 Uncompressed Size 0000000000000005
144+
*
145+
* @param zip the ZIP file to update to ZIP64
93146
*/
94-
private static void createZipFile() throws IOException {
95-
try (FileOutputStream fos = new FileOutputStream(ZIP_FILE_NAME);
96-
ZipOutputStream zos = new ZipOutputStream(fos)) {
97-
System.out.printf("Creating Zip file: %s%n", ZIP_FILE_NAME);
98-
for (String srcFile : ZIP_ENTRIES) {
99-
System.out.printf("...Adding Entry: %s%n", srcFile);
100-
File fileToZip = new File(srcFile);
101-
try (FileInputStream fis = new FileInputStream(fileToZip)) {
102-
ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
103-
zipEntry.setSize(fileToZip.length());
104-
zos.putNextEntry(zipEntry);
105-
byte[] bytes = new byte[BUFFER_SIZE];
106-
int length;
107-
while ((length = fis.read(bytes)) >= 0) {
108-
zos.write(bytes, 0, length);
109-
}
110-
}
111-
}
112-
}
147+
private static void updateCENHeaderToZip64(byte[] zip) {
148+
ByteBuffer buffer = ByteBuffer.wrap(zip).order(ByteOrder.LITTLE_ENDIAN);
149+
// Find the offset of the first CEN header
150+
int cenOffset = buffer.getInt(zip.length- ZipFile.ENDHDR + ZipFile.ENDOFF);
151+
// Find the offset of the extra field
152+
int nlen = buffer.getShort(cenOffset + ZipFile.CENNAM);
153+
int extraOffset = cenOffset + ZipFile.CENHDR + nlen;
154+
155+
// Change the header ID from 'unknown' to ZIP64
156+
buffer.putShort(extraOffset, ZIP64_TAG);
157+
// Update the 'uncompressed size' ZIP64 value to the actual uncompressed length
158+
int fieldOffset = extraOffset
159+
+ Short.BYTES // TAG
160+
+ Short.BYTES; // data size
161+
buffer.putLong(fieldOffset, CONTENT.length);
162+
163+
// Set the 'uncompressed size' field of the CEN to 0xFFFFFFFF
164+
buffer.putInt(cenOffset + ZipFile.CENLEN, ZIP64_MAGIC_VALUE);
113165
}
114166

115167
/**
116-
* Create the files that will be added to the ZIP file
117-
* @throws IOException if there is a problem creating the files
168+
* Create an extra field with the given tag and data block size, and a
169+
* blank data block.
170+
* @return an extra field with the specified tag and size
171+
* @param tag the header id of the extra field
172+
* @param blockSize the size of the extra field's data block
118173
*/
119-
private static void createFiles() throws IOException {
120-
try (RandomAccessFile largeFile = new RandomAccessFile(LARGE_FILE_NAME, "rw");
121-
RandomAccessFile smallFile = new RandomAccessFile(SMALL_FILE_NAME, "rw")) {
122-
System.out.printf("Creating %s%n", LARGE_FILE_NAME);
123-
largeFile.setLength(LARGE_FILE_SIZE);
124-
System.out.printf("Creating %s%n", SMALL_FILE_NAME);
125-
smallFile.setLength(SMALL_FILE_SIZE);
126-
}
174+
private static byte[] createBlankExtra(short tag, short blockSize) {
175+
int size = Short.BYTES // tag
176+
+ Short.BYTES // data block size
177+
+ blockSize; // data block;
178+
179+
byte[] extra = new byte[size];
180+
ByteBuffer.wrap(extra).order(ByteOrder.LITTLE_ENDIAN)
181+
.putShort(0, tag)
182+
.putShort(Short.BYTES, blockSize);
183+
return extra;
127184
}
128185

129186
/**
130187
* Make sure the needed test files do not exist prior to executing the test
131188
* @throws IOException
132189
*/
133-
@BeforeMethod
190+
@BeforeEach
134191
public void setUp() throws IOException {
135192
deleteFiles();
136193
}
@@ -139,8 +196,16 @@ public void setUp() throws IOException {
139196
* Remove the files created for the test
140197
* @throws IOException
141198
*/
142-
@AfterMethod
199+
@AfterEach
143200
public void tearDown() throws IOException {
144201
deleteFiles();
145202
}
203+
204+
/**
205+
* Delete the files created for use by the test
206+
* @throws IOException if an error occurs deleting the files
207+
*/
208+
private static void deleteFiles() throws IOException {
209+
Files.deleteIfExists(ZIP_FILE);
210+
}
146211
}

0 commit comments

Comments
 (0)