1
1
/*
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.
3
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
4
*
5
5
* This code is free software; you can redistribute it and/or modify it
20
20
* or visit www.oracle.com if you need additional information or have any
21
21
* questions.
22
22
*/
23
- import org .testng .annotations .AfterMethod ;
24
- import org .testng .annotations .BeforeMethod ;
25
- import org .testng .annotations .Test ;
26
23
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 ;
28
33
import java .nio .file .Files ;
29
34
import java .nio .file .Path ;
30
- import java .util .List ;
31
35
import java .util .zip .ZipEntry ;
32
36
import java .util .zip .ZipFile ;
33
37
import java .util .zip .ZipOutputStream ;
34
38
35
- import static org .testng .Assert .assertTrue ;
39
+ import static org .junit .jupiter .api .Assertions .assertEquals ;
40
+
36
41
37
42
/**
38
43
* @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
41
47
* @compile Zip64SizeTest.java
42
- * @run testng Zip64SizeTest
48
+ * @run junit Zip64SizeTest
43
49
*/
44
50
public class Zip64SizeTest {
45
-
46
- private static final int BUFFER_SIZE = 2048 ;
47
51
// 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 ;
58
61
59
62
/**
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
63
67
*/
64
68
@ Test
65
- private static void validateZipEntrySizes () throws IOException {
66
- createFiles ();
69
+ public void validateZipEntrySizes () throws IOException {
67
70
createZipFile ();
68
71
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" );
71
74
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" );
74
77
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 ());
77
79
}
78
80
}
79
81
80
82
/**
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
83
105
*/
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 );
88
134
}
89
135
90
136
/**
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
93
146
*/
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 );
113
165
}
114
166
115
167
/**
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
118
173
*/
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 ;
127
184
}
128
185
129
186
/**
130
187
* Make sure the needed test files do not exist prior to executing the test
131
188
* @throws IOException
132
189
*/
133
- @ BeforeMethod
190
+ @ BeforeEach
134
191
public void setUp () throws IOException {
135
192
deleteFiles ();
136
193
}
@@ -139,8 +196,16 @@ public void setUp() throws IOException {
139
196
* Remove the files created for the test
140
197
* @throws IOException
141
198
*/
142
- @ AfterMethod
199
+ @ AfterEach
143
200
public void tearDown () throws IOException {
144
201
deleteFiles ();
145
202
}
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
+ }
146
211
}
0 commit comments