The test/regression/ZipVerify.java tests should be updated with
the file I have attached to this email. I have also attached
a new file called ZipVerifyUtils.java that is needed by the
ZipVerify.java class. This new regression test now checks the
output of 1 entry, 2 entry, and directory entry zip files.

Mo Dejong
Red Hat Inc.
// This class will write a zip file into memory and verify that the
// contents being written matches the expected output.

import java.io.*;
import java.util.*;
import java.util.zip.*;

public class ZipVerify extends ZipVerifyUtils {

    public static void main(String[] argv) throws Exception
    {
        TimeZone.setDefault(TimeZone.getTimeZone("GMT"));

        // Set debug to false and no "output" messages will be displayed
        // Error messages will still be displayed
        debug = false;

        // This will dump the entire stream unless set to false
        debug_stream = false;

        checkSingleEntry(false);
        checkSingleEntry(true);

        checkSingleEntry2(false);
        checkSingleEntry2(true);

        checkDirectoryEntry();

        checkTwoEntries(false);
        checkTwoEntries(true);

    }
    
    public static void checkSingleEntry(boolean compressed) throws Exception
    {
        // 1/1/1984 12:30 and 30 seconds
        long expected_unix_time = 0x66ddd29670L;

        // 1/1/1984 12:30 and 30 seconds ( this is 0x66ddd29670L converted to dostime)
        byte[] expected_dostime = {-49, 99, 33, 8};

        // The entry name we pass to ZipEntry(String)
        byte[] expected_file_name = toBytes("data");

        // "extra" data segment that is added to a ZipEntry
        byte[] expected_extra_field = toBytes("hi");

        byte[] uncompressed_file_data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        byte[] compressed_file_data = {99, 96, 100, 98, 102, 97, 101, 99, -25, -32, 4, 
0};


        // The total size of the zip file
        int uncompressed_zipfile_length = 120;
        int compressed_zipfile_length = 138;

        // The CRC checksum we expect to find
        long expected_checksum = 1164760902L;

        // Zip internal data
        long uncompressed_rel_header_offset = 0;
        long compressed_rel_header_offset = 0;
        int total_disknumber = 1;
        int total_central = 1;
        long expected_cen_size = 52;
        long uncompressed_cen_offset = 46;
        long compressed_cen_offset = 64;

        // ---------------------------------------------

        EntryRecord[] entries = new EntryRecord[1];
        entries[0] = new EntryRecord(expected_file_name,
                                     uncompressed_file_data,
                                     expected_extra_field,
                                     expected_unix_time);

        byte[] data = generateAndVerifyZipData(compressed,
                                               entries,
                                               uncompressed_zipfile_length,
                                               compressed_zipfile_length);


        // file offset used to keep track of each field position in the stream
        int zip_offset = 0;

        zip_offset += verifyLOCHeader(compressed,
                                      expected_file_name,
                                      uncompressed_file_data,
                                      compressed_file_data,
                                      expected_extra_field,
                                      expected_dostime,
                                      data,
                                      zip_offset);

        zip_offset += verifyNameAndExtra(expected_file_name,
                                         expected_extra_field,
                                         data,
                                         zip_offset);

        zip_offset += verifyFileData(compressed,
                                     uncompressed_file_data,
                                     compressed_file_data,
                                     data,
                                     zip_offset);

        // If we used compression, a DATA header should show up next

        if (compressed) {
            zip_offset += verifyDATAHeader(uncompressed_file_data,
                                           compressed_file_data,
                                           expected_checksum,
                                           data,
                                           zip_offset);
        }

        zip_offset += verifyCENHeader(compressed,
                                      expected_file_name,
                                      uncompressed_file_data,
                                      compressed_file_data,
                                      expected_extra_field,
                                      expected_dostime,
                                      expected_checksum,
                                      uncompressed_rel_header_offset,
                                      compressed_rel_header_offset,
                                      data,
                                      zip_offset);


        zip_offset += verifyNameAndExtra(expected_file_name,
                                         expected_extra_field,
                                         data,
                                         zip_offset);

        zip_offset += verifyENDHeader(compressed,
                                      total_disknumber,
                                      total_central,
                                      expected_cen_size,
                                      uncompressed_cen_offset,
                                      compressed_cen_offset,
                                      data,
                                      zip_offset);

        verifyEOF(data, zip_offset);
    }

