From 993cbcdc5c36767cd369f9793761a5cb4a544740 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Tue, 22 Mar 2022 15:27:13 -0400 Subject: [PATCH 1/3] Add `zip::spec::{CentralDirectoryHeader, DataDescriptor, LocalFileHeader}`, and partially integrate them into `zip::{read, write}`. --- src/read.rs | 74 +++++------------ src/spec.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/write.rs | 132 ++++++++++++----------------- 3 files changed, 308 insertions(+), 129 deletions(-) diff --git a/src/read.rs b/src/read.rs index c619f24c..ae235c9a 100644 --- a/src/read.rs +++ b/src/read.rs @@ -640,70 +640,40 @@ pub(crate) fn central_header_to_zip_file( archive_offset: u64, ) -> ZipResult { let central_header_start = reader.seek(io::SeekFrom::Current(0))?; - // Parse central header - let signature = reader.read_u32::()?; - if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { - return Err(ZipError::InvalidArchive("Invalid Central Directory header")); - } + let central_header = spec::CentralDirectoryHeader::parse(reader)?; - let version_made_by = reader.read_u16::()?; - let _version_to_extract = reader.read_u16::()?; - let flags = reader.read_u16::()?; - let encrypted = flags & 1 == 1; - let is_utf8 = flags & (1 << 11) != 0; - let using_data_descriptor = flags & (1 << 3) != 0; - let compression_method = reader.read_u16::()?; - let last_mod_time = reader.read_u16::()?; - let last_mod_date = reader.read_u16::()?; - let crc32 = reader.read_u32::()?; - let compressed_size = reader.read_u32::()?; - let uncompressed_size = reader.read_u32::()?; - let file_name_length = reader.read_u16::()? as usize; - let extra_field_length = reader.read_u16::()? as usize; - let file_comment_length = reader.read_u16::()? as usize; - let _disk_number = reader.read_u16::()?; - let _internal_file_attributes = reader.read_u16::()?; - let external_file_attributes = reader.read_u32::()?; - let offset = reader.read_u32::()? as u64; - let mut file_name_raw = vec![0; file_name_length]; - reader.read_exact(&mut file_name_raw)?; - let mut extra_field = vec![0; extra_field_length]; - reader.read_exact(&mut extra_field)?; - let mut file_comment_raw = vec![0; file_comment_length]; - reader.read_exact(&mut file_comment_raw)?; - - let file_name = match is_utf8 { - true => String::from_utf8_lossy(&*file_name_raw).into_owned(), - false => file_name_raw.clone().from_cp437(), + let file_name = match central_header.flags.is_utf8() { + true => String::from_utf8_lossy(&*central_header.file_name_raw).into_owned(), + false => central_header.file_name_raw.clone().from_cp437(), }; - let file_comment = match is_utf8 { - true => String::from_utf8_lossy(&*file_comment_raw).into_owned(), - false => file_comment_raw.from_cp437(), + let file_comment = match central_header.flags.is_utf8() { + true => String::from_utf8_lossy(&*central_header.file_comment_raw).into_owned(), + false => central_header.file_comment_raw.clone().from_cp437(), }; // Construct the result let mut result = ZipFileData { - system: System::from_u8((version_made_by >> 8) as u8), - version_made_by: version_made_by as u8, - encrypted, - using_data_descriptor, + system: System::from_u8((central_header.version_made_by >> 8) as u8), + version_made_by: central_header.version_made_by as u8, + encrypted: central_header.flags.encrypted(), + using_data_descriptor: central_header.flags.using_data_descriptor(), compression_method: { #[allow(deprecated)] - CompressionMethod::from_u16(compression_method) + CompressionMethod::from_u16(central_header.compression_method) }, compression_level: None, - last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time), - crc32, - compressed_size: compressed_size as u64, - uncompressed_size: uncompressed_size as u64, - file_name, - file_name_raw, - extra_field, - file_comment, - header_start: offset, + last_modified_time: DateTime::from_msdos(central_header.last_mod_date, central_header.last_mod_time), + crc32: central_header.crc32, + compressed_size: central_header.compressed_size as u64, + uncompressed_size: central_header.uncompressed_size as u64, + file_name: file_name, + file_name_raw: central_header.file_name_raw, + extra_field: central_header.extra_field, + file_comment: file_comment, + header_start: central_header.offset as u64, central_header_start, data_start: AtomicU64::new(0), - external_attributes: external_file_attributes, + external_attributes: central_header.external_file_attributes, large_file: false, aes_mode: None, }; diff --git a/src/spec.rs b/src/spec.rs index 3ffcf732..96dc0738 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs, dead_code)] use crate::result::{ZipError, ZipResult}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io; @@ -8,10 +9,12 @@ 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 DATA_DESCRIPTOR_SIGNATURE: u32 = 0x08074b50; pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64; pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize; +#[derive(Clone, Debug)] pub struct CentralDirectoryEnd { pub disk_number: u16, pub disk_with_central_directory: u16, @@ -23,6 +26,10 @@ pub struct CentralDirectoryEnd { } impl CentralDirectoryEnd { + pub fn len(&self) -> usize { + 22 + self.zip_file_comment.len() + } + pub fn parse(reader: &mut T) -> ZipResult { let magic = reader.read_u32::()?; if magic != CENTRAL_DIRECTORY_END_SIGNATURE { @@ -205,3 +212,227 @@ impl Zip64CentralDirectoryEnd { Ok(()) } } + +#[derive(Copy, Clone, Debug)] +pub struct GeneralPurposeBitFlags(pub u16); + +impl GeneralPurposeBitFlags { + #[inline] + pub fn encrypted(&self) -> bool { + self.0 & 1 == 1 + } + + #[inline] + pub fn is_utf8(&self) -> bool { + self.0 & (1 << 11) != 0 + } + + #[inline] + pub fn using_data_descriptor(&self) -> bool { + self.0 & (1 << 3) != 0 + } + + #[inline] + pub fn set_using_data_descriptor(&mut self, b: bool) { + self.0 &= !(1 << 3); + if b { + self.0 |= 1 << 3; + } + } +} + +#[derive(Clone, Debug)] +pub struct CentralDirectoryHeader { + pub version_made_by: u16, + pub version_to_extract: u16, + pub flags: GeneralPurposeBitFlags, + pub compression_method: u16, + pub last_mod_time: u16, + pub last_mod_date: u16, + pub crc32: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, + pub disk_number: u16, + pub internal_file_attributes: u16, + pub external_file_attributes: u32, + pub offset: u32, + pub file_name_raw: Vec, + pub extra_field: Vec, + pub file_comment_raw: Vec, +} + +impl CentralDirectoryHeader { + pub fn len(&self) -> usize { + 46 + self.file_name_raw.len() + self.extra_field.len() + self.file_comment_raw.len() + } + pub fn parse(reader: &mut R) -> ZipResult { + let signature = reader.read_u32::()?; + if signature != CENTRAL_DIRECTORY_HEADER_SIGNATURE { + return Err(ZipError::InvalidArchive("Invalid Central Directory header")); + } + + let version_made_by = reader.read_u16::()?; + let version_to_extract = reader.read_u16::()?; + let flags = reader.read_u16::()?; + let compression_method = reader.read_u16::()?; + let last_mod_time = reader.read_u16::()?; + let last_mod_date = reader.read_u16::()?; + let crc32 = reader.read_u32::()?; + let compressed_size = reader.read_u32::()?; + let uncompressed_size = reader.read_u32::()?; + let file_name_length = reader.read_u16::()?; + let extra_field_length = reader.read_u16::()?; + let file_comment_length = reader.read_u16::()?; + let disk_number = reader.read_u16::()?; + let internal_file_attributes = reader.read_u16::()?; + let external_file_attributes = reader.read_u32::()?; + let offset = reader.read_u32::()?; + let mut file_name_raw = vec![0; file_name_length as usize]; + reader.read_exact(&mut file_name_raw)?; + let mut extra_field = vec![0; extra_field_length as usize]; + reader.read_exact(&mut extra_field)?; + let mut file_comment_raw = vec![0; file_comment_length as usize]; + reader.read_exact(&mut file_comment_raw)?; + + Ok(CentralDirectoryHeader { + version_made_by, + version_to_extract, + flags: GeneralPurposeBitFlags(flags), + compression_method, + last_mod_time, + last_mod_date, + crc32, + compressed_size, + uncompressed_size, + disk_number, + internal_file_attributes, + external_file_attributes, + offset, + file_name_raw, + extra_field, + file_comment_raw, + }) + } + + pub fn write(&self, writer: &mut T) -> ZipResult<()> { + writer.write_u32::(CENTRAL_DIRECTORY_HEADER_SIGNATURE)?; + writer.write_u16::(self.version_made_by)?; + writer.write_u16::(self.version_to_extract)?; + writer.write_u16::(self.flags.0)?; + writer.write_u16::(self.compression_method)?; + writer.write_u16::(self.last_mod_time)?; + writer.write_u16::(self.last_mod_date)?; + writer.write_u32::(self.crc32)?; + writer.write_u32::(self.compressed_size)?; + writer.write_u32::(self.uncompressed_size)?; + writer.write_u16::(self.file_name_raw.len() as u16)?; + writer.write_u16::(self.extra_field.len() as u16)?; + writer.write_u16::(self.file_comment_raw.len() as u16)?; + writer.write_u16::(self.disk_number)?; + writer.write_u16::(self.internal_file_attributes)?; + writer.write_u32::(self.external_file_attributes)?; + writer.write_u32::(self.offset)?; + writer.write_all(&self.file_name_raw)?; + writer.write_all(&self.extra_field)?; + writer.write_all(&self.file_comment_raw)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct LocalFileHeader { + pub version_to_extract: u16, + pub flags: GeneralPurposeBitFlags, + pub compression_method: u16, + pub last_mod_time: u16, + pub last_mod_date: u16, + pub crc32: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, + pub file_name_raw: Vec, + pub extra_field: Vec, +} + +impl LocalFileHeader { + pub fn len(&self) -> usize { + 30 + self.file_name_raw.len() + self.extra_field.len() + } + + pub fn parse(reader: &mut R) -> ZipResult { + let signature = reader.read_u32::()?; + if signature != LOCAL_FILE_HEADER_SIGNATURE { + return Err(ZipError::InvalidArchive("Invalid local file header")); + } + + let version_to_extract = reader.read_u16::()?; + let flags = reader.read_u16::()?; + let compression_method = reader.read_u16::()?; + let last_mod_time = reader.read_u16::()?; + let last_mod_date = reader.read_u16::()?; + let crc32 = reader.read_u32::()?; + let compressed_size = reader.read_u32::()?; + let uncompressed_size = reader.read_u32::()?; + let file_name_length = reader.read_u16::()?; + let extra_field_length = reader.read_u16::()?; + + let mut file_name_raw = vec![0; file_name_length as usize]; + reader.read_exact(&mut file_name_raw)?; + let mut extra_field = vec![0; extra_field_length as usize]; + reader.read_exact(&mut extra_field)?; + + Ok(LocalFileHeader { + version_to_extract, + flags: GeneralPurposeBitFlags(flags), + compression_method, + last_mod_time, + last_mod_date, + crc32, + compressed_size, + uncompressed_size, + file_name_raw, + extra_field, + }) + } + + pub fn write(&self, writer: &mut T) -> ZipResult<()> { + writer.write_u32::(LOCAL_FILE_HEADER_SIGNATURE)?; + writer.write_u16::(self.version_to_extract)?; + writer.write_u16::(self.flags.0)?; + writer.write_u16::(self.compression_method)?; + writer.write_u16::(self.last_mod_time)?; + writer.write_u16::(self.last_mod_date)?; + writer.write_u32::(self.crc32)?; + writer.write_u32::(self.compressed_size)?; + writer.write_u32::(self.uncompressed_size)?; + writer.write_u16::(self.file_name_raw.len() as u16)?; + writer.write_u16::(self.extra_field.len() as u16)?; + writer.write_all(&self.file_name_raw)?; + writer.write_all(&self.extra_field)?; + Ok(()) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct DataDescriptor { + pub crc32: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, +} + +impl DataDescriptor { + pub fn read(reader: &mut T) -> ZipResult { + let first_word = reader.read_u32::()?; + let crc32 = if first_word == DATA_DESCRIPTOR_SIGNATURE { + reader.read_u32::()? + } else { + first_word + }; + let compressed_size = reader.read_u32::()?; + let uncompressed_size = reader.read_u32::()?; + Ok(DataDescriptor { + crc32, + compressed_size, + uncompressed_size, + }) + } +} diff --git a/src/write.rs b/src/write.rs index 3d0e0170..214807a5 100644 --- a/src/write.rs +++ b/src/write.rs @@ -998,44 +998,38 @@ fn clamp_opt(value: T, range: RangeInclusive) -> Option { } fn write_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { - // local file header signature - writer.write_u32::(spec::LOCAL_FILE_HEADER_SIGNATURE)?; - // version needed to extract - writer.write_u16::(file.version_needed())?; - // general purpose bit flag - let flag = if !file.file_name.is_ascii() { + let flags = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; - writer.write_u16::(flag)?; - // Compression method #[allow(deprecated)] - writer.write_u16::(file.compression_method.to_u16())?; - // last mod file time and last mod file date - writer.write_u16::(file.last_modified_time.timepart())?; - writer.write_u16::(file.last_modified_time.datepart())?; - // crc-32 - writer.write_u32::(file.crc32)?; - // compressed size and uncompressed size - if file.large_file { - writer.write_u32::(spec::ZIP64_BYTES_THR as u32)?; - writer.write_u32::(spec::ZIP64_BYTES_THR as u32)?; - } else { - writer.write_u32::(file.compressed_size as u32)?; - writer.write_u32::(file.uncompressed_size as u32)?; - } - // file name length - writer.write_u16::(file.file_name.as_bytes().len() as u16)?; - // extra field length - let extra_field_length = if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16; - writer.write_u16::(extra_field_length)?; - // file name - writer.write_all(file.file_name.as_bytes())?; - // zip64 extra field - if file.large_file { - write_local_zip64_extra_field(writer, file)?; - } + let compression_method = file.compression_method.to_u16(); + let compressed_size = file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32; + let uncompressed_size = file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32; + + let mut extra_field = if file.large_file { + let mut zip64_extra_field = vec![0; 20]; + write_local_zip64_extra_field(&mut zip64_extra_field, file)?; + zip64_extra_field + } else { + Vec::new() + }; + extra_field.extend_from_slice(&file.extra_field[..]); + + let local_file_header = spec::LocalFileHeader { + version_to_extract: file.version_needed(), + flags: spec::GeneralPurposeBitFlags(flags), + compression_method, + last_mod_time: file.last_modified_time.timepart(), + last_mod_date: file.last_modified_time.datepart(), + crc32: file.crc32, + compressed_size, + uncompressed_size, + file_name_raw: file.file_name.as_bytes().to_vec(), + extra_field, + }; + local_file_header.write(writer)?; Ok(()) } @@ -1070,56 +1064,40 @@ fn write_central_directory_header(writer: &mut T, file: &ZipFileData) let zip64_extra_field_length = write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?; - // central file header signature - writer.write_u32::(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?; - // version made by - let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16); - writer.write_u16::(version_made_by)?; - // version needed to extract - writer.write_u16::(file.version_needed())?; - // general puprose bit flag - let flag = if !file.file_name.is_ascii() { + let flags = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; - writer.write_u16::(flag)?; - // compression method #[allow(deprecated)] - writer.write_u16::(file.compression_method.to_u16())?; - // last mod file time + date - writer.write_u16::(file.last_modified_time.timepart())?; - writer.write_u16::(file.last_modified_time.datepart())?; - // crc-32 - writer.write_u32::(file.crc32)?; - // compressed size - writer.write_u32::(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?; - // uncompressed size - writer.write_u32::(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?; - // file name length - writer.write_u16::(file.file_name.as_bytes().len() as u16)?; - // extra field length - writer.write_u16::(zip64_extra_field_length + file.extra_field.len() as u16)?; - // file comment length - writer.write_u16::(0)?; - // disk number start - writer.write_u16::(0)?; - // internal file attribytes - writer.write_u16::(0)?; - // external file attributes - writer.write_u32::(file.external_attributes)?; - // relative offset of local header - writer.write_u32::(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?; - // file name - writer.write_all(file.file_name.as_bytes())?; - // zip64 extra field - writer.write_all(&zip64_extra_field[..zip64_extra_field_length as usize])?; - // extra field - writer.write_all(&file.extra_field)?; - // file comment - // + let compression_method = file.compression_method.to_u16(); + let compressed_size = file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32; + let uncompressed_size = file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32; + let offset = file.header_start.min(spec::ZIP64_BYTES_THR) as u32; - Ok(()) + let mut extra_field = zip64_extra_field[..zip64_extra_field_length as usize].to_vec(); + extra_field.extend_from_slice(&file.extra_field[..]); + + let header = spec::CentralDirectoryHeader { + version_made_by: (file.system as u16) << 8 | (file.version_made_by as u16), + version_to_extract: file.version_needed(), + flags: spec::GeneralPurposeBitFlags(flags), + compression_method, + last_mod_time: file.last_modified_time.timepart(), + last_mod_date: file.last_modified_time.datepart(), + crc32: file.crc32, + compressed_size, + uncompressed_size, + disk_number: 0, + internal_file_attributes: 0, + external_file_attributes: file.external_attributes, + offset, + file_name_raw: file.file_name.as_bytes().to_vec(), + extra_field, + file_comment_raw: Vec::new(), + }; + + header.write(writer) } fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> { From 45a98c01b99c8161bf0096dc1b4c6fb7ee250f36 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 25 Mar 2022 12:32:41 -0400 Subject: [PATCH 2/3] Cargo fmt and cargo clippy fixes. --- src/read.rs | 11 ++++--- src/write.rs | 86 ++++++++++++++++++++++++++-------------------------- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/read.rs b/src/read.rs index ae235c9a..d76a780f 100644 --- a/src/read.rs +++ b/src/read.rs @@ -640,7 +640,7 @@ pub(crate) fn central_header_to_zip_file( archive_offset: u64, ) -> ZipResult { let central_header_start = reader.seek(io::SeekFrom::Current(0))?; - let central_header = spec::CentralDirectoryHeader::parse(reader)?; + let central_header = spec::CentralDirectoryHeader::parse(reader)?; let file_name = match central_header.flags.is_utf8() { true => String::from_utf8_lossy(&*central_header.file_name_raw).into_owned(), @@ -662,14 +662,17 @@ pub(crate) fn central_header_to_zip_file( CompressionMethod::from_u16(central_header.compression_method) }, compression_level: None, - last_modified_time: DateTime::from_msdos(central_header.last_mod_date, central_header.last_mod_time), + last_modified_time: DateTime::from_msdos( + central_header.last_mod_date, + central_header.last_mod_time, + ), crc32: central_header.crc32, compressed_size: central_header.compressed_size as u64, uncompressed_size: central_header.uncompressed_size as u64, - file_name: file_name, + file_name, file_name_raw: central_header.file_name_raw, extra_field: central_header.extra_field, - file_comment: file_comment, + file_comment, header_start: central_header.offset as u64, central_header_start, data_start: AtomicU64::new(0), diff --git a/src/write.rs b/src/write.rs index 214807a5..2e398b91 100644 --- a/src/write.rs +++ b/src/write.rs @@ -1008,28 +1008,28 @@ fn write_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipR let compressed_size = file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32; let uncompressed_size = file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32; - let mut extra_field = if file.large_file { - let mut zip64_extra_field = vec![0; 20]; - write_local_zip64_extra_field(&mut zip64_extra_field, file)?; - zip64_extra_field - } else { - Vec::new() - }; - extra_field.extend_from_slice(&file.extra_field[..]); + let mut extra_field = if file.large_file { + let mut zip64_extra_field = vec![0; 20]; + write_local_zip64_extra_field(&mut zip64_extra_field, file)?; + zip64_extra_field + } else { + Vec::new() + }; + extra_field.extend_from_slice(&file.extra_field[..]); - let local_file_header = spec::LocalFileHeader { - version_to_extract: file.version_needed(), - flags: spec::GeneralPurposeBitFlags(flags), - compression_method, - last_mod_time: file.last_modified_time.timepart(), - last_mod_date: file.last_modified_time.datepart(), - crc32: file.crc32, - compressed_size, - uncompressed_size, - file_name_raw: file.file_name.as_bytes().to_vec(), - extra_field, - }; - local_file_header.write(writer)?; + let local_file_header = spec::LocalFileHeader { + version_to_extract: file.version_needed(), + flags: spec::GeneralPurposeBitFlags(flags), + compression_method, + last_mod_time: file.last_modified_time.timepart(), + last_mod_date: file.last_modified_time.datepart(), + crc32: file.crc32, + compressed_size, + uncompressed_size, + file_name_raw: file.file_name.as_bytes().to_vec(), + extra_field, + }; + local_file_header.write(writer)?; Ok(()) } @@ -1075,29 +1075,29 @@ fn write_central_directory_header(writer: &mut T, file: &ZipFileData) let uncompressed_size = file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32; let offset = file.header_start.min(spec::ZIP64_BYTES_THR) as u32; - let mut extra_field = zip64_extra_field[..zip64_extra_field_length as usize].to_vec(); - extra_field.extend_from_slice(&file.extra_field[..]); - - let header = spec::CentralDirectoryHeader { - version_made_by: (file.system as u16) << 8 | (file.version_made_by as u16), - version_to_extract: file.version_needed(), - flags: spec::GeneralPurposeBitFlags(flags), - compression_method, - last_mod_time: file.last_modified_time.timepart(), - last_mod_date: file.last_modified_time.datepart(), - crc32: file.crc32, - compressed_size, - uncompressed_size, - disk_number: 0, - internal_file_attributes: 0, - external_file_attributes: file.external_attributes, - offset, - file_name_raw: file.file_name.as_bytes().to_vec(), - extra_field, - file_comment_raw: Vec::new(), - }; + let mut extra_field = zip64_extra_field[..zip64_extra_field_length as usize].to_vec(); + extra_field.extend_from_slice(&file.extra_field[..]); - header.write(writer) + let header = spec::CentralDirectoryHeader { + version_made_by: (file.system as u16) << 8 | (file.version_made_by as u16), + version_to_extract: file.version_needed(), + flags: spec::GeneralPurposeBitFlags(flags), + compression_method, + last_mod_time: file.last_modified_time.timepart(), + last_mod_date: file.last_modified_time.datepart(), + crc32: file.crc32, + compressed_size, + uncompressed_size, + disk_number: 0, + internal_file_attributes: 0, + external_file_attributes: file.external_attributes, + offset, + file_name_raw: file.file_name.as_bytes().to_vec(), + extra_field, + file_comment_raw: Vec::new(), + }; + + header.write(writer) } fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> { From fc1007a5a4de129f5027e62a3948e4eeefdffb32 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 25 Mar 2022 14:34:01 -0400 Subject: [PATCH 3/3] Add round-trip serialization tests for `zip::spec` types. --- src/spec.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/src/spec.rs b/src/spec.rs index 96dc0738..1f826b93 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -14,7 +14,7 @@ pub const DATA_DESCRIPTOR_SIGNATURE: u32 = 0x08074b50; pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64; pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct CentralDirectoryEnd { pub disk_number: u16, pub disk_with_central_directory: u16, @@ -213,7 +213,7 @@ impl Zip64CentralDirectoryEnd { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct GeneralPurposeBitFlags(pub u16); impl GeneralPurposeBitFlags { @@ -241,7 +241,7 @@ impl GeneralPurposeBitFlags { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct CentralDirectoryHeader { pub version_made_by: u16, pub version_to_extract: u16, @@ -339,7 +339,7 @@ impl CentralDirectoryHeader { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct LocalFileHeader { pub version_to_extract: u16, pub flags: GeneralPurposeBitFlags, @@ -412,7 +412,7 @@ impl LocalFileHeader { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct DataDescriptor { pub crc32: u32, pub compressed_size: u32, @@ -435,4 +435,92 @@ impl DataDescriptor { uncompressed_size, }) } + + pub fn write(&self, writer: &mut T) -> ZipResult<()> { + writer.write_u32::(DATA_DESCRIPTOR_SIGNATURE)?; + writer.write_u32::(self.crc32)?; + writer.write_u32::(self.compressed_size)?; + writer.write_u32::(self.uncompressed_size)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::{ + CentralDirectoryHeader, DataDescriptor, GeneralPurposeBitFlags, LocalFileHeader, ZipResult, + }; + use std::io::Cursor; + #[test] + fn test_cdh_roundtrip() -> ZipResult<()> { + let cdh1 = CentralDirectoryHeader { + version_made_by: 1, + version_to_extract: 2, + flags: GeneralPurposeBitFlags(3), + compression_method: 4, + last_mod_time: 5, + last_mod_date: 6, + crc32: 7, + compressed_size: 8, + uncompressed_size: 9, + disk_number: 10, + internal_file_attributes: 11, + external_file_attributes: 12, + offset: 13, + file_name_raw: b"a".to_vec(), + extra_field: b"bb".to_vec(), + file_comment_raw: b"ccc".to_vec(), + }; + let mut bytes = Vec::new(); + { + let mut cursor = Cursor::new(&mut bytes); + cdh1.write(&mut cursor)?; + } + let cdh2 = CentralDirectoryHeader::parse(&mut &bytes[..])?; + assert_eq!(cdh1, cdh2); + Ok(()) + } + + #[test] + fn test_lfh_roundtrip() -> ZipResult<()> { + let lfh1 = LocalFileHeader { + version_to_extract: 1, + flags: GeneralPurposeBitFlags(2), + compression_method: 3, + last_mod_time: 4, + last_mod_date: 5, + crc32: 6, + compressed_size: 7, + uncompressed_size: 8, + file_name_raw: b"a".to_vec(), + extra_field: b"bb".to_vec(), + }; + let mut bytes = Vec::new(); + { + let mut cursor = Cursor::new(&mut bytes); + lfh1.write(&mut cursor)?; + } + let lfh2 = LocalFileHeader::parse(&mut &bytes[..])?; + assert_eq!(lfh1, lfh2); + Ok(()) + } + + #[test] + fn test_dd_roundtrip() -> ZipResult<()> { + let dd1 = DataDescriptor { + crc32: 1, + compressed_size: 2, + uncompressed_size: 3, + }; + let mut bytes = Vec::new(); + { + let mut cursor = Cursor::new(&mut bytes); + dd1.write(&mut cursor)?; + } + let dd2 = DataDescriptor::read(&mut &bytes[..])?; + assert_eq!(dd1, dd2); + let dd3 = DataDescriptor::read(&mut &bytes[4..])?; + assert_eq!(dd1, dd3); + Ok(()) + } }