1use crate::result::{ZipError, ZipResult};
2use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
3use std::io;
4use std::io::prelude::*;
5
6pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
7pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
8const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50;
9pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50;
10const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50;
11
12pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64;
13pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize;
14
15pub 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
25impl 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
111pub 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
117impl 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
145pub 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
157impl 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