    public static void checkSingleEntry2(boolean compressed) throws Exception
    {
        // 1/1/1984 12:30 and 30 seconds
        long expected_unix_time = 0x66ddd29670L;

        // 1/1/1984 12:30 and 30 seconds ( this is 0x66ddd29670L converted to dostime)
        byte[] expected_dostime = {-49, 99, 33, 8};

        // The entry name we pass to ZipEntry(String)
        byte[] expected_file_name = toBytes("file1");

        // "extra" data segment that is added to a ZipEntry
        byte[] expected_extra_field = null;

        byte[] uncompressed_file_data = {5, 4, 3, 2, 1, 0};
        byte[] compressed_file_data = {99, 101, 97, 102, 98, 100, 0, 0};

        // The total size of the zip file
        int uncompressed_zipfile_length = 114;
        int compressed_zipfile_length = 132;

        // The CRC checksum we expect to find
        long expected_checksum = 480631825L;

        // Zip internal data
        long uncompressed_rel_header_offset = 0;
        long compressed_rel_header_offset = 0;
        int total_disknumber = 1;
        int total_central = 1;
        long expected_cen_size = 51;
        long uncompressed_cen_offset = 41;
        long compressed_cen_offset = 59;

        // ---------------------------------------------


        EntryRecord[] entries = new EntryRecord[1];
        entries[0] = new EntryRecord(expected_file_name,
                                     uncompressed_file_data,
                                     expected_extra_field,
                                     expected_unix_time);

        byte[] data = generateAndVerifyZipData(compressed,
                                               entries,
                                               uncompressed_zipfile_length,
                                               compressed_zipfile_length);


        // file offset used to keep track of each field position in the stream
        int zip_offset = 0;

        zip_offset += verifyLOCHeader(compressed,
                                      expected_file_name,
                                      uncompressed_file_data,
                                      compressed_file_data,
                                      expected_extra_field,
                                      expected_dostime,
                                      data,
                                      zip_offset);

        zip_offset += verifyNameAndExtra(expected_file_name,
                                         expected_extra_field,
                                         data,
                                         zip_offset);

        zip_offset += verifyFileData(compressed,
                                     uncompressed_file_data,
                                     compressed_file_data,
                                     data,
                                     zip_offset);

        // If we used compression, a DATA header should show up next

        if (compressed) {
            zip_offset += verifyDATAHeader(uncompressed_file_data,
                                           compressed_file_data,
                                           expected_checksum,
                                           data,
                                           zip_offset);
        }

        zip_offset += verifyCENHeader(compressed,
                                      expected_file_name,
                                      uncompressed_file_data,
                                      compressed_file_data,
                                      expected_extra_field,
                                      expected_dostime,
                                      expected_checksum,
                                      uncompressed_rel_header_offset,
                                      compressed_rel_header_offset,
                                      data,
                                      zip_offset);


        zip_offset += verifyNameAndExtra(expected_file_name,
                                         expected_extra_field,
                                         data,
                                         zip_offset);

        zip_offset += verifyENDHeader(compressed,
                                      total_disknumber,
                                      total_central,
                                      expected_cen_size,
                                      uncompressed_cen_offset,
                                      compressed_cen_offset,
                                      data,
                                      zip_offset);

        verifyEOF(data, zip_offset);
    }

    // The trick about directory entries is that they are never compressed

    public static void checkDirectoryEntry() throws Exception
    {
        // 1/1/1984 12:30 and 30 seconds
        long expected_unix_time = 0x66ddd29670L;

        // 1/1/1984 12:30 and 30 seconds ( this is 0x66ddd29670L converted to dostime)
        byte[] expected_dostime = {-49, 99, 33, 8};

        // The entry name we pass to ZipEntry(String)
        byte[] expected_file_name = toBytes("foo/");

        // "extra" data segment that is added to a ZipEntry
        byte[] expected_extra_field = null;

        byte[] uncompressed_file_data = null;
        byte[] compressed_file_data = null;

        // The total size of the zip file
        int uncompressed_zipfile_length = 106;
        int compressed_zipfile_length = 106;

        // The CRC checksum we expect to find
        long expected_checksum = 0;

        // Zip internal data
        long uncompressed_rel_header_offset = 0;
        long compressed_rel_header_offset = 0;
        int total_disknumber = 1;
        int total_central = 1;
        long expected_cen_size = 50;
        long uncompressed_cen_offset = 34;
        long compressed_cen_offset = 34;

        boolean compressed = false;

        // ---------------------------------------------


        EntryRecord[] entries = new EntryRecord[1];
        entries[0] = new EntryRecord(expected_file_name,
                                     uncompressed_file_data,
                                     expected_extra_field,
                                     expected_unix_time);

        byte[] data = generateAndVerifyZipData(compressed,
                                               entries,
                                               uncompressed_zipfile_length,
                                               compressed_zipfile_length);


        // file offset used to keep track of each field position in the stream
        int zip_offset = 0;

        zip_offset += verifyLOCHeader(compressed,
                                      expected_file_name,
                                      uncompressed_file_data,
                                      compressed_file_data,
                                      expected_extra_field,
                                      expected_dostime,
                                      data,
                                      zip_offset);

        zip_offset += verifyNameAndExtra(expected_file_name,
                                         expected_extra_field,
                                         data,
                                         zip_offset);

        // No file_data or DATA header for a directory entry

        zip_offset += verifyCENHeader(compressed,
                                      expected_file_name,
                                      uncompressed_file_data,
                                      compressed_file_data,
                                      expected_extra_field,
                                      expected_dostime,
                                      expected_checksum,
                                      uncompressed_rel_header_offset,
                                      compressed_rel_header_offset,
                                      data,
                                      zip_offset);


        zip_offset += verifyNameAndExtra(expected_file_name,
                                         expected_extra_field,
                                         data,
                                         zip_offset);

        zip_offset += verifyENDHeader(compressed,
                                      total_disknumber,
                                      total_central,
                                      expected_cen_size,
                                      uncompressed_cen_offset,
                                      compressed_cen_offset,
                                      data,
                                      zip_offset);

        verifyEOF(data, zip_offset);
    }

