< prev index next >

test/jdk/java/util/zip/ZipFile/Zip64SizeTest.java

Print this page
*** 1,7 ***
  /*
!  * Copyright (c) 2019, 2021, 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.
--- 1,7 ---
  /*
!  * Copyright (c) 2019, 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.

*** 18,129 ***
   *
   * 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.
   */
- import org.testng.annotations.AfterMethod;
- import org.testng.annotations.BeforeMethod;
- import org.testng.annotations.Test;
  
! import java.io.*;
  import java.nio.file.Files;
  import java.nio.file.Path;
- import java.util.List;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipFile;
  import java.util.zip.ZipOutputStream;
  
! import static org.testng.Assert.assertTrue;
  
  /**
   * @test
!  * @bug 8226530
!  * @summary ZIP File System tests that leverage DirectoryStream
   * @compile Zip64SizeTest.java
!  * @run testng Zip64SizeTest
   */
  public class Zip64SizeTest {
- 
-     private static final int BUFFER_SIZE = 2048;
      // ZIP file to create
!     private static final String ZIP_FILE_NAME = "Zip64SizeTest.zip";
!     // File that will be created with a size greater than 0xFFFFFFFF
!     private static final String LARGE_FILE_NAME = "LargeZipEntry.txt";
!     // File that will be created with a size less than 0xFFFFFFFF
!     private static final String SMALL_FILE_NAME = "SmallZipEntry.txt";
!     // List of files to be added to the ZIP file
!     private static final List<String> ZIP_ENTRIES = List.of(LARGE_FILE_NAME,
!             SMALL_FILE_NAME);
!     private static final long LARGE_FILE_SIZE = 5L * 1024L * 1024L * 1024L; // 5GB
-     private static final long SMALL_FILE_SIZE = 0x100000L; // 1024L x 1024L;
  
      /**
!      * Validate that if the size of a ZIP entry exceeds 0xFFFFFFFF, that the
!      * correct size is returned from the ZIP64 Extended information.
!      * @throws IOException
       */
      @Test
!     private static void validateZipEntrySizes() throws IOException {
-         createFiles();
          createZipFile();
          System.out.println("Validating Zip Entry Sizes");
!         try (ZipFile zip = new ZipFile(ZIP_FILE_NAME)) {
!             ZipEntry ze = zip.getEntry(LARGE_FILE_NAME);
              System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
!             assertTrue(ze.getSize() == LARGE_FILE_SIZE);
!             ze = zip.getEntry(SMALL_FILE_NAME);
              System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
!             assertTrue(ze.getSize() == SMALL_FILE_SIZE);
- 
          }
      }
  
      /**
!      * Delete the files created for use by the test
!      * @throws IOException if an error occurs deleting the files
       */
!     private static void deleteFiles() throws IOException {
!         Files.deleteIfExists(Path.of(ZIP_FILE_NAME));
!         Files.deleteIfExists(Path.of(LARGE_FILE_NAME));
!         Files.deleteIfExists(Path.of(SMALL_FILE_NAME));
      }
  
      /**
!      * Create the ZIP file adding an entry whose size exceeds 0xFFFFFFFF
!      * @throws IOException if an error occurs creating the ZIP File
       */
!     private static void createZipFile() throws IOException {
!         try (FileOutputStream fos = new FileOutputStream(ZIP_FILE_NAME);
!              ZipOutputStream zos = new ZipOutputStream(fos)) {
!             System.out.printf("Creating Zip file: %s%n", ZIP_FILE_NAME);
!             for (String srcFile : ZIP_ENTRIES) {
!                 System.out.printf("...Adding Entry: %s%n", srcFile);
!                 File fileToZip = new File(srcFile);
!                 try (FileInputStream fis = new FileInputStream(fileToZip)) {
!                     ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
!                     zipEntry.setSize(fileToZip.length());
!                     zos.putNextEntry(zipEntry);
!                     byte[] bytes = new byte[BUFFER_SIZE];
!                     int length;
!                     while ((length = fis.read(bytes)) >= 0) {
!                         zos.write(bytes, 0, length);
!                     }
!                 }
!             }
-         }
      }
  
      /**
!      * Create the files that will be added to the ZIP file
!      * @throws IOException if there is a problem  creating the files
       */
!     private static void createFiles() throws IOException {
!         try (RandomAccessFile largeFile = new RandomAccessFile(LARGE_FILE_NAME, "rw");
!              RandomAccessFile smallFile = new RandomAccessFile(SMALL_FILE_NAME, "rw")) {
!             System.out.printf("Creating %s%n", LARGE_FILE_NAME);
!             largeFile.setLength(LARGE_FILE_SIZE);
!             System.out.printf("Creating %s%n", SMALL_FILE_NAME);
!             smallFile.setLength(SMALL_FILE_SIZE);
!         }
      }
  
      /**
       * Make sure the needed test files do not exist prior to executing the test
       * @throws IOException
       */
!     @BeforeMethod
      public void setUp() throws IOException {
          deleteFiles();
      }
  
      /**
       * Remove the files created for the test
       * @throws IOException
       */
!     @AfterMethod
      public void tearDown() throws IOException {
          deleteFiles();
      }
  }
