File: Zip64DataDescriptor.java

package info (click to toggle)
openjdk-25 25.0.1%2B8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 825,408 kB
  • sloc: java: 5,585,680; cpp: 1,333,948; xml: 1,321,242; ansic: 488,034; asm: 404,003; objc: 21,088; sh: 15,106; javascript: 13,265; python: 8,319; makefile: 2,518; perl: 357; awk: 351; pascal: 103; exp: 83; sed: 72; jsp: 24
file content (282 lines) | stat: -rw-r--r-- 10,742 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 8303866
 * @summary ZipInputStream should read 8-byte data descriptors if the LOC has
 *   a ZIP64 extended information extra field
 * @run junit Zip64DataDescriptor
 */


import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import java.util.zip.*;

import static org.junit.jupiter.api.Assertions.*;

public class Zip64DataDescriptor {

    // A byte array holding a small-sized Zip64 ZIP file, described below
    private byte[] zip64File;

    // A byte array holding a ZIP used for testing invalid Zip64 extra fields
    private byte[] invalidZip64;

    @BeforeEach
    public void setup() throws IOException {
        /*
         * Structure of the ZIP64 file used below . Note the presence
         * of a Zip64 extended information extra field and the
         * Data Descriptor having 8-byte values for csize and size.
         *
         * The file was produced using the zip command on MacOS
         * (zip 3.0, by Info-ZIP), in streamming mode (to enable Zip64),
         * using the -fd option (to force the use of data descriptors)
         *
         * The following command was used:
         * <pre>echo hello | zip -fd > hello.zip</pre>
         *
         * ------  Local File Header  ------
         * 000000  signature          0x04034b50
         * 000004  version            45
         * 000006  flags              0x0008
         * 000008  method             8              Deflated
         * 000010  time               0xb180         22:12
         * 000012  date               0x565c         2023-02-28
         * 000014  crc                0x00000000
         * 000018  csize              -1
         * 000022  size               -1
         * 000026  nlen               1
         * 000028  elen               20
         * 000030  name               1 bytes        '-'
         * 000031  ext id             0x0001         Zip64 extended information extra field
         * 000033  ext size           16
         * 000035  z64 size           0
         * 000043  z64 csize          0
         *
         * ------  File Data  ------
         * 000051  data               8 bytes
         *
         * ------  Data Desciptor  ------
         * 000059  signature          0x08074b50
         * 000063  crc                0x363a3020
         * 000067  csize              8
         * 000075  size               6
         * 000083  ...
         */

        String hex = """
                504b03042d000800080080b15c5600000000ffffffffffffffff01001400
                2d0100100000000000000000000000000000000000cb48cdc9c9e7020050
                4b070820303a3608000000000000000600000000000000504b01021e032d
                000800080080b15c5620303a360800000006000000010000000000000001
                000000b011000000002d504b050600000000010001002f00000053000000
                0000""";

        zip64File = HexFormat.of().parseHex(hex.replaceAll("\n", ""));

        // Create the ZIP file used for testing that invalid Zip64 extra fields are ignored
        // This ZIP has the regular 4-bit data descriptor

        byte[] extra = new byte[Long.BYTES + Long.BYTES + Short.BYTES * 2]; // Size of a regular Zip64 extra field
        ByteBuffer buffer = ByteBuffer.wrap(extra).order(ByteOrder.LITTLE_ENDIAN);
        buffer.putShort(0, (short) 123); // Not processed by ZipEntry.setExtra
        buffer.putShort(Short.BYTES, (short) (extra.length - 4));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ZipOutputStream zo = new ZipOutputStream(baos)) {
            ZipEntry ze = new ZipEntry("-");
            ze.setExtra(extra);
            zo.putNextEntry(ze);
            zo.write("hello\n".getBytes(StandardCharsets.UTF_8));
        }

        invalidZip64 = baos.toByteArray();

