JDK-8143613 : Modify ZipInputStream to support later PKZIP standards.
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.util.jar
  • Affected Version: 8u66
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic
  • CPU: generic
  • Submitted: 2015-11-20
  • Updated: 2024-03-06
  • Resolved: 2024-01-25
Related Reports
Relates :  
Description
A DESCRIPTION OF THE REQUEST :
ZipInputStream throws a ZipException (with message "only DEFLATED entries can have EXT descriptor") when there is a zero-length entry and the compression method is not set to DEFLATE. This is compliant with PKZIP standard 2.04g - however, later versions of the PKZIP standard have relaxed this requirement and any compression method can be used for zero-length entries.

JUSTIFICATION :
From the Zip Format Specification [https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT] section 4.4.4 general purpose bit flag:

Bit 3: If this bit is set, the fields crc-32, compressed size and uncompressed size are set to zero in the local header. The correct values are put in the data descriptor immediately following the compressed data. (Note: PKZIP version 2.04g for DOS only recognizes this bit for method 8 compression, newer versions of PKZIP recognize this bit for any compression method.)

ZipInputStream appears to comply strictly to version 2.04g and throws a ZipException when the General Purpose Bit Flag is set and the compression is not set to DEFLATE (method 8).

This is not compliant with later PKZIP versions and causes ZipInputStream to be unable to read some zip files.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
ZipInputStream to handle zip files which are compliant to the latest PKZip standard.
ACTUAL -
ZipInputStream throws ZipException when it reads a zip file which is valid under a later specification.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.util.zip.*;

public class TestZip {
    public static void main( final String[] args ) throws IOException {
        final File file = new File( args[0] );
        if ( file.exists() )
        {
            System.out.println( "File already exists" );
            return;
        }
        final boolean general_purpose_bit_flag_bit3_on = true;
        final byte gpbf = general_purpose_bit_flag_bit3_on ? 0x08 : 0x00;
        
        final byte[] contents = new byte[]{
            // Local File header
            'P', 'K', 3, 4,     // Local File Header Signature
            13, 0,              // Version needed to extract
            gpbf, 8,            // General purpose bit flag
            ZipEntry.STORED, 0, // Compression method
            'q', 'l', 't', 'G', // Last Modification time & date
            0, 0, 0, 0,         // CRC32
            0, 0, 0, 0,         // Compressed Size
            0, 0, 0, 0,         // Uncompressed Size
            12, 0,              // File name length
            0, 0,               // Extra field length
            'F', 'o', 'l', 'd', 'e', 'r', '_', 'n', 'a', 'm', 'e', '/',
                                // File name
            // Central directory file header
            'P', 'K', 1, 2,     // Central Directory File Header Signature
            13, 0,              // Version made by
            13, 0,              // Version needed to extract
            gpbf, 8,            // General purpose bit flag
            ZipEntry.STORED, 0, // Compression method
            'q', 'l', 't', 'G', // Last Modification time & date
            0, 0, 0, 0,         // CRC32
            0, 0, 0, 0,         // Compressed Size
            0, 0, 0, 0,         // Uncompressed Size
            12, 0,              // File name length
            0, 0,               // Extra field length
            0, 0,               // File comment length
            0, 0,               // Disk number where file starts
            0, 0,               // Internal File attributes
            0, 0, 0, 0,         // External File attributes
            0, 0, 0, 0,         // Relative offset of local header file
            'F', 'o', 'l', 'd', 'e', 'r', '_', 'n', 'a', 'm', 'e', '/',
                                // File name
            // End of Central Directory Record
            'P', 'K', 5, 6,     // Local File Header Signature
            0, 0,               // Number of this disk
            0, 0,               // Disk where CD starts
            1, 0,               // Number of CD records on this disk
            1, 0,               // Total number of records
            58, 0, 0, 0,        // Size of CD
            42, 0, 0, 0,        // Offset of start of CD
            0, 0,               // Comment length
        };
        try ( FileOutputStream fos = new FileOutputStream( file ) )
        {
            fos.write(contents);
        }
        try ( ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) ) )
        {
            ZipEntry entry = zis.getNextEntry();
            System.out.println( entry.getName() );
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None found


Comments
Closing because implementing streaming mode parsing for the STORED compression method is inherently impossible.
25-01-2024

I see multiple problems with this bug report: 1: The test vector in the reproducer is not valid APPNOTE.TXT specifies: ``` 4.3.9 Data descriptor: 4.3.9.1 This descriptor MUST exist if bit 3 of the general purpose bit flag is set (see below). ``` The test vector included in the reproducer DOES NOT include a data descriptor record, hence it is invalid according to APPNOTE.TXT. 2: How would ZipInputStream know where the file data ends for a STORED entry? Even if the test vector in the repoducer was updated to include a Data Descriptor record, how would ZipInputStream know when it has found the end of the file data? The STORED compression method simply stores the text "as is", there is no metadata in the format telling when the end of the entry is reached. One could scan for the Data Descriptor signature, but that would be unsafe since the file data could include this value, moreover the signature is optional. I think this issue can be closed as wont-fix.
25-01-2024

Moving across to dev -team for evaluation.
23-11-2015