zip-core 0.0.4

zip implementation independent structs and helpers
Documentation
use crate::raw::{
    ArchiveExtraDataRecord, ArchiveExtraDataRecordFixed, CentralDirectoryHeader, CentralDirectoryHeaderFixed,
    DigitalSignature, DigitalSignatureFixed, EndOfCentralDirectory, EndOfCentralDirectoryFixed, ExtensibleData,
    ExtensibleDataFixed, LocalFileHeader, LocalFileHeaderFixed, Zip64EndOfCentralDirectory,
    Zip64EndOfCentralDirectoryFixed,
};

/// implemented for all dynamic records that contain a fixed element
pub trait PartialRecord {
    type Partial;

    fn get_partial(&self) -> &Self::Partial;
    /// all dynamic records contain dynamic data and length information in the
    /// fixed part. Verify if those 2 values are matching
    fn is_valid_sizes(&self) -> bool;
}

impl PartialRecord for LocalFileHeader {
    type Partial = LocalFileHeaderFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool {
        self.file_name.len() == self.fixed.file_name_length as usize
            && self.extra_field.len() == self.fixed.extra_field_length as usize
    }
}

impl PartialRecord for ArchiveExtraDataRecord {
    type Partial = ArchiveExtraDataRecordFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool { self.extra_field_data.len() == self.fixed.extra_field_length as usize }
}

impl PartialRecord for CentralDirectoryHeader {
    type Partial = CentralDirectoryHeaderFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool {
        self.file_name.len() == self.fixed.file_name_length as usize
            && self.extra_field.len() == self.fixed.extra_field_length as usize
            && self.file_comment.len() == self.fixed.file_comment_length as usize
    }
}

impl PartialRecord for DigitalSignature {
    type Partial = DigitalSignatureFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool { self.signature_data.len() == self.fixed.size_of_data as usize }
}

impl PartialRecord for Zip64EndOfCentralDirectory {
    type Partial = Zip64EndOfCentralDirectoryFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool {
        // as stated elsewhere, this is not just a length, but some part of a
        // calculation overflow protection check:
        const MIN_SIZE: usize = Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES - 12;
        if (self.fixed.size_of_zip64_end_of_central_directory_record as usize) < MIN_SIZE {
            return false;
        }
        let length = self.fixed.size_of_zip64_end_of_central_directory_record as usize - MIN_SIZE;
        self.zip64_extensible_data_sector.len() == length
    }
}

impl PartialRecord for EndOfCentralDirectory {
    type Partial = EndOfCentralDirectoryFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool { self.zip_file_comment.len() == self.fixed.zip_file_comment_length as usize }
}

impl PartialRecord for ExtensibleData {
    type Partial = ExtensibleDataFixed;

    fn get_partial(&self) -> &Self::Partial { &self.fixed }

    fn is_valid_sizes(&self) -> bool { self.data.len() == self.fixed.data_size as usize }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn partial_local_file_header() {
        let file_name = vec![48, 49, 32, 50];
        let extra_field = vec![61, 62, 32, 63, 64];
        let lf = LocalFileHeader {
            fixed:       LocalFileHeaderFixed {
                local_file_header_signature: LocalFileHeaderFixed::LOCAL_FILE_HEADER_SIGNATURE,
                version_needed_to_extract: 1,
                general_purpose_bit_flag: 2,
                compression_method: 3,
                last_mod_file_time: 4,
                last_mod_file_date: 5,
                crc_32: 6,
                compressed_size: 7,
                uncompressed_size: 8,
                file_name_length: file_name.len() as u16,
                extra_field_length: extra_field.len() as u16,
            },
            file_name:   file_name.clone(),
            extra_field: extra_field.clone(),
        };
        assert!(lf.is_valid_sizes());
        let _ = lf.get_partial();
    }

    #[test]
    fn partial_archive_extra_data_record() {
        let extra_field_data = vec![48, 49, 32, 50];
        let ad = ArchiveExtraDataRecord {
            fixed: ArchiveExtraDataRecordFixed {
                archive_extra_data_signature: 1,
                extra_field_length: extra_field_data.len() as u64,
            },
            extra_field_data: extra_field_data.clone(),
        };
        assert!(ad.is_valid_sizes());
        let _ = ad.get_partial();
    }

