zip_core/
partial.rs

1use crate::raw::{
2    ArchiveExtraDataRecord, ArchiveExtraDataRecordFixed, CentralDirectoryHeader, CentralDirectoryHeaderFixed,
3    DigitalSignature, DigitalSignatureFixed, EndOfCentralDirectory, EndOfCentralDirectoryFixed, ExtensibleData,
4    ExtensibleDataFixed, LocalFileHeader, LocalFileHeaderFixed, Zip64EndOfCentralDirectory,
5    Zip64EndOfCentralDirectoryFixed,
6};
7
8/// implemented for all dynamic records that contain a fixed element
9pub trait PartialRecord {
10    type Partial;
11
12    fn get_partial(&self) -> &Self::Partial;
13    /// all dynamic records contain dynamic data and length information in the
14    /// fixed part. Verify if those 2 values are matching
15    fn is_valid_sizes(&self) -> bool;
16}
17
18impl PartialRecord for LocalFileHeader {
19    type Partial = LocalFileHeaderFixed;
20
21    fn get_partial(&self) -> &Self::Partial { &self.fixed }
22
23    fn is_valid_sizes(&self) -> bool {
24        self.file_name.len() == self.fixed.file_name_length as usize
25            && self.extra_field.len() == self.fixed.extra_field_length as usize
26    }
27}
28
29impl PartialRecord for ArchiveExtraDataRecord {
30    type Partial = ArchiveExtraDataRecordFixed;
31
32    fn get_partial(&self) -> &Self::Partial { &self.fixed }
33
34    fn is_valid_sizes(&self) -> bool { self.extra_field_data.len() == self.fixed.extra_field_length as usize }
35}
36
37impl PartialRecord for CentralDirectoryHeader {
38    type Partial = CentralDirectoryHeaderFixed;
39
40    fn get_partial(&self) -> &Self::Partial { &self.fixed }
41
42    fn is_valid_sizes(&self) -> bool {
43        self.file_name.len() == self.fixed.file_name_length as usize
44            && self.extra_field.len() == self.fixed.extra_field_length as usize
45            && self.file_comment.len() == self.fixed.file_comment_length as usize
46    }
47}
48
49impl PartialRecord for DigitalSignature {
50    type Partial = DigitalSignatureFixed;
51
52    fn get_partial(&self) -> &Self::Partial { &self.fixed }
53
54    fn is_valid_sizes(&self) -> bool { self.signature_data.len() == self.fixed.size_of_data as usize }
55}
56
57impl PartialRecord for Zip64EndOfCentralDirectory {
58    type Partial = Zip64EndOfCentralDirectoryFixed;
59
60    fn get_partial(&self) -> &Self::Partial { &self.fixed }
61
62    fn is_valid_sizes(&self) -> bool {
63        // as stated elsewhere, this is not just a length, but some part of a
64        // calculation overflow protection check:
65        const MIN_SIZE: usize = Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES - 12;
66        if (self.fixed.size_of_zip64_end_of_central_directory_record as usize) < MIN_SIZE {
67            return false;
68        }
69        let length = self.fixed.size_of_zip64_end_of_central_directory_record as usize - MIN_SIZE;
70        self.zip64_extensible_data_sector.len() == length
71    }
72}
73
74impl PartialRecord for EndOfCentralDirectory {
75    type Partial = EndOfCentralDirectoryFixed;
76
77    fn get_partial(&self) -> &Self::Partial { &self.fixed }
78
79    fn is_valid_sizes(&self) -> bool { self.zip_file_comment.len() == self.fixed.zip_file_comment_length as usize }
80}
81
82impl PartialRecord for ExtensibleData {
83    type Partial = ExtensibleDataFixed;
84
85    fn get_partial(&self) -> &Self::Partial { &self.fixed }
86
87    fn is_valid_sizes(&self) -> bool { self.data.len() == self.fixed.data_size as usize }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn partial_local_file_header() {
96        let file_name = vec![48, 49, 32, 50];
97        let extra_field = vec![61, 62, 32, 63, 64];
98        let lf = LocalFileHeader {
99            fixed:       LocalFileHeaderFixed {
100                local_file_header_signature: LocalFileHeaderFixed::LOCAL_FILE_HEADER_SIGNATURE,
101                version_needed_to_extract: 1,
102                general_purpose_bit_flag: 2,
103                compression_method: 3,
104                last_mod_file_time: 4,
105                last_mod_file_date: 5,
106                crc_32: 6,
107                compressed_size: 7,
108                uncompressed_size: 8,
109                file_name_length: file_name.len() as u16,
110                extra_field_length: extra_field.len() as u16,
111            },
112            file_name:   file_name.clone(),
113            extra_field: extra_field.clone(),
114        };
115        assert!(lf.is_valid_sizes());
116        let _ = lf.get_partial();
117    }
118
119    #[test]
120    fn partial_archive_extra_data_record() {
121        let extra_field_data = vec![48, 49, 32, 50];
122        let ad = ArchiveExtraDataRecord {
123            fixed: ArchiveExtraDataRecordFixed {
124                archive_extra_data_signature: 1,
125                extra_field_length: extra_field_data.len() as u64,
126            },
127            extra_field_data: extra_field_data.clone(),
128        };
129        assert!(ad.is_valid_sizes());
130        let _ = ad.get_partial();
131    }
132
133    #[test]
134    fn partial_central_directory_header() {
135        let file_name = vec![48, 49, 32, 50];
136        let extra_field = vec![61, 62, 32, 63, 64];
137        let file_comment = vec![51, 52, 53, 32, 54, 55];
138        let cd = CentralDirectoryHeader {
139            fixed:        CentralDirectoryHeaderFixed {
140                central_file_header_signature: 1,
141                version_made_by: 2,
142                version_needed_to_extract: 3,
143                general_purpose_bit_flag: 4,
144                compression_method: 5,
145                last_mod_file_time: 6,
146                last_mod_file_date: 7,
147                crc_32: 8,
148                compressed_size: 9,
149                uncompressed_size: 10,
150                file_name_length: file_name.len() as u16,
151                extra_field_length: extra_field.len() as u16,
152                file_comment_length: file_comment.len() as u16,
153                disk_number_start: 11,
154                internal_file_attributes: 12,
155                external_file_attributes: 13,
156                relative_offset_of_local_header: 14,
157            },
158            file_name:    file_name.clone(),
159            extra_field:  extra_field.clone(),
160            file_comment: file_comment.clone(),
161        };
162        assert!(cd.is_valid_sizes());
163        let _ = cd.get_partial();
164    }
165
166    #[test]
167    fn partial_digital_signature() {
168        let signature_data = vec![48, 49, 32, 50];
169        let ds = DigitalSignature {
170            fixed: DigitalSignatureFixed {
171                header_signature: DigitalSignatureFixed::HEADER_SIGNATURE,
172                size_of_data:     signature_data.len() as u16,
173            },
174            signature_data: signature_data.clone(),
175        };
176        assert!(ds.is_valid_sizes());
177        let _ = ds.get_partial();
178    }
179
180    #[test]
181    fn partial_zip64_end_of_central_directory() {
182        let mut directory = Zip64EndOfCentralDirectory {
183            fixed: Zip64EndOfCentralDirectoryFixed {
184                zip64_end_of_central_dir_signature: 1,
185                size_of_zip64_end_of_central_directory_record: Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES as u64
186                    - 8,
187                version_made_by: 3,
188                version_needed_to_extract: 4,
189                number_of_this_disk: 5,
190                number_of_the_disk_with_the_start_of_the_central_directory: 6,
191                total_number_of_entries_in_the_central_directory_on_this_disk: 7,
192                total_number_of_entries_in_the_central_directory: 8,
193                size_of_the_central_directory: 9,
194                offset_of_start_of_central_directory_with_respect_to_the_starting_disk_number: 10,
195            },
196            zip64_extensible_data_sector: vec![48, 49, 50, 51],
197        };
198
199        assert!(directory.is_valid_sizes());
200        let _ = directory.get_partial();
201        directory.fixed.size_of_zip64_end_of_central_directory_record =
202            Zip64EndOfCentralDirectoryFixed::SIZE_IN_BYTES as u64 - 13;
203        assert!(!directory.is_valid_sizes());
204    }
205
206    #[test]
207    fn partial_end_of_central_directory() {
208        let zip_file_comment = vec![48, 49, 32, 50];
209        let cd = EndOfCentralDirectory {
210            fixed: EndOfCentralDirectoryFixed {
211                end_of_central_dir_signature: 1,
212                number_of_this_disk: 2,
213                number_of_the_disk_with_the_start_of_the_central_directory: 3,
214                total_number_of_entries_in_the_central_directory_on_this_disk: 4,
215                total_number_of_entries_in_the_central_directory: 5,
216                size_of_the_central_directory: 6,
217                offset_of_start_of_central_directory_with_respect_to_the_starting_disk_number: 7,
218                zip_file_comment_length: zip_file_comment.len() as u16,
219            },
220            zip_file_comment: zip_file_comment.clone(),
221        };
222        assert!(cd.is_valid_sizes());
223        let _ = cd.get_partial();
224    }
225
226    #[test]
227    fn partial_extensible_data() {
228        let data = vec![48, 49, 32];
229        let ed = ExtensibleData {
230            fixed: ExtensibleDataFixed {
231                header_id: 0x0001,
232                data_size: data.len() as u16,
233            },
234            data:  data.clone(),
235        };
236        assert!(ed.is_valid_sizes());
237        let _ = ed.get_partial();
238    }
239}