diff options
Diffstat (limited to 'third_party/rust/zip/src/spec.rs')
-rw-r--r-- | third_party/rust/zip/src/spec.rs | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/third_party/rust/zip/src/spec.rs b/third_party/rust/zip/src/spec.rs new file mode 100644 index 0000000000..1d8cb0a643 --- /dev/null +++ b/third_party/rust/zip/src/spec.rs @@ -0,0 +1,219 @@ +use crate::result::{ZipError, ZipResult}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::io; +use std::io::prelude::*; + +pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50; +pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50; +const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50; +pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50; +const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50; + +pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64; +pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize; + +pub struct CentralDirectoryEnd { + pub disk_number: u16, + pub disk_with_central_directory: u16, + pub number_of_files_on_this_disk: u16, + pub number_of_files: u16, + pub central_directory_size: u32, + pub central_directory_offset: u32, + pub zip_file_comment: Vec<u8>, +} + +impl CentralDirectoryEnd { + // Per spec 4.4.1.4 - a CentralDirectoryEnd field might be insufficient to hold the + // required data. In this case the file SHOULD contain a ZIP64 format record + // and the field of this record will be set to -1 + pub(crate) fn record_too_small(&self) -> bool { + self.disk_number == 0xFFFF + || self.disk_with_central_directory == 0xFFFF + || self.number_of_files_on_this_disk == 0xFFFF + || self.number_of_files == 0xFFFF + || self.central_directory_size == 0xFFFFFFFF + || self.central_directory_offset == 0xFFFFFFFF + } + + pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> { + let magic = reader.read_u32::<LittleEndian>()?; + if magic != CENTRAL_DIRECTORY_END_SIGNATURE { + return Err(ZipError::InvalidArchive("Invalid digital signature header")); + } + let disk_number = reader.read_u16::<LittleEndian>()?; + let disk_with_central_directory = reader.read_u16::<LittleEndian>()?; + let number_of_files_on_this_disk = reader.read_u16::<LittleEndian>()?; + let number_of_files = reader.read_u16::<LittleEndian>()?; + let central_directory_size = reader.read_u32::<LittleEndian>()?; + let central_directory_offset = reader.read_u32::<LittleEndian>()?; + let zip_file_comment_length = reader.read_u16::<LittleEndian>()? as usize; + let mut zip_file_comment = vec![0; zip_file_comment_length]; + reader.read_exact(&mut zip_file_comment)?; + + Ok(CentralDirectoryEnd { + disk_number, + disk_with_central_directory, + number_of_files_on_this_disk, + number_of_files, + central_directory_size, + central_directory_offset, + zip_file_comment, + }) + } + + pub fn find_and_parse<T: Read + io::Seek>( + reader: &mut T, + ) -> ZipResult<(CentralDirectoryEnd, u64)> { + const HEADER_SIZE: u64 = 22; + const BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE: u64 = HEADER_SIZE - 6; + let file_length = reader.seek(io::SeekFrom::End(0))?; + + let search_upper_bound = file_length.saturating_sub(HEADER_SIZE + ::std::u16::MAX as u64); + + if file_length < HEADER_SIZE { + return Err(ZipError::InvalidArchive("Invalid zip header")); + } + + let mut pos = file_length - HEADER_SIZE; + while pos >= search_upper_bound { + reader.seek(io::SeekFrom::Start(pos))?; + if reader.read_u32::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE { + reader.seek(io::SeekFrom::Current( + BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64, + ))?; + let cde_start_pos = reader.seek(io::SeekFrom::Start(pos))?; + return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos)); + } + pos = match pos.checked_sub(1) { + Some(p) => p, + None => break, + }; + } + Err(ZipError::InvalidArchive( + "Could not find central directory end", + )) + } + + pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> { + writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_END_SIGNATURE)?; + writer.write_u16::<LittleEndian>(self.disk_number)?; + writer.write_u16::<LittleEndian>(self.disk_with_central_directory)?; + writer.write_u16::<LittleEndian>(self.number_of_files_on_this_disk)?; + writer.write_u16::<LittleEndian>(self.number_of_files)?; + writer.write_u32::<LittleEndian>(self.central_directory_size)?; + writer.write_u32::<LittleEndian>(self.central_directory_offset)?; + writer.write_u16::<LittleEndian>(self.zip_file_comment.len() as u16)?; + writer.write_all(&self.zip_file_comment)?; + Ok(()) + } +} + +pub struct Zip64CentralDirectoryEndLocator { + pub disk_with_central_directory: u32, + pub end_of_central_directory_offset: u64, + pub number_of_disks: u32, +} + +impl Zip64CentralDirectoryEndLocator { + pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> { + let magic = reader.read_u32::<LittleEndian>()?; + if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE { + return Err(ZipError::InvalidArchive( + "Invalid zip64 locator digital signature header", + )); + } + let disk_with_central_directory = reader.read_u32::<LittleEndian>()?; + let end_of_central_directory_offset = reader.read_u64::<LittleEndian>()?; + let number_of_disks = reader.read_u32::<LittleEndian>()?; + + Ok(Zip64CentralDirectoryEndLocator { + disk_with_central_directory, + end_of_central_directory_offset, + number_of_disks, + }) + } + + pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> { + writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE)?; + writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?; + writer.write_u64::<LittleEndian>(self.end_of_central_directory_offset)?; + writer.write_u32::<LittleEndian>(self.number_of_disks)?; + Ok(()) + } +} + +pub struct Zip64CentralDirectoryEnd { + pub version_made_by: u16, + pub version_needed_to_extract: u16, + pub disk_number: u32, + pub disk_with_central_directory: u32, + pub number_of_files_on_this_disk: u64, + pub number_of_files: u64, + pub central_directory_size: u64, + pub central_directory_offset: u64, + //pub extensible_data_sector: Vec<u8>, <-- We don't do anything with this at the moment. +} + +impl Zip64CentralDirectoryEnd { + pub fn find_and_parse<T: Read + io::Seek>( + reader: &mut T, + nominal_offset: u64, + search_upper_bound: u64, + ) -> ZipResult<(Zip64CentralDirectoryEnd, u64)> { + let mut pos = nominal_offset; + + while pos <= search_upper_bound { + reader.seek(io::SeekFrom::Start(pos))?; + + if reader.read_u32::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE { + let archive_offset = pos - nominal_offset; + + let _record_size = reader.read_u64::<LittleEndian>()?; + // We would use this value if we did anything with the "zip64 extensible data sector". + + let version_made_by = reader.read_u16::<LittleEndian>()?; + let version_needed_to_extract = reader.read_u16::<LittleEndian>()?; + let disk_number = reader.read_u32::<LittleEndian>()?; + let disk_with_central_directory = reader.read_u32::<LittleEndian>()?; + let number_of_files_on_this_disk = reader.read_u64::<LittleEndian>()?; + let number_of_files = reader.read_u64::<LittleEndian>()?; + let central_directory_size = reader.read_u64::<LittleEndian>()?; + let central_directory_offset = reader.read_u64::<LittleEndian>()?; + + return Ok(( + Zip64CentralDirectoryEnd { + version_made_by, + version_needed_to_extract, + disk_number, + disk_with_central_directory, + number_of_files_on_this_disk, + number_of_files, + central_directory_size, + central_directory_offset, + }, + archive_offset, + )); + } + + pos += 1; + } + + Err(ZipError::InvalidArchive( + "Could not find ZIP64 central directory end", + )) + } + + pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> { + writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE)?; + writer.write_u64::<LittleEndian>(44)?; // record size + writer.write_u16::<LittleEndian>(self.version_made_by)?; + writer.write_u16::<LittleEndian>(self.version_needed_to_extract)?; + writer.write_u32::<LittleEndian>(self.disk_number)?; + writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?; + writer.write_u64::<LittleEndian>(self.number_of_files_on_this_disk)?; + writer.write_u64::<LittleEndian>(self.number_of_files)?; + writer.write_u64::<LittleEndian>(self.central_directory_size)?; + writer.write_u64::<LittleEndian>(self.central_directory_offset)?; + Ok(()) + } +} |