    // Write two entries to a zip file and verify the output

    public static void checkTwoEntries(boolean compressed) throws Exception
    {
        // 1/1/1984 12:30 and 30 seconds
        long expected_unix_time = 0x66ddd29670L;

        // 1/1/1984 12:30 and 30 seconds ( this is 0x66ddd29670L converted to dostime)
        byte[] expected_dostime = {-49, 99, 33, 8};

        // The entry name we pass to ZipEntry(String)
        byte[] expected_file_name_one = toBytes("data");

        // "extra" data segment that is added to a ZipEntry
        byte[] expected_extra_field_one = toBytes("hi");

        byte[] uncompressed_file_data_one = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        byte[] compressed_file_data_one = {99, 96, 100, 98, 102, 97, 101, 99, -25, 
-32, 4, 0};

        // The entry name we pass to ZipEntry(String)
        byte[] expected_file_name_two = toBytes("file1");

        // "extra" data segment that is added to a ZipEntry
        byte[] expected_extra_field_two = null;

        byte[] uncompressed_file_data_two = {5, 4, 3, 2, 1, 0};
        byte[] compressed_file_data_two = {99, 101, 97, 102, 98, 100, 0, 0};


        // The total size of the zip file
        int uncompressed_zipfile_length = 212;
        int compressed_zipfile_length = 248;

        // The CRC checksums we expect to find
        long expected_checksum_one = 1164760902L;
        long expected_checksum_two = 480631825L;

        // Zip internal data
        long uncompressed_rel_header_offset_one = 0;
        long compressed_rel_header_offset_one = 0;
        long uncompressed_rel_header_offset_two = 46;
        long compressed_rel_header_offset_two = 64;

        int total_disknumber = 2;
        int total_central = 2;

        long expected_cen_size = 103;
        long uncompressed_cen_offset = 87;
        long compressed_cen_offset = 123;

        // ---------------------------------------------


        EntryRecord[] entries = new EntryRecord[2];
        entries[0] = new EntryRecord(expected_file_name_one,
                                     uncompressed_file_data_one,
                                     expected_extra_field_one,
                                     expected_unix_time);

        entries[1] = new EntryRecord(expected_file_name_two,
                                     uncompressed_file_data_two,
                                     expected_extra_field_two,
                                     expected_unix_time);

        byte[] data = generateAndVerifyZipData(compressed,
                                               entries,
                                               uncompressed_zipfile_length,
                                               compressed_zipfile_length);


        // file offset used to keep track of each field position in the stream
        int zip_offset = 0;

        zip_offset += verifyLOCHeader(compressed,
                                      expected_file_name_one,
                                      uncompressed_file_data_one,
                                      compressed_file_data_one,
                                      expected_extra_field_one,
                                      expected_dostime,
                                      data,
                                      zip_offset);

        zip_offset += verifyNameAndExtra(expected_file_name_one,
                                         expected_extra_field_one,
                                         data,
                                         zip_offset);

        zip_offset += verifyFileData(compressed,
                                     uncompressed_file_data_one,
                                     compressed_file_data_one,
                                     data,
                                     zip_offset);

        // If we used compression, a DATA header should show up next

        if (compressed) {
            zip_offset += verifyDATAHeader(uncompressed_file_data_one,
                                           compressed_file_data_one,
                                           expected_checksum_one,
                                           data,
                                           zip_offset);
        }

        zip_offset += verifyLOCHeader(compressed,
                                      expected_file_name_two,
                                      uncompressed_file_data_two,
                                      compressed_file_data_two,
                                      expected_extra_field_two,
                                      expected_dostime,
                                      data,
                                      zip_offset);

        zip_offset += verifyNameAndExtra(expected_file_name_two,
                                         expected_extra_field_two,
                                         data,
                                         zip_offset);

        zip_offset += verifyFileData(compressed,
                                     uncompressed_file_data_two,
                                     compressed_file_data_two,
                                     data,
                                     zip_offset);

        // If we used compression, a DATA header should show up next

        if (compressed) {
            zip_offset += verifyDATAHeader(uncompressed_file_data_two,
                                           compressed_file_data_two,
                                           expected_checksum_two,
                                           data,
                                           zip_offset);
        }

        // CEN for entry one

        zip_offset += verifyCENHeader(compressed,
                                      expected_file_name_one,
                                      uncompressed_file_data_one,
                                      compressed_file_data_one,
                                      expected_extra_field_one,
                                      expected_dostime,
                                      expected_checksum_one,
                                      uncompressed_rel_header_offset_one,
                                      compressed_rel_header_offset_one,
                                      data,
                                      zip_offset);


        zip_offset += verifyNameAndExtra(expected_file_name_one,
                                         expected_extra_field_one,
                                         data,
                                         zip_offset);

        // CEN for entry two

        zip_offset += verifyCENHeader(compressed,
                                      expected_file_name_two,
                                      uncompressed_file_data_two,
                                      compressed_file_data_two,
                                      expected_extra_field_two,
                                      expected_dostime,
                                      expected_checksum_two,
                                      uncompressed_rel_header_offset_two,
                                      compressed_rel_header_offset_two,
                                      data,
                                      zip_offset);

        zip_offset += verifyNameAndExtra(expected_file_name_two,
                                         expected_extra_field_two,
                                         data,
                                         zip_offset);

        zip_offset += verifyENDHeader(compressed,
                                      total_disknumber,
                                      total_central,
                                      expected_cen_size,
                                      uncompressed_cen_offset,
                                      compressed_cen_offset,
                                      data,
                                      zip_offset);

        verifyEOF(data, zip_offset);
    }

}
// Utils used by the ZipVerify class

