1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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:
        if (self.fixed.size_of_zip64_end_of_central_directory_record as usize)
            < Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES - 12
        {
            return false;
        }
        let length = self.fixed.size_of_zip64_end_of_central_directory_record as usize + 12
            - Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES;
        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_zip64_end_of_central_directory() {
        let 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();
    }
}