1 | use crate::result::{ZipError, ZipResult}; |
2 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; |
3 | use std::io; |
4 | use std::io::prelude::*; |
5 | |
6 | pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50; |
7 | pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50; |
8 | const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50; |
9 | pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50; |
10 | const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50; |
11 | |
12 | pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64; |
13 | pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize; |
14 | |
15 | pub struct CentralDirectoryEnd { |
16 | pub disk_number: u16, |
17 | pub disk_with_central_directory: u16, |
18 | pub number_of_files_on_this_disk: u16, |
19 | pub number_of_files: u16, |
20 | pub central_directory_size: u32, |
21 | pub central_directory_offset: u32, |
22 | pub zip_file_comment: Vec<u8>, |
23 | } |
24 | |
25 | impl CentralDirectoryEnd { |
26 | // Per spec 4.4.1.4 - a CentralDirectoryEnd field might be insufficient to hold the |
27 | // required data. In this case the file SHOULD contain a ZIP64 format record |
28 | // and the field of this record will be set to -1 |
29 | pub(crate) fn record_too_small(&self) -> bool { |
30 | self.disk_number == 0xFFFF |
31 | || self.disk_with_central_directory == 0xFFFF |
32 | || self.number_of_files_on_this_disk == 0xFFFF |
33 | || self.number_of_files == 0xFFFF |
34 | || self.central_directory_size == 0xFFFFFFFF |
35 | || self.central_directory_offset == 0xFFFFFFFF |
36 | } |
37 | |
38 | pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> { |
39 | let magic = reader.read_u32::<LittleEndian>()?; |
40 | if magic != CENTRAL_DIRECTORY_END_SIGNATURE { |
41 | return Err(ZipError::InvalidArchive("Invalid digital signature header" )); |
42 | } |
43 | let disk_number = reader.read_u16::<LittleEndian>()?; |
44 | let disk_with_central_directory = reader.read_u16::<LittleEndian>()?; |
45 | let number_of_files_on_this_disk = reader.read_u16::<LittleEndian>()?; |
46 | let number_of_files = reader.read_u16::<LittleEndian>()?; |
47 | let central_directory_size = reader.read_u32::<LittleEndian>()?; |
48 | let central_directory_offset = reader.read_u32::<LittleEndian>()?; |
49 | let zip_file_comment_length = reader.read_u16::<LittleEndian>()? as usize; |
50 | let mut zip_file_comment = vec![0; zip_file_comment_length]; |
51 | reader.read_exact(&mut zip_file_comment)?; |
52 | |
53 | Ok(CentralDirectoryEnd { |
54 | disk_number, |
55 | disk_with_central_directory, |
56 | number_of_files_on_this_disk, |
57 | number_of_files, |
58 | central_directory_size, |
59 | central_directory_offset, |
60 | zip_file_comment, |
61 | }) |
62 | } |
63 | |
64 | pub fn find_and_parse<T: Read + io::Seek>( |
65 | reader: &mut T, |
66 | ) -> ZipResult<(CentralDirectoryEnd, u64)> { |
67 | const HEADER_SIZE: u64 = 22; |
68 | const BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE: u64 = HEADER_SIZE - 6; |
69 | let file_length = reader.seek(io::SeekFrom::End(0))?; |
70 | |
71 | let search_upper_bound = file_length.saturating_sub(HEADER_SIZE + ::std::u16::MAX as u64); |
72 | |
73 | if file_length < HEADER_SIZE { |
74 | return Err(ZipError::InvalidArchive("Invalid zip header" )); |
75 | } |
76 | |
77 | let mut pos = file_length - HEADER_SIZE; |
78 | while pos >= search_upper_bound { |
79 | reader.seek(io::SeekFrom::Start(pos))?; |
80 | if reader.read_u32::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE { |
81 | reader.seek(io::SeekFrom::Current( |
82 | BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64, |
83 | ))?; |
84 | let cde_start_pos = reader.seek(io::SeekFrom::Start(pos))?; |
85 | return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos)); |
86 | } |
87 | pos = match pos.checked_sub(1) { |
88 | Some(p) => p, |
89 | None => break, |
90 | }; |
91 | } |
92 | Err(ZipError::InvalidArchive( |
93 | "Could not find central directory end" , |
94 | )) |
95 | } |
96 | |
97 | pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> { |
98 | writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_END_SIGNATURE)?; |
99 | writer.write_u16::<LittleEndian>(self.disk_number)?; |
100 | writer.write_u16::<LittleEndian>(self.disk_with_central_directory)?; |
101 | writer.write_u16::<LittleEndian>(self.number_of_files_on_this_disk)?; |
102 | writer.write_u16::<LittleEndian>(self.number_of_files)?; |
103 | writer.write_u32::<LittleEndian>(self.central_directory_size)?; |
104 | writer.write_u32::<LittleEndian>(self.central_directory_offset)?; |
105 | writer.write_u16::<LittleEndian>(self.zip_file_comment.len() as u16)?; |
106 | writer.write_all(&self.zip_file_comment)?; |
107 | Ok(()) |
108 | } |
109 | } |
110 | |
111 | pub struct Zip64CentralDirectoryEndLocator { |
112 | pub disk_with_central_directory: u32, |
113 | pub end_of_central_directory_offset: u64, |
114 | pub number_of_disks: u32, |
115 | } |
116 | |
117 | impl Zip64CentralDirectoryEndLocator { |
118 | pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> { |
119 | let magic = reader.read_u32::<LittleEndian>()?; |
120 | if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE { |
121 | return Err(ZipError::InvalidArchive( |
122 | "Invalid zip64 locator digital signature header" , |
123 | )); |
124 | } |
125 | let disk_with_central_directory = reader.read_u32::<LittleEndian>()?; |
126 | let end_of_central_directory_offset = reader.read_u64::<LittleEndian>()?; |
127 | let number_of_disks = reader.read_u32::<LittleEndian>()?; |
128 | |
129 | Ok(Zip64CentralDirectoryEndLocator { |
130 | disk_with_central_directory, |
131 | end_of_central_directory_offset, |
132 | number_of_disks, |
133 | }) |
134 | } |
135 | |
136 | pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> { |
137 | writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE)?; |
138 | writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?; |
139 | writer.write_u64::<LittleEndian>(self.end_of_central_directory_offset)?; |
140 | writer.write_u32::<LittleEndian>(self.number_of_disks)?; |
141 | Ok(()) |
142 | } |
143 | } |
144 | |
145 | pub struct Zip64CentralDirectoryEnd { |
146 | pub version_made_by: u16, |
147 | pub version_needed_to_extract: u16, |
148 | pub disk_number: u32, |
149 | pub disk_with_central_directory: u32, |
150 | pub number_of_files_on_this_disk: u64, |
151 | pub number_of_files: u64, |
152 | pub central_directory_size: u64, |
153 | pub central_directory_offset: u64, |
154 | //pub extensible_data_sector: Vec<u8>, <-- We don't do anything with this at the moment. |
155 | } |
156 | |
157 | impl Zip64CentralDirectoryEnd { |
158 | pub fn find_and_parse<T: Read + io::Seek>( |
159 | reader: &mut T, |
160 | nominal_offset: u64, |
161 | search_upper_bound: u64, |
162 | ) -> ZipResult<(Zip64CentralDirectoryEnd, u64)> { |
163 | let mut pos = nominal_offset; |
164 | |
165 | while pos <= search_upper_bound { |
166 | reader.seek(io::SeekFrom::Start(pos))?; |
167 | |
168 | if reader.read_u32::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE { |
169 | let archive_offset = pos - nominal_offset; |
170 | |
171 | let _record_size = reader.read_u64::<LittleEndian>()?; |
172 | // We would use this value if we did anything with the "zip64 extensible data sector". |
173 | |
174 | let version_made_by = reader.read_u16::<LittleEndian>()?; |
175 | let version_needed_to_extract = reader.read_u16::<LittleEndian>()?; |
176 | let disk_number = reader.read_u32::<LittleEndian>()?; |
177 | let disk_with_central_directory = reader.read_u32::<LittleEndian>()?; |
178 | let number_of_files_on_this_disk = reader.read_u64::<LittleEndian>()?; |
179 | let number_of_files = reader.read_u64::<LittleEndian>()?; |
180 | let central_directory_size = reader.read_u64::<LittleEndian>()?; |
181 | let central_directory_offset = reader.read_u64::<LittleEndian>()?; |
182 | |
183 | return Ok(( |
184 | Zip64CentralDirectoryEnd { |
185 | version_made_by, |
186 | version_needed_to_extract, |
187 | disk_number, |
188 | disk_with_central_directory, |
189 | number_of_files_on_this_disk, |
190 | number_of_files, |
191 | central_directory_size, |
192 | central_directory_offset, |
193 | }, |
194 | archive_offset, |
195 | )); |
196 | } |
197 | |
198 | pos += 1; |
199 | } |
200 | |
201 | Err(ZipError::InvalidArchive( |
202 | "Could not find ZIP64 central directory end" , |
203 | )) |
204 | } |
205 | |
206 | pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> { |
207 | writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE)?; |
208 | writer.write_u64::<LittleEndian>(44)?; // record size |
209 | writer.write_u16::<LittleEndian>(self.version_made_by)?; |
210 | writer.write_u16::<LittleEndian>(self.version_needed_to_extract)?; |
211 | writer.write_u32::<LittleEndian>(self.disk_number)?; |
212 | writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?; |
213 | writer.write_u64::<LittleEndian>(self.number_of_files_on_this_disk)?; |
214 | writer.write_u64::<LittleEndian>(self.number_of_files)?; |
215 | writer.write_u64::<LittleEndian>(self.central_directory_size)?; |
216 | writer.write_u64::<LittleEndian>(self.central_directory_offset)?; |
217 | Ok(()) |
218 | } |
219 | } |
220 | |