import java.io.*;
import java.util.zip.*;
import java.util.*;

public class ZipVerifyUtils {
    public static boolean debug = true;
    public static boolean debug_stream = true;

    // A Util class used to pass entry data to generateAndVerifyZipData()
    public static class EntryRecord {
        public EntryRecord(byte[] expected_file_name,
                           byte[] uncompressed_file_data,
                           byte[] expected_extra_field,
                           long expected_unix_time) {
            this.expected_file_name = expected_file_name;
            this.uncompressed_file_data = uncompressed_file_data;
            this.expected_extra_field = expected_extra_field;
            this.expected_unix_time = expected_unix_time;
        }

        byte[] expected_file_name;
        byte[] uncompressed_file_data;
        byte[] expected_extra_field;
        long expected_unix_time;
    }

    public static long getTime(int year, int month, int date, int hour,
                               int minute, int second)
    {
        GregorianCalendar c;

        // This is broken, need to call constructor with times!
        //c = new GregorianCalendar();
        //c.set(year, month, date, hour, minute, second);

        c = new GregorianCalendar(year, month, date, hour, minute, second);

        return c.getTime().getTime();
    }
    
    public static int get16(byte[] buf, int base) {
        int val = (int)buf[base] & 0xFF;
        val |= ((int)buf[base+1] & 0xFF) << 8;
        return (val);
    }

    public static int get32(byte[] buf, int base) {
        int val = (int)buf[base] & 0xFF;
        val |= ((int)buf[base+1] & 0xFF) << 8;
        val |= ((int)buf[base+2] & 0xFF) << 16;
        val |= ((int)buf[base+3] & 0xFF) << 24;
        return (val);
    }

    public static void put16(byte[] array, int pos, int val) {
        array[pos]   = (byte)val;
        array[pos+1] = (byte)(val >>> 8);
    }

    public static void put32(byte[] array, int pos, int val) {
        array[pos]   = (byte) val;
        array[pos+1] = (byte)(val >>> 8);
        array[pos+2] = (byte)(val >>> 16);
        array[pos+3] = (byte)(val >>> 24);
    }

    // This is a quick little helper method to convert a String
    // into an array of bytes

    public static byte[] toBytes(String value) {
        final int len = value.length();
        byte[] bytes = new byte[len];

        for (int i=0; i < len; i++) {
            bytes[i] = (byte) value.charAt(i);
        }
        return bytes;
    }

    // This method will do something to signal an error
    // but it should not actually stop execution

    public static void error(String err) throws Exception
    {
        // throw new RuntimeError(err);
        System.err.println(err);
    }

    // Just print the given message without an "error"

    public static void output(String msg) throws Exception
    {
        System.out.println(msg);
    }


    // Compare the bytes in data at the given offset to the expected array
    // if there is a mismatch, print those bytes that do not match
    
