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
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 ;
2833import java .nio .file .Files ;
2934import java .nio .file .Path ;
30- import java .util .List ;
3135import java .util .zip .ZipEntry ;
3236import java .util .zip .ZipFile ;
3337import 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 */
4450public 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