    #[test]
    fn partial_central_directory_header() {
        let file_name = vec![48, 49, 32, 50];
        let extra_field = vec![61, 62, 32, 63, 64];
        let file_comment = vec![51, 52, 53, 32, 54, 55];
        let cd = CentralDirectoryHeader {
            fixed:        CentralDirectoryHeaderFixed {
                central_file_header_signature: 1,
                version_made_by: 2,
                version_needed_to_extract: 3,
                general_purpose_bit_flag: 4,
                compression_method: 5,
                last_mod_file_time: 6,
                last_mod_file_date: 7,
                crc_32: 8,
                compressed_size: 9,
                uncompressed_size: 10,
                file_name_length: file_name.len() as u16,
                extra_field_length: extra_field.len() as u16,
                file_comment_length: file_comment.len() as u16,
                disk_number_start: 11,
                internal_file_attributes: 12,
                external_file_attributes: 13,
                relative_offset_of_local_header: 14,
            },
            file_name:    file_name.clone(),
            extra_field:  extra_field.clone(),
            file_comment: file_comment.clone(),
        };
        assert!(cd.is_valid_sizes());
        let _ = cd.get_partial();
    }

    #[test]
    fn partial_digital_signature() {
        let signature_data = vec![48, 49, 32, 50];
        let ds = DigitalSignature {
            fixed: DigitalSignatureFixed {
                header_signature: DigitalSignatureFixed::HEADER_SIGNATURE,
                size_of_data:     signature_data.len() as u16,
            },
            signature_data: signature_data.clone(),
        };
        assert!(ds.is_valid_sizes());
        let _ = ds.get_partial();
    }

    #[test]
    fn partial_zip64_end_of_central_directory() {
        let mut directory = Zip64EndOfCentralDirectory {
            fixed: Zip64EndOfCentralDirectoryFixed {
                zip64_end_of_central_dir_signature: 1,
                size_of_zip64_end_of_central_directory_record: Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES as u64
                    - 8,
                version_made_by: 3,
                version_needed_to_extract: 4,
                number_of_this_disk: 5,
                number_of_the_disk_with_the_start_of_the_central_directory: 6,
                total_number_of_entries_in_the_central_directory_on_this_disk: 7,
                total_number_of_entries_in_the_central_directory: 8,
                size_of_the_central_directory: 9,
                offset_of_start_of_central_directory_with_respect_to_the_starting_disk_number: 10,
            },
            zip64_extensible_data_sector: vec![48, 49, 50, 51],
        };

        assert!(directory.is_valid_sizes());
        let _ = directory.get_partial();
        directory.fixed.size_of_zip64_end_of_central_directory_record =
            Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES as u64 - 13;
        assert!(!directory.is_valid_sizes());
    }

    #[test]
    fn partial_end_of_central_directory() {
        let zip_file_comment = vec![48, 49, 32, 50];
        let cd = EndOfCentralDirectory {
            fixed: EndOfCentralDirectoryFixed {
                end_of_central_dir_signature: 1,
                number_of_this_disk: 2,
                number_of_the_disk_with_the_start_of_the_central_directory: 3,
                total_number_of_entries_in_the_central_directory_on_this_disk: 4,
                total_number_of_entries_in_the_central_directory: 5,
                size_of_the_central_directory: 6,
                offset_of_start_of_central_directory_with_respect_to_the_starting_disk_number: 7,
                zip_file_comment_length: zip_file_comment.len() as u16,
            },
            zip_file_comment: zip_file_comment.clone(),
        };
        assert!(cd.is_valid_sizes());
        let _ = cd.get_partial();
    }

    #[test]
    fn partial_extensible_data() {
        let data = vec![48, 49, 32];
        let ed = ExtensibleData {
            fixed: ExtensibleDataFixed {
                header_id: 0x0001,
                data_size: data.len() as u16,
            },
            data:  data.clone(),
        };
        assert!(ed.is_valid_sizes());
        let _ = ed.get_partial();
    }
}