        // Set Zip64 magic values on compressed and uncompressed size fields
        ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN)
                .putInt(ZipFile.LOCSIZ, 0xFFFFFFFF)
                .putInt(ZipFile.LOCLEN, 0xFFFFFFFF);

        // Set the Zip64 header ID 0x1 on the extra field in the invalid file
        setExtraHeaderId((short) 0x1);
    }

    /*
     * Verify that small-sized Zip64 entries can be parsed by ZipInputStream
     */
    @Test
    public void shouldReadZip64Descriptor() throws IOException {
        readZipInputStream(zip64File);
    }

    /*
     * For maximal backward compatibility when reading Zip64 descriptors, invalid
     * Zip64 extra data sizes should be ignored
     */
    @Test
    public void shouldIgnoreInvalidExtraSize() throws IOException {
        setExtraSize((short) 42);
        readZipInputStream(invalidZip64);
    }

    /*
     * Files with Zip64 magic values but no Zip64 field should be ignored
     * when considering 8 byte data descriptors
     */
    @Test
    public void shouldIgnoreNoZip64Header() throws IOException {
        setExtraSize((short) 123);
        readZipInputStream(invalidZip64);
    }

    /*
     * Theoretically, ZIP files may exist with ZIP64 format, but with 4-byte
     * data descriptors. Such files will fail to parse, as demonstrated by this test.
     */
    @Test
    public void shouldFailParsingZip64With4ByteDataDescriptor() throws IOException {
        ZipException ex = assertThrows(ZipException.class, () -> {
            readZipInputStream(invalidZip64);
        });

        String msg = String.format("Expected exeption message to contain 'invalid entry size', was %s",
                ex.getMessage());
        assertTrue(ex.getMessage().contains("invalid entry size"), msg);
    }

    /*
     * Validate that an extra data size exceeding the length of the extra field is ignored
     */
    @Test
    public void shouldIgnoreExcessiveExtraSize() throws IOException {

        setExtraSize(Short.MAX_VALUE);


        readZipInputStream(invalidZip64);
    }

    /*
     * Validate that the Data Descriptor is read with 32-bit fields if neither the
     * LOC's 'uncompressed size' or 'compressed size' fields have the Zip64 magic value,
     * even when there is a Zip64 field in the extra field.
     */
    @Test
    public void shouldIgnoreNoMagicMarkers() throws IOException {
        // Set compressed and uncompressed size fields to zero
        ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN)
                .putInt(ZipFile.LOCSIZ, 0)
                .putInt(ZipFile.LOCLEN, 0);


        readZipInputStream(invalidZip64);
    }

    /*
     * Validate that an extra data size exceeding the length of the extra field is ignored
     */
    @Test
    public void shouldIgnoreTrucatedZip64Extra() throws IOException {

        truncateZip64();

        readZipInputStream(invalidZip64);
    }

    /**
     * Update the Extra field header ID of the invalid file
     */
    private void setExtraHeaderId(short id) {
        // Set the header ID on the extra field
        ByteBuffer buffer = ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN);
        int nlen = buffer.getShort(ZipFile.LOCNAM);
        buffer.putShort(ZipFile.LOCHDR + nlen, id);
    }

    /**
     * Updates the 16-bit 'data size' field of the Zip64 extended information field,
     * potentially to an invalid value.
     * @param size the value to set in the 'data size' field.
     */
    private void setExtraSize(short size) {
        ByteBuffer buffer = ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN);
        // Compute the offset to the Zip64 data block size field
        short nlen = buffer.getShort(ZipFile.LOCNAM);
        int dataSizeOffset = ZipFile.LOCHDR + nlen + Short.BYTES;
        buffer.putShort(dataSizeOffset, size);
    }

    /**
     * Puts a truncated Zip64 field (just the tag) at the end of the LOC extra field.
     * The beginning of the extra field is filled with a generic extra field containing
     * just zeros.
     */
    private void truncateZip64() {
        ByteBuffer buffer = ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN);
        // Get the LOC name and extra sizes
        short nlen = buffer.getShort(ZipFile.LOCNAM);
        short elen = buffer.getShort(ZipFile.LOCEXT);
        int cenOffset = ZipFile.LOCHDR + nlen + elen;

        // Zero out the extra field
        int estart = ZipFile.LOCHDR + nlen;
        buffer.put(estart, new byte[elen]);
        // Put a generic extra field in the start
        buffer.putShort(estart, (short) 42);
        buffer.putShort(estart + Short.BYTES, (short) (elen - 4 - 2));
        // Put a truncated (just the tag) Zip64 field at the end
        buffer.putShort(cenOffset - Short.BYTES, (short) 0x0001);
    }

    /*
     * Consume and verify the ZIP file using ZipInputStream
     */
    private void readZipInputStream(byte[] zip) throws IOException {
        try (ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zip))) {
            // Read the ZIP entry, this calls readLOC
            ZipEntry e = in.getNextEntry();

            // Sanity check the zip entry
            assertNotNull(e, "Missing zip entry");
            assertEquals("-", e.getName());

            // Read the entry data, this causes readEND to parse the data descriptor
            assertEquals("hello\n", new String(in.readAllBytes(), StandardCharsets.UTF_8));

            // There should only be a single zip entry
            assertNull(in.getNextEntry(), "Unexpected additional zip entry");
        }
    }
}