File: DataDescriptorIgnoreCrcAndSizeFields.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 (180 lines) | stat: -rw-r--r-- 7,987 bytes parent folder | download | duplicates (3)
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
/*
 * Copyright (c) 2023, 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 8321396
 * @summary Verify that ZipInputStream ignores non-zero, incorrect 'crc',
 * 'compressed size' and 'uncompressed size' values when in streaming mode.
 * @run junit DataDescriptorIgnoreCrcAndSizeFields
 */

import org.junit.jupiter.api.Test;

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.zip.*;

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

public class DataDescriptorIgnoreCrcAndSizeFields {

    /**
     * Verify that ZipInputStream correctly ignores values from a LOC header's
     * 'crc', 'compressed size' and 'uncompressed size' fields, when in
     * streaming mode and these fields are incorrectly set to non-zero values.
     */
    @Test
    public void shouldIgnoreCrcAndSizeValuesInStreamingMode() throws IOException {
        // ZIP with incorrect 'CRC', 'compressed size' and 'uncompressed size' values
        byte[] zip = zipWithIncorrectCrcAndSizeValuesInLocalHeader();

        // ZipInputStream should ignore the incorrect field values
        try (ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zip))) {
            ZipEntry first = in.getNextEntry();
            assertNotNull(first, "Zip file is unexpectedly missing first entry");

            // CRC, compressed size and size should be uninitialized at this point
            assertCrcAndSize(first, -1, -1, -1);
            // Check that name and contents is as expected
            assertNameAndContents("first", first, in);
            // At this point, ZipInputStream should have read correct values from the data descriptor
            assertCrcAndSize(first, crc32("first"), compressedSize("first"), uncompressedSize("first"));

            // For extra caution, also read and validate the second entry
            ZipEntry second = in.getNextEntry();
            assertNotNull(second, "Zip file is unexpectedly missing second entry");

            // CRC, compressed size and size should be uninitialized at this point
            assertCrcAndSize(second, -1, -1, -1);
            // Check that name and contents is as expected
            assertNameAndContents("second", second, in);
            // At this point, ZipInputStream should have read correct values from the data descriptor
            assertCrcAndSize(second, crc32("second"), compressedSize("second"), uncompressedSize("second"));
        }

    }

    /**
     * Assert that the given ZipEntry has the expected name and that
     * the expected content can be read from the ZipInputStream
     * @param expected the expected name and content
     * @param entry the entry to check the name of
     * @param in the ZipInputStream to check the entry content of
     * @throws IOException if an IO exception occurs
     */
    private static void assertNameAndContents(String expected, ZipEntry entry, ZipInputStream in) throws IOException {
        assertEquals(expected, entry.getName());
        assertArrayEquals(expected.getBytes(StandardCharsets.UTF_8), in.readAllBytes());
    }

    /**
     * Assert that a ZipEntry has the expected CRC-32, compressed size and uncompressed size values
     * @param entry the ZipEntry to validate
     * @param expectedCrc the expected CRC-32 value
     * @param expectedCompressedSize the exprected compressed size value
     * @param expectedSize the expected size value
     */
    private static void assertCrcAndSize(ZipEntry entry, long expectedCrc, long expectedCompressedSize, long expectedSize) {
        assertEquals(expectedCrc, entry.getCrc());
        assertEquals(expectedCompressedSize, entry.getCompressedSize());
        assertEquals(expectedSize, entry.getSize());
    }

    /**
     * Return the CRC-32 value for the given string encoded in UTF-8
     * @param content the string to produce a CRC-32 checksum for
     * @return the CRC-value of the encoded string
     */
    private long crc32(String content) {
        CRC32 crc32 = new CRC32();
        crc32.update(content.getBytes(StandardCharsets.UTF_8));
        return crc32.getValue();
    }

    /**
     * Return the length of the given content encoded in UTF-8
     * @param content the content to return the encoded length for
     * @return the uncompressed size of the encoded content
     */
    private long uncompressedSize(String content) {
        return content.getBytes(StandardCharsets.UTF_8).length;
    }

    /**
     * Returns the size of the given content, as if it was encoded in UTF-8 and then deflated
     * @param content the content to get the compressed size of
     * @return the compressed size of the content
     * @throws IOException if an IO exception occurs
     */
    private long compressedSize(String content) throws IOException {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        try (OutputStream o = new DeflaterOutputStream(bao, new Deflater(Deflater.DEFAULT_COMPRESSION, true))) {
            o.write(content.getBytes(StandardCharsets.UTF_8));
        }
        return bao.size();
    }

    /**
     * When a ZIP entry is created in 'streaming' mode, the 'general purpose bit flag' 3
     * is set, and the fields crc-32, compressed size and uncompressed size are set to
     * zero in the local header.
     *
     * Certain legacy ZIP tools incorrectly set non-zero values for one or more of these
     * three fields when in streaming mode.
     *
     * This method creates a ZIP where the first entry has a local header where the
     * mentioned fields are set to a non-zero, incorrect values. The second entry
     * has the correct zero values for these fields.
     */
    private static byte[] zipWithIncorrectCrcAndSizeValuesInLocalHeader() throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (ZipOutputStream zo = new ZipOutputStream(out)) {
            // Write a first entry
            zo.putNextEntry(new ZipEntry("first"));
            zo.write("first".getBytes(StandardCharsets.UTF_8));
            // Add a second entry
            zo.putNextEntry(new ZipEntry("second"));
            zo.write("second".getBytes(StandardCharsets.UTF_8));
        }

        // ZipOutputStream correctly produces local headers with zero crc and sizes values
        byte[] zip = out.toByteArray();

        // Buffer for updating the local header values
        ByteBuffer buffer = ByteBuffer.wrap(zip).order(ByteOrder.LITTLE_ENDIAN);
        // Set the CRC-32 field to an incorrect value
        buffer.putShort(ZipEntry.LOCCRC, (short) 42);
        // Set the compressed size to an incorrect value
        buffer.putShort(ZipEntry.LOCSIZ, (short) 42);
        // Set the uncompressed size to an incorrect value
        buffer.putShort(ZipEntry.LOCLEN, (short) 42);

        return zip;
    }
}