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 record_too_small(&self) -> bool29 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 parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd>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 find_and_parse<T: Read + io::Seek>( reader: &mut T, ) -> ZipResult<(CentralDirectoryEnd, u64)>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 write<T: Write>(&self, writer: &mut T) -> ZipResult<()>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 { parse<T: Read>(reader: &mut T) -> ZipResult<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 write<T: Write>(&self, writer: &mut T) -> ZipResult<()>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 { find_and_parse<T: Read + io::Seek>( reader: &mut T, nominal_offset: u64, search_upper_bound: u64, ) -> ZipResult<(Zip64CentralDirectoryEnd, u64)>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 write<T: Write>(&self, writer: &mut T) -> ZipResult<()>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