    static int expect(String field, byte[] data, int zip_offset, byte[] expected)
    throws Exception
    {
        if (expected == null)
            return 0;

        for (int i=0; i < expected.length; i++) {
            if (expected[i] != data[i + zip_offset]) {
                error(field + "[" + i + "] is " +
                       data[i + zip_offset] + " expected " +
                       expected[i] + " at field offset " + zip_offset);
            }
        }

        return expected.length;
    }

    // Same as expect() above except mismatches are printed as char types

    static int expectC(String field, byte[] data, int zip_offset, byte[] expected)
    throws Exception
    {
        if (expected == null)
            return 0;

        for (int i=0; i < expected.length; i++) {
            if (expected[i] != data[i + zip_offset]) {
                error(field + "[" + i + "] is '" +
                       (char) data[i + zip_offset] + "' expected '" +
                       (char) expected[i] + "' at field offset " + zip_offset);
            }
        }

        return expected.length;
    }

    // Compare two 16 bit numbers and display and error if they do not match

    static int expect(String field, byte[] data, int zip_offset, int expected)
    throws Exception
    {
        int num = get16(data, zip_offset);

        if (num != expected) {
            error(field + " is " + num +
                  " expected " + expected
                  + " at field offset " + zip_offset);
        }

        return 2; // Return the number of bytes to advance in the stream
    }

    // Compare two 32 bit numbers and display and error if they do not match

    static int expectL(String field, byte[] data, int zip_offset, long expected)
    throws Exception
    {
        long num = (long) get32(data, zip_offset);

        if (num != expected) {
            error(field + " is " + num +
                  " expected " + expected
                  + " at field offset " + zip_offset);
        }

        return 4; // Return the number of bytes to advance in the stream
    }

    // This method is used to add an uncompressed entry to the given ZipOutputStream

    public static void addEntry(ZipOutputStream zout, boolean compressed,
                                byte[] name, byte[] bytes, byte[] extra, long 
unix_time)
        throws Exception
    {
        // Convert byte[] to String
        StringBuffer name_buf = new StringBuffer();
        for (int i=0; i < name.length; i++) {
            name_buf.append( (char) name[i] );
        }
        String name_str = name_buf.toString();
 
        ZipEntry ze = new ZipEntry(name_str);

        int bytes_length = (bytes == null ? 0 : bytes.length);

        if (compressed) {
            ze.setMethod(ZipEntry.DEFLATED);
        } else {
            ze.setMethod(ZipEntry.STORED);
            ze.setSize(bytes_length);
            ze.setCrc(0);
        }

        ze.setExtra(extra);

        ze.setTime(unix_time);

        zout.putNextEntry(ze);

        if (bytes_length > 0) {

            zout.write(bytes);

            if (! compressed) {
                CRC32 crc = new CRC32();
                crc.update(bytes);      
                ze.setCrc( crc.getValue() );
            }

        }

        zout.closeEntry();

        if (debug) {
            output("added entry \"" + name_str
                               + "\", data size = " + bytes_length);
        }
    }

    // This method will create a byte[] containing data from a zip stream
    // and do some simple checks on the size of the data generated

    public static byte[] generateAndVerifyZipData(boolean compressed,
                                                  EntryRecord[] entries,
                                                  int uncompressed_zipfile_length,
                                                  int compressed_zipfile_length)
        throws Exception
    {
        // Create the in-memory output stream the zip will be written to

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        // Write a zip stream containing the data we want to add

        ZipOutputStream zout = new ZipOutputStream(baos);

        for (int i=0; i < entries.length; i++) {
            EntryRecord er = entries[i];
            addEntry(zout, compressed, er.expected_file_name,
                     er.uncompressed_file_data, er.expected_extra_field,
                     er.expected_unix_time);
        }

        zout.close();

        // Get the bytes written to the stream as an array

        byte[] data = baos.toByteArray();

        // Display all the bytes that were written to the zip file!

        if (debug_stream) {
            for (int i=0; i < data.length; i++) {
                output(i + "\t" + data[i] +
                                   "\t (char) " + ((char) data[i]));
            }
        }

        if (debug) {
            output((compressed ? "compressed" : "uncompressed") + " zip file 
written");
        }

        // Display the total number of bytes written

        if (debug) {
            output("zip bytes written = " + data.length);
        }

        int zipfile_length = uncompressed_zipfile_length;

        if (compressed) {
            zipfile_length = compressed_zipfile_length;
        }

        if (data.length != zipfile_length){
            error("length of zip file should be " + zipfile_length + " bytes it was " 
+
                  data.length + " bytes");
        }

        return data;
    }


    // This method will read a LOC header and verify that
    // the fields in the header match the expected results.
    // This method returns the new zip_offset.

    public static int verifyLOCHeader(boolean compressed,
                                      byte[] expected_file_name,
                                      byte[] uncompressed_file_data,
                                      byte[] compressed_file_data,
                                      byte[] expected_extra_field,
                                      byte[] expected_dostime,
                                      byte[] data,
                                      int zip_offset)
        throws Exception
    {
        // u4 LOC signature {80, 75, 3, 4}

        byte[] expected_loc_signature = {80, 75, 3, 4};

        int old_zip_offset = zip_offset;

        int uncompressed_file_data_length = (uncompressed_file_data == null ? 0 :
                                             uncompressed_file_data.length);

        int compressed_file_data_length = (compressed_file_data == null ? 0 :
                                             compressed_file_data.length);

        int expected_extra_field_length = (expected_extra_field == null ? 0
                                           : expected_extra_field.length);

        zip_offset += expect("loc_signature", data, zip_offset, 
expected_loc_signature);


        // u2 version_needed_to_extract (10 or 20)

        zip_offset += expect("extract_version", data, zip_offset,
                             (compressed ? 20 : 10));


        // u2 zip entry flags (seems to be set to 0 or 8)
        // what is this for ??? is it like the compression method ???
        
        zip_offset += expect("flags", data, zip_offset,
                             (compressed ? 8 : 0));
        

        // u2 compression_method (0 or 8)
 
        zip_offset += expect("compression_method", data, zip_offset,
                             (compressed ? 8 : 0));

        // u4 dostime

        zip_offset += expect("dostime", data, zip_offset, expected_dostime);


        // u4 CRC32 checksum

        zip_offset += expectL("crc32", data, zip_offset, 0L);


        // u4 compressed_size

        zip_offset += expectL("compressed_size", data, zip_offset,
                             (compressed ? 0 : uncompressed_file_data_length));

        // u4 uncompressed_size

        zip_offset += expectL("uncompressed_size", data, zip_offset,
                             (compressed ? 0 : uncompressed_file_data_length));


        // u2 filename_length

        zip_offset += expect("filename_length", data, zip_offset,
                             expected_file_name.length);

        // u2 extra_field_length

        zip_offset += expect("extra_field_length", data, zip_offset,
                             expected_extra_field_length);

        if (debug) {
            output("after reading LOC Header, zip_offset is " + zip_offset);
        }

        if ((zip_offset - old_zip_offset) != 30) {
            error("LOC header size should be 30, it was " + (zip_offset - 
old_zip_offset));
        }

        return (zip_offset - old_zip_offset);
    }

    // verify the name, extra, and data fields that show up after
    // a LOC header. This method returns the new zip_offset.

    public static int verifyNameAndExtra(byte[] expected_file_name,
                                         byte[] expected_extra_field,
                                         byte[] data,
                                         int zip_offset)
        throws Exception
    {
        int old_zip_offset = zip_offset;

        // The entry name, we called ZipEntry() with it

        zip_offset += expectC("file_name", data, zip_offset, expected_file_name);

        // Check for "extra" data segment, it can be zero so
        // there may not be any data here!

        zip_offset += expectC("extra_field", data, zip_offset, expected_extra_field);

        if (debug) {
            output("after reading Name-Extra, zip_offset is " + zip_offset);
        }

        return (zip_offset - old_zip_offset);
    }

    // verify the actual file data from the entry

    public static int verifyFileData(boolean compressed,
                                     byte[] uncompressed_file_data,
                                     byte[] compressed_file_data,
                                     byte[] data,
                                     int zip_offset)
        throws Exception
    {
        int old_zip_offset = zip_offset;

        zip_offset += expect("file_data", data, zip_offset,
                             (compressed ? compressed_file_data :
                              uncompressed_file_data) );

        if (debug) {
            output("after reading File-Data, zip_offset is " + zip_offset);
        }

        return (zip_offset - old_zip_offset);
    }

    // This method will read a DATA header and verify that
    // the fields in the header match the expected results.
    // This method returns the new zip_offset.

    public static int verifyDATAHeader(byte[] uncompressed_file_data,
                                       byte[] compressed_file_data,
                                       long expected_checksum,
                                       byte[] data,
                                       int zip_offset)
        throws Exception
    {
        // A DATA header should show up after a compressed entry

        byte[] compressed_post_data_header   = {80, 75, 7, 8}; // aka DATA

        int old_zip_offset = zip_offset;

        int uncompressed_file_data_length = (uncompressed_file_data == null ? 0 :
                                             uncompressed_file_data.length);

        int compressed_file_data_length = (compressed_file_data == null ? 0 :
                                             compressed_file_data.length);

        zip_offset += expect("data_header", data, zip_offset, 
compressed_post_data_header);

        // u4 CRC32 checksum

        zip_offset += expectL("crc32", data, zip_offset, expected_checksum);

        // u4 compressed_size

        zip_offset += expectL("compressed_size", data, zip_offset,
                              compressed_file_data_length);

        // u4 uncompressed_size

        zip_offset += expectL("uncompressed_size", data, zip_offset,
                              uncompressed_file_data_length);

        if (debug) {
            output("after reading DATA header, zip_offset is " + zip_offset);
        }

        if ((zip_offset - old_zip_offset) != 16) {
            error("DATA header size should be 16, it was " + (zip_offset - 
old_zip_offset));
        }

        return (zip_offset - old_zip_offset);
    }

    // This method will read a CEN header and verify that
    // the fields in the header match the expected results.
    // This method returns the new zip_offset.

    public static int verifyCENHeader(boolean compressed,
                                      byte[] expected_file_name,
                                      byte[] uncompressed_file_data,
                                      byte[] compressed_file_data,
                                      byte[] expected_extra_field,
                                      byte[] expected_dostime,
                                      long expected_checksum,
                                      long uncompressed_rel_header_offset,
                                      long compressed_rel_header_offset,
                                      byte[] data,
                                      int zip_offset)
        throws Exception
    {
        // A CEN header should show up after an uncompressed entry or
        // after the DATA header that follows a compressed entry

        byte[] uncompressed_post_data_header = {80, 75, 1, 2}; // aka CEN

        int old_zip_offset = zip_offset;

        int uncompressed_file_data_length = (uncompressed_file_data == null ? 0 :
                                             uncompressed_file_data.length);

        int compressed_file_data_length = (compressed_file_data == null ? 0 :
                                             compressed_file_data.length);

        int expected_extra_field_length = (expected_extra_field == null ? 0
                                           : expected_extra_field.length);

        zip_offset += expect("cen_header", data, zip_offset, 
uncompressed_post_data_header);

        // u2 version_used_to_write (10 or 20)

        zip_offset += expect("write_version", data, zip_offset,
                                 (compressed ? 20 : 10));
        

        // u2 version_needed_to_extract (10 or 20)

        zip_offset += expect("extract_version", data, zip_offset,
                                 (compressed ? 20 : 10));
        

        // u2 zip entry flags (seems to be set to 0 or 8)
        // what is this for ??? is it like the compression method ???

        zip_offset += expect("flags", data, zip_offset,
                             (compressed ? 8 : 0));

        // u2 compression_method (0 or 8)

        zip_offset += expect("compression_method", data, zip_offset,
                             (compressed ? 8 : 0));

        // u4 dostime

        zip_offset += expect("dostime", data, zip_offset, expected_dostime);


        // u4 CRC32 checksum

        zip_offset += expectL("crc32", data, zip_offset, expected_checksum);

        // u4 compressed_size

        zip_offset += expectL("compressed_size", data, zip_offset,
                             (compressed ? compressed_file_data_length :
                              uncompressed_file_data_length));

        // u4 uncompressed_size

        zip_offset += expectL("uncompressed_size", data, zip_offset,
                              uncompressed_file_data_length);

        // u2 filename_length

        zip_offset += expect("filename_length", data, zip_offset,
                             expected_file_name.length);

        // u2 extra_field_length

        zip_offset += expect("extra_field_length", data, zip_offset,
                             expected_extra_field_length);
        
        // u2 comment_length

        zip_offset += expect("comment_length", data, zip_offset, 0);

        // u2 disknum

        zip_offset += expect("disknum", data, zip_offset, 0);

        // u2 internal_file_attributes

        zip_offset += expect("internal_file_attributes", data, zip_offset, 0);

        // u4 external_file_attributes

        zip_offset += expectL("external_file_attributes", data, zip_offset, 0);

        // u4 relative_offset_of_local_header

        zip_offset += expectL("relative_offset_of_local_header", data, zip_offset,
                              (compressed ? compressed_rel_header_offset :
                               uncompressed_rel_header_offset));

        if (debug) {
            output("after reading CEN header, zip_offset is " + zip_offset);
        }

        if ((zip_offset - old_zip_offset) != 46) {
            error("CEN header size should be 46, it was " + (zip_offset - 
old_zip_offset));
        }

        return (zip_offset - old_zip_offset);
    }


    // This method will read an END header and verify that
    // the fields in the header match the expected results.
    // This method returns the new zip_offset.

    public static int verifyENDHeader(boolean compressed,
                                      int total_disknumber,
                                      int total_central,
                                      long expected_cen_size,
                                      long uncompressed_cen_offset,
                                      long compressed_cen_offset,
                                      byte[] data,
                                      int zip_offset)
        throws Exception
    {
        byte[] expected_end_signature = {80, 75, 5, 6}; // aka END

        int old_zip_offset = zip_offset;

        zip_offset += expect("end_signature", data, zip_offset, 
expected_end_signature);

        // u2 disknumber

        zip_offset += expect("disknumber", data, zip_offset, 0);

        // u2 central_disknumber

        zip_offset += expect("central_disknumber", data, zip_offset, 0);

        // u2 total_disknumber

        zip_offset += expect("total_disknumber", data, zip_offset, total_disknumber);

        // u2 total_central

        zip_offset += expect("total_central", data, zip_offset, total_central);

        // u4 cen_size

        zip_offset += expectL("cen_size", data, zip_offset, expected_cen_size);

        // u2 cen_offset (index into file where CEN header begins)

        zip_offset += expectL("cen_offset", data, zip_offset,
                             (compressed ? compressed_cen_offset : 
uncompressed_cen_offset));

        // u2 comment_length

        zip_offset += expect("comment_length", data, zip_offset, 0);

        if (debug) {
            output("after reading END header, zip_offset is " + zip_offset);
        }

        if ((zip_offset - old_zip_offset) != 22) {
            error("END header size should be 22, it was " + (zip_offset - 
old_zip_offset));
        }

        return (zip_offset - old_zip_offset);
    }

    // This method will check that all the data in the stream
    // has been verified by comparing the zip_offset to the
    // length of the data array.

    public static void verifyEOF(byte[] data,
                                 int zip_offset)
        throws Exception
    {
        if (debug) {
            output("verified " + zip_offset + " bytes");
        }

        if (zip_offset != data.length) {
            error("zip_offset not at EOF");
        } else {
            if (debug) {
                output("zip_offset is at EOF");
            }
        }
    }

    // 
--------------------------------------------------------------------------------

    // Zip file format
    // Byte offset -> Size DESCRIPTION

    // zo is the zip_offset, the integer index into the stream
    // z0 = 0

    // First the LOC header appears

    // zo + 0  -> u4 LOC signature {80, 75, 3, 4}
    // zo + 4  -> u2 version_needed_to_extract (10 or 20)
    // zo + 6  -> u2 zip entry flags
    // zo + 8  -> u2 compression_method (0 or 8)
    // zo + 10 -> u4 dostime
    // zo + 14 -> u4 crc32
    // zo + 18 -> u4 compressed_size
    // zo + 22 -> u4 uncompressed_size
    // zo + 26 -> u2 filename_length
    // zo + 28 -> u2 extra_field_length

    // zo = 30

    // zo - (zo + filename_length) filename
    // zo - (zo + extra_field_length) extra_field

    // then the actual data from the file appears in the zip file

    // zo = (zo + filename_length + extra_field_length + data_length)


    // If the entry was compressed, then the DATA header will appear

    // zo + 0  -> u4 DATA signature {80, 75, 7, 8}
    // zo + 4  -> u4 data_crc
    // zo + 8  -> u4 data_compressedsize
    // zo + 12 -> u4 data_uncompressedsize


    // Now the CEN header will appear

    // zo + 0  -> u4 CEN signature {80, 75, 1, 2}
    // zo + 4  -> u2 version used to write zipfile (10 or 20)
    // zo + 6  -> u2 version needed to extract (10 or 20)
    // zo + 8  -> u2 flags
    // zo + 10 -> u2 compression_method
    // zo + 12 -> u4 dostime
    // zo + 16 -> u4 crc
    // zo + 20 -> u4 compressed_size
    // zo + 24 -> u4 uncompressed_size
    // zo + 28 -> u2 filename_length
    // zo + 30 -> u2 extra_field_length
    // zo + 32 -> u2 comment_length
    // zo + 34 -> u2 disknum
    // zo + 36 -> u2 internal_file_attributes
    // zo + 38 -> u4 external_file_attributes
    // zo + 42 -> u4 relative_offset_of_local_header

    // zo = zo + 46

    // zo - (zo + filename_length) filename
    // zo - (zo + extra_field_length) extra_field

    // Now the END header will appear

    // zo + 0  -> u4 END signature {80, 75, 5, 6}
    // zo + 4  -> u2 disknumber
    // zo + 6  -> u2 central_disknumber
    // zo + 8  -> u2 total_disknumber
    // zo + 10 -> u2 total_central
    // zo + 12 -> u4 cen_size
    // zo + 16 -> u4 cen_offset (index into file where CEN header begins)
    // zo + 20 -> u2 comment_length

    // ----------------------------------------------------------------------

    // If there are two entries in the zip file the headers appear like this

    // LOC (one)
    // ?DATA (one)?
    // LOC (two)
    // ?DATA (two)?
    // CEN (one)
    // CEN (two)
    // END

    // Three entries would look like this

    // LOC (one)
    // ?DATA (one)?
    // LOC (two)
    // ?DATA (two)?
    // LOC (three)
    // ?DATA (three)?
    // CEN (one)
    // CEN (two)
    // CEN (three)
    // END

}

Reply via email to