/* * Copyright 2012 Google, Inc. All Rights Reserved. * Copyright (c) 2023, 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 8056934 * @summary Verify the ability to read zip files whose local header * data descriptor is missing the optional signature *
* No way to adapt the technique in this test to get a ZIP64 zip file * without data descriptors was found. * @run junit DataDescriptorSignatureMissing */ import org.junit.jupiter.api.Test; import java.io.*; 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 DataDescriptorSignatureMissing { /** * Verify that ZipInputStream correctly parses a ZIP with a Data Descriptor without * the recommended but optional signature. */ @Test public void shouldParseSignaturelessDescriptor() throws IOException { // The ZIP with a signature-less descriptor byte[] zip = makeZipWithSignaturelessDescriptor(); // ZipInputStream should read the signature-less data descriptor try (ZipInputStream in = new ZipInputStream( new ByteArrayInputStream(zip))) { ZipEntry first = in.getNextEntry(); assertNotNull(first, "Zip file is unexpectedly missing first entry"); assertEquals("first", first.getName()); assertArrayEquals("first".getBytes(StandardCharsets.UTF_8), in.readAllBytes()); ZipEntry second = in.getNextEntry(); assertNotNull(second, "Zip file is unexpectedly missing second entry"); assertEquals("second", second.getName()); assertArrayEquals("second".getBytes(StandardCharsets.UTF_8), in.readAllBytes()); } } /** * The 'Data descriptor' record is used to facilitate ZIP streaming. If the size of an * entry is unknown at the time the LOC header is written, bit 3 of the General Purpose Bit Flag * is set, and the File data is immediately followed by the 'Data descriptor' record. This record * then contains the compressed and uncompressed sizes of the entry and also the CRC value. * * The 'Data descriptor' record is usually preceded by the recommended, but optional * signature value 0x08074b50. * * A ZIP entry in streaming mode has the following structure: * * ------ Local File Header ------ * 000000 signature 0x04034b50 * 000004 version 20 * 000006 flags 0x0808 # Notice bit 3 is set * [..] Omitted for brevity * * ------ File Data ------ * 000035 data 7 bytes * * ------ Data Descriptor ------ * 000042 signature 0x08074b50 * 000046 crc 0x3610a686 * 000050 csize 7 * 000054 size 5 * * A signature-less data descriptor will look like the following: * * ------ Data Descriptor ------ * 000042 crc 0x3610a686 * 000046 csize 7 * 000050 size 5 * * This method produces a ZIP with two entries, where the first entry * is made signature-less. */ private static byte[] makeZipWithSignaturelessDescriptor() throws IOException { // Offset of the signed data descriptor int sigOffset; 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)); // Force the data descriptor to be written out zo.closeEntry(); // Signed data descriptor starts 16 bytes before current offset sigOffset = out.size() - 4 * Integer.BYTES; // Add a second entry zo.putNextEntry(new ZipEntry("second")); zo.write("second".getBytes(StandardCharsets.UTF_8)); } // The generated ZIP file with a signed data descriptor byte[] sigZip = out.toByteArray(); // The offset of the CRC immediately following the 4-byte signature int crcOffset = sigOffset + Integer.BYTES; // Create a ZIP file with a signature-less data descriptor for the first entry ByteArrayOutputStream sigLess = new ByteArrayOutputStream(); sigLess.write(sigZip, 0, sigOffset); // Skip the signature sigLess.write(sigZip, crcOffset, sigZip.length - crcOffset); byte[] siglessZip = sigLess.toByteArray(); // Adjust the CEN offset in the END header ByteBuffer buffer = ByteBuffer.wrap(siglessZip).order(ByteOrder.LITTLE_ENDIAN); // Reduce cenOffset by 4 bytes int cenOff = siglessZip.length - ZipFile.ENDHDR + ZipFile.ENDOFF; int realCenOff = buffer.getInt(cenOff) - Integer.BYTES; buffer.putInt(cenOff, realCenOff); // Adjust the LOC offset in the second CEN header int cen = realCenOff; // Skip past the first CEN header int nlen = buffer.getShort(cen + ZipFile.CENNAM); cen += ZipFile.CENHDR + nlen; // Reduce LOC offset by 4 bytes int locOff = cen + ZipFile.CENOFF; buffer.putInt(locOff, buffer.getInt(locOff) - Integer.BYTES); return siglessZip; } }