--- 18,194 ---
   *
   * 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.
   */
  
! import org.junit.jupiter.api.AfterEach;
+ import org.junit.jupiter.api.BeforeEach;
+ import org.junit.jupiter.api.Test;
+ 
+ import java.io.ByteArrayOutputStream;
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
+ import java.nio.ByteOrder;
+ import java.nio.charset.StandardCharsets;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipFile;
  import java.util.zip.ZipOutputStream;
  
! import static org.junit.jupiter.api.Assertions.assertEquals;
+ 
  
  /**
   * @test
!  * @bug 8226530 8303891
!  * @summary Verify that ZipFile reads size fields using the Zip64 extra
+  * field when only the 'uncompressed size' field has the ZIP64 "magic value" 0xFFFFFFFF
   * @compile Zip64SizeTest.java
!  * @run junit Zip64SizeTest
   */
  public class Zip64SizeTest {
      // ZIP file to create
!     private static final Path ZIP_FILE = Path.of("Zip64SizeTest.zip");
!     // Contents to write to ZIP entries
!     private static final byte[] CONTENT = "Hello".getBytes(StandardCharsets.UTF_8);
!     // This opaque tag will be ignored by ZipEntry.setExtra0
!     private static final int UNKNOWN_TAG = 0x9902;
!     // Tag used when converting the extra field to a real ZIP64 extra field
!     private static final short ZIP64_TAG = 0x1;
!     // Marker value to indicate that the actual value is stored in the ZIP64 extra field
!     private static final int ZIP64_MAGIC_VALUE = 0xFFFFFFFF;
  
      /**
!      * Validate that if the 'uncompressed size' of a ZIP CEN header is 0xFFFFFFFF, then the
!      * actual size is retrieved from the corresponding ZIP64 Extended information field.
!      *
+      * @throws IOException if an unexpected IOException occurs
       */
      @Test
!     public void validateZipEntrySizes() throws IOException {
          createZipFile();
          System.out.println("Validating Zip Entry Sizes");
!         try (ZipFile zip = new ZipFile(ZIP_FILE.toFile())) {
!             ZipEntry ze = zip.getEntry("first");
              System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
!             assertEquals(CONTENT.length, ze.getSize());
!             ze = zip.getEntry("second");
              System.out.printf("Entry: %s, size= %s%n", ze.getName(), ze.getSize());
!             assertEquals(CONTENT.length, ze.getSize());
          }
      }
  
      /**
!      * Create a ZIP file with a CEN entry where the 'uncompressed size' is stored in
!      * the ZIP64 field, but the 'compressed size' is in the CEN field. This makes the
+      * ZIP64 data block 8 bytes long, which triggers the regression described in 8226530.
+      *
+      * The CEN entry for the "first" entry will have the following structure:
+      * (Note the CEN 'Uncompressed Length' being 0xFFFFFFFF and the ZIP64
+      * 'Uncompressed Size' being 5)
+      *
+      * 0081 CENTRAL HEADER #1     02014B50
+      * 0085 Created Zip Spec      14 '2.0'
+      * 0086 Created OS            00 'MS-DOS'
+      * [...] Omitted for brevity
+      * 0091 CRC                   F7D18982
+      * 0095 Compressed Length     00000007
+      * 0099 Uncompressed Length   FFFFFFFF
+      * [...] Omitted for brevity
+      * 00AF Filename              'first'
+      * 00B4 Extra ID #0001        0001 'ZIP64'
+      * 00B6   Length              0008
+      * 00B8   Uncompressed Size   0000000000000005
+      *
+      * @throws IOException if an error occurs creating the ZIP File
       */
!     private static void createZipFile() throws IOException {
!         ByteArrayOutputStream baos = new ByteArrayOutputStream();
!         try (ZipOutputStream zos = new ZipOutputStream(baos)) {
! 
+             // The 'first' entry will store 'uncompressed size' in the Zip64 format
+             ZipEntry e1 = new ZipEntry("first");
+ 
+             // Make an extra field with the correct size for an 8-byte 'uncompressed size'
+             // Zip64 field. Temporarily use the 'unknown' tag 0x9902 to make
+             // ZipEntry.setExtra0 skip parsing this as a Zip64.
+             // See APPNOTE.TXT, 4.6.1 Third Party Mappings
+             byte[] opaqueExtra = createBlankExtra((short) UNKNOWN_TAG, (short) Long.BYTES);
+             e1.setExtra(opaqueExtra);
+ 
+             zos.putNextEntry(e1);
+             zos.write(CONTENT);
+ 
+             // A second entry, not in Zip64 format
+             ZipEntry e2 = new ZipEntry("second");
+             zos.putNextEntry(e2);
+             zos.write(CONTENT);
+         }
+ 
+         byte[] zip = baos.toByteArray();
+ 
+         // Update the CEN of 'first' to use the Zip64 format
+         updateCENHeaderToZip64(zip);
+         Files.write(ZIP_FILE, zip);
      }
  
      /**
!      * Update the CEN entry of the "first" entry to use ZIP64 format for the
!      * 'uncompressed size' field. The updated extra field will have the following
+      * structure:
+      *
+      * 00B4 Extra ID #0001        0001 'ZIP64'
+      * 00B6   Length              0008
+      * 00B8   Uncompressed Size   0000000000000005
+      *
+      * @param zip the ZIP file to update to ZIP64
       */
!     private static void updateCENHeaderToZip64(byte[] zip) {
!         ByteBuffer buffer = ByteBuffer.wrap(zip).order(ByteOrder.LITTLE_ENDIAN);
!         // Find the offset of the first CEN header
!         int cenOffset = buffer.getInt(zip.length- ZipFile.ENDHDR + ZipFile.ENDOFF);
!         // Find the offset of the extra field
!         int nlen = buffer.getShort(cenOffset + ZipFile.CENNAM);
!         int extraOffset = cenOffset + ZipFile.CENHDR + nlen;
! 
!         // Change the header ID from 'unknown' to ZIP64
!         buffer.putShort(extraOffset, ZIP64_TAG);
!         // Update the 'uncompressed size' ZIP64 value to the actual uncompressed length
!         int fieldOffset = extraOffset
!                 + Short.BYTES // TAG
!                 + Short.BYTES; // data size
!         buffer.putLong(fieldOffset, CONTENT.length);
! 
!         // Set the 'uncompressed size' field of the CEN to 0xFFFFFFFF
!         buffer.putInt(cenOffset + ZipFile.CENLEN, ZIP64_MAGIC_VALUE);
      }
  
      /**
!      * Create an extra field with the given tag and data block size, and a
!      * blank data block.
+      * @return an extra field with the specified tag and size
+      * @param tag the header id of the extra field
+      * @param blockSize the size of the extra field's data block
       */
!     private static byte[] createBlankExtra(short tag, short blockSize) {
!         int size = Short.BYTES  // tag
!                 + Short.BYTES   // data block size
!                 + blockSize;   // data block;
! 
!         byte[] extra = new byte[size];
!         ByteBuffer.wrap(extra).order(ByteOrder.LITTLE_ENDIAN)
!                 .putShort(0, tag)
+                 .putShort(Short.BYTES, blockSize);
+         return extra;
      }
  
      /**
       * Make sure the needed test files do not exist prior to executing the test
       * @throws IOException
       */
!     @BeforeEach
      public void setUp() throws IOException {
          deleteFiles();
      }
  
      /**
       * Remove the files created for the test
       * @throws IOException
       */
!     @AfterEach
      public void tearDown() throws IOException {
          deleteFiles();
      }
+ 
+     /**
+      * Delete the files created for use by the test
+      * @throws IOException if an error occurs deleting the files
+      */
+     private static void deleteFiles() throws IOException {
+         Files.deleteIfExists(ZIP_FILE);
+     }
  }
< prev index next >