diff --git a/src/extra_fields/extended_timestamp.rs b/src/extra_fields/extended_timestamp.rs index 0521fd5f2..283ab0469 100644 --- a/src/extra_fields/extended_timestamp.rs +++ b/src/extra_fields/extended_timestamp.rs @@ -222,21 +222,21 @@ mod tests { fn check_extended_timestamp_value() { let mut cursor = Cursor::new(&[0b0000_0001_u8, 0x00, 0x00, 0x00, 0x01]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 5).unwrap(); - assert_eq!(result.mod_time(), Some(16777216)); + assert_eq!(result.mod_time(), Some(1 << 24)); assert_eq!(result.ac_time(), None); assert_eq!(result.cr_time(), None); let mut cursor = Cursor::new(&[0b0000_0010_u8, 0x00, 0x00, 0x00, 0x02]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 5).unwrap(); assert_eq!(result.mod_time(), None); - assert_eq!(result.ac_time(), Some(33554432)); + assert_eq!(result.ac_time(), Some(2 << 24)); assert_eq!(result.cr_time(), None); let mut cursor = Cursor::new(&[0b0000_0100_u8, 0x00, 0x00, 0x00, 0x03]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 5).unwrap(); assert_eq!(result.mod_time(), None); assert_eq!(result.ac_time(), None); - assert_eq!(result.cr_time(), Some(50331648)); + assert_eq!(result.cr_time(), Some(3 << 24)); let mut cursor = Cursor::new(&[ 0b0000_0011_u8, @@ -250,8 +250,8 @@ mod tests { 0x02, ]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 9).unwrap(); - assert_eq!(result.mod_time(), Some(16777216)); - assert_eq!(result.ac_time(), Some(33554432)); + assert_eq!(result.mod_time(), Some(1 << 24)); + assert_eq!(result.ac_time(), Some(2 << 24)); assert_eq!(result.cr_time(), None); let mut cursor = Cursor::new(&[ @@ -270,9 +270,9 @@ mod tests { 0x03, ]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 13).unwrap(); - assert_eq!(result.mod_time(), Some(16777216)); - assert_eq!(result.ac_time(), Some(33554432)); - assert_eq!(result.cr_time(), Some(50331648)); + assert_eq!(result.mod_time(), Some(1 << 24)); + assert_eq!(result.ac_time(), Some(2 << 24)); + assert_eq!(result.cr_time(), Some(3 << 24)); } #[test] @@ -280,7 +280,7 @@ mod tests { // in the central header let mut cursor = Cursor::new(&[0b0000_0111_u8, 0x00, 0x00, 0x00, 0x01]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 5).unwrap(); - assert_eq!(result.mod_time(), Some(16777216)); + assert_eq!(result.mod_time(), Some(1 << 24)); assert_eq!(result.ac_time(), None); assert_eq!(result.cr_time(), None); @@ -301,8 +301,8 @@ mod tests { 0x03, ]); let result = ExtendedTimestamp::try_from_reader(&mut cursor, 13).unwrap(); - assert_eq!(result.mod_time(), Some(16777216)); - assert_eq!(result.ac_time(), Some(33554432)); - assert_eq!(result.cr_time(), Some(50331648)); + assert_eq!(result.mod_time(), Some(1 << 24)); + assert_eq!(result.ac_time(), Some(2 << 24)); + assert_eq!(result.cr_time(), Some(3 << 24)); } } diff --git a/src/extra_fields/zipinfo_utf8.rs b/src/extra_fields/zipinfo_utf8.rs index 493621ec6..f1a1bcf8e 100644 --- a/src/extra_fields/zipinfo_utf8.rs +++ b/src/extra_fields/zipinfo_utf8.rs @@ -51,9 +51,7 @@ mod tests { use crate::extra_fields::UnicodeExtraField; #[test] fn unicode_extra_field_crc32_correct() { - let data = [ - 0x01, 0xef, 0x39, 0x8e, 0x4b, 'u' as u8, 't' as u8, 'f' as u8, '-' as u8, '8' as u8, - ]; + let data = [0x01, 0xef, 0x39, 0x8e, 0x4b, b'u', b't', b'f', b'-', b'8']; let extra = UnicodeExtraField::try_from_reader(&mut std::io::Cursor::new(data), 10).unwrap(); let res = extra.unwrap_valid(b"abcdef"); @@ -64,9 +62,7 @@ mod tests { #[test] fn unicode_extra_field_crc32_incorrect() { - let data = [ - 0x01, 0x00, 0x00, 0x00, 0x00, 'u' as u8, 't' as u8, 'f' as u8, '-' as u8, '8' as u8, - ]; + let data = [0x01, 0x00, 0x00, 0x00, 0x00, b'u', b't', b'f', b'-', b'8']; let extra = UnicodeExtraField::try_from_reader(&mut std::io::Cursor::new(data), 10).unwrap(); let res = extra.unwrap_valid(b"abcdef"); diff --git a/src/read.rs b/src/read.rs index 65ea29cd1..98a0c8549 100644 --- a/src/read.rs +++ b/src/read.rs @@ -47,6 +47,7 @@ pub use crate::aes::AesInfo; /// If your logic depends on the buffer being completely populated, use [`Self::read_exact()`] instead. It will continue reading until the entire buffer is filled or an error occurs. #[derive(Debug)] pub struct ZipFile<'a, R: Read + ?Sized> { + pub(crate) file_name_raw: Cow<'a, [u8]>, pub(crate) data: Cow<'a, ZipFileData>, pub(crate) reader: ZipFileReader<'a, R>, } @@ -78,7 +79,7 @@ pub(crate) fn make_writable_dir_all>(outpath: T) -> Result<(), Zi pub(crate) fn make_symlink_impl( outpath: &Path, target_str: &str, - _existing_files: &IndexMap, T>, + _existing_files: &IndexMap, T>, ) -> ZipResult<()> { std::os::unix::fs::symlink(Path::new(&target_str), outpath)?; Ok(()) @@ -88,7 +89,7 @@ pub(crate) fn make_symlink_impl( pub(crate) fn make_symlink_impl( outpath: &Path, target_str: &str, - existing_files: &IndexMap, T>, + existing_files: &IndexMap, T>, ) -> ZipResult<()> { let target = Path::new(OsStr::new(&target_str)); let target_is_dir_from_archive = @@ -112,7 +113,7 @@ pub(crate) fn make_symlink_impl( pub(crate) fn make_symlink( outpath: &Path, target: &[u8], - #[cfg_attr(not(any(windows, unix)), allow(unused))] existing_files: &IndexMap, T>, + #[cfg_attr(not(any(windows, unix)), allow(unused))] existing_files: &IndexMap, T>, ) -> ZipResult<()> { let Ok(target_str) = std::str::from_utf8(target) else { return Err(invalid!("Invalid UTF-8 as symlink target")); @@ -124,7 +125,7 @@ pub(crate) fn make_symlink( pub(crate) fn make_symlink( outpath: &Path, target: &[u8], - #[cfg_attr(not(any(windows, unix)), allow(unused))] existing_files: &IndexMap, T>, + #[cfg_attr(not(any(windows, unix)), allow(unused))] existing_files: &IndexMap, T>, ) -> ZipResult<()> { let Ok(_) = std::str::from_utf8(target) else { return Err(invalid!("Invalid UTF-8 as symlink target")); @@ -218,7 +219,7 @@ impl ZipArchive { pub(crate) fn merge_contents( &mut self, mut w: W, - ) -> ZipResult, ZipFileData>> { + ) -> ZipResult, ZipFileData>> { if self.shared.files.is_empty() { return Ok(IndexMap::new()); } @@ -455,13 +456,13 @@ impl ZipArchive { pub(crate) fn central_header_to_zip_file( reader: &mut R, central_directory: &CentralDirectoryInfo, -) -> ZipResult { +) -> ZipResult<(ZipFileData, Arc<[u8]>)> { let central_header_start = reader.stream_position()?; // Parse central header let block = ZipCentralEntryBlock::parse(reader)?; - let file = central_header_to_zip_file_inner( + let (file, file_name_raw) = central_header_to_zip_file_inner( reader, central_directory.archive_offset, central_header_start, @@ -471,12 +472,12 @@ pub(crate) fn central_header_to_zip_file( let central_header_end = reader.stream_position()?; reader.seek(SeekFrom::Start(central_header_end))?; - Ok(file) + Ok((file, file_name_raw)) } #[inline] -fn read_variable_length_byte_field(reader: &mut R, len: usize) -> ZipResult> { - let mut data = vec![0; len].into_boxed_slice(); +fn read_variable_length_byte_field(reader: &mut R, len: usize) -> ZipResult> { + let mut data = vec![0; len]; if let Err(e) = reader.read_exact(&mut data) { if e.kind() == io::ErrorKind::UnexpectedEof { return Err(invalid!( @@ -494,7 +495,7 @@ fn central_header_to_zip_file_inner( archive_offset: u64, central_header_start: u64, block: ZipCentralEntryBlock, -) -> ZipResult { +) -> ZipResult<(ZipFileData, Arc<[u8]>)> { let ZipCentralEntryBlock { // magic, version_made_by, @@ -518,14 +519,10 @@ fn central_header_to_zip_file_inner( let is_utf8 = ZipFlags::matching(flags, ZipFlags::LanguageEncoding); - let file_name_raw = read_variable_length_byte_field(reader, file_name_length as usize)?; + let mut file_name_raw = read_variable_length_byte_field(reader, file_name_length as usize)?; let extra_field = read_variable_length_byte_field(reader, extra_field_length as usize)?; let file_comment_raw = read_variable_length_byte_field(reader, file_comment_length as usize)?; - let file_name: Box = if is_utf8 { - String::from_utf8_lossy(&file_name_raw).into() - } else { - file_name_raw.from_cp437()?.into() - }; + let file_comment: Box = if is_utf8 { String::from_utf8_lossy(&file_comment_raw).into() } else { @@ -543,8 +540,7 @@ fn central_header_to_zip_file_inner( compressed_size: compressed_size.into(), uncompressed_size: uncompressed_size.into(), flags, - file_name, - file_name_raw, + file_name: Arc::from(""), // temporary extra_field: Some(Arc::from(extra_field)), central_extra_field: None, file_comment, @@ -558,7 +554,25 @@ fn central_header_to_zip_file_inner( aes_extra_data_start: 0, extra_fields: Vec::new(), }; - parse_extra_field(&mut result)?; + parse_extra_field(&mut result, &mut file_name_raw)?; + + let is_utf8 = ZipFlags::matching(result.flags, ZipFlags::LanguageEncoding); + let file_name_arc: Arc; + let file_name_raw_arc: Arc<[u8]>; + if is_utf8 { + if let Ok(s) = std::str::from_utf8(&file_name_raw) { + file_name_arc = Arc::from(s); + file_name_raw_arc = + unsafe { Arc::from_raw(Arc::into_raw(file_name_arc.clone()) as *const [u8]) }; + } else { + file_name_arc = String::from_utf8_lossy(&file_name_raw).into(); + file_name_raw_arc = file_name_raw.into(); + } + } else { + file_name_arc = file_name_raw.from_cp437()?.into(); + file_name_raw_arc = file_name_raw.into(); + } + result.file_name = file_name_arc; let aes_enabled = result.compression_method == CompressionMethod::AES; if aes_enabled && result.aes_mode.is_none() { @@ -571,10 +585,13 @@ fn central_header_to_zip_file_inner( .checked_add(archive_offset) .ok_or(invalid!("Archive header is too large"))?; - Ok(result) + Ok((result, file_name_raw_arc)) } -pub(crate) fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { +pub(crate) fn parse_extra_field( + file: &mut ZipFileData, + file_name_raw: &mut Vec, +) -> ZipResult<()> { let mut extra_field = file.extra_field.clone(); let mut central_extra_field = file.central_extra_field.clone(); for field_group in [&mut extra_field, &mut central_extra_field] { @@ -589,7 +606,8 @@ pub(crate) fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { let mut position = reader.position(); while position < len as u64 { let old_position = position; - let remove = parse_single_extra_field(file, &mut reader, position, false)?; + let remove = + parse_single_extra_field(file, &mut reader, position, false, file_name_raw)?; position = reader.position(); if remove { modified = true; @@ -622,6 +640,7 @@ pub(crate) fn parse_single_extra_field( reader: &mut R, bytes_already_read: u64, disallow_zip64: bool, + file_name_raw: &mut Vec, ) -> ZipResult { let kind = match reader.read_u16_le() { Ok(kind) => kind, @@ -699,10 +718,9 @@ pub(crate) fn parse_single_extra_field( Ok(UsedExtraField::UnicodePath) => { // Info-ZIP Unicode Path Extra Field // APPNOTE 4.6.9 and https://libzip.org/specifications/extrafld.txt - file.file_name_raw = UnicodeExtraField::try_from_reader(reader, len)? - .unwrap_valid(&file.file_name_raw)?; - file.file_name = - String::from_utf8(file.file_name_raw.clone().into_vec())?.into_boxed_str(); + let unicode = UnicodeExtraField::try_from_reader(reader, len)?; + let file_name = unicode.unwrap_valid(file_name_raw)?; + *file_name_raw = file_name.into_vec(); file.flags |= ZipFlags::LanguageEncoding.as_u16(); } _ => { @@ -800,7 +818,7 @@ impl<'a, R: Read + ?Sized> ZipFile<'a, R> { /// /// The encoding of this data is currently undefined. pub fn name_raw(&self) -> &[u8] { - &self.get_metadata().file_name_raw + &self.file_name_raw } /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte, diff --git a/src/read/stream.rs b/src/read/stream.rs index 567c1f33b..ebc6bc6d0 100644 --- a/src/read/stream.rs +++ b/src/read/stream.rs @@ -11,6 +11,7 @@ use indexmap::IndexMap; use std::borrow::Cow; use std::io::{self, Read}; use std::path::{Path, PathBuf}; +use std::sync::Arc; /// Stream decoder for zip. #[derive(Debug)] @@ -32,13 +33,13 @@ impl ZipStreamReader { // Parse central header let block = ZipCentralEntryBlock::parse(&mut self.0)?; - let file = central_header_to_zip_file_inner( + let (file, file_name_raw) = central_header_to_zip_file_inner( &mut self.0, archive_offset, central_header_start, block, )?; - Ok(ZipStreamFileMetadata(file)) + Ok(ZipStreamFileMetadata(file, file_name_raw)) } /// Iterate over the stream and extract all file and their @@ -61,7 +62,7 @@ impl ZipStreamReader { /// Extraction is not atomic; If an error is encountered, some of the files /// may be left on disk. pub fn extract>(self, directory: P) -> ZipResult<()> { - struct Extractor(PathBuf, IndexMap, ()>); + struct Extractor(PathBuf, IndexMap, ()>); impl ZipStreamVisitor for Extractor { fn visit_file(&mut self, file: &mut ZipFile<'_, R>) -> ZipResult<()> { self.1.insert(file.name_raw().into(), ()); @@ -133,7 +134,7 @@ pub trait ZipStreamVisitor { /// Additional metadata for the file. #[derive(Debug)] -pub struct ZipStreamFileMetadata(ZipFileData); +pub struct ZipStreamFileMetadata(ZipFileData, Arc<[u8]>); impl ZipStreamFileMetadata { /// Get the name of the file @@ -156,7 +157,7 @@ impl ZipStreamFileMetadata { /// /// The encoding of this data is currently undefined. pub fn name_raw(&self) -> &[u8] { - &self.0.file_name_raw + &self.1 } /// Rewrite the path, ignoring any path components with special meaning. @@ -244,9 +245,9 @@ pub fn read_zipfile_from_stream(reader: &mut R) -> ZipResult {} Err(e) => return Err(e), } @@ -263,6 +264,7 @@ pub fn read_zipfile_from_stream(reader: &mut R) -> ZipResult( let block = block.from_le(); - let mut result = ZipFileData::from_local_block(block, reader)?; + let (mut result, file_name_raw) = ZipFileData::from_local_block(block, reader)?; result.compressed_size = compressed_size; if result.is_encrypted() { @@ -316,6 +318,7 @@ pub fn read_zipfile_from_stream_with_compressed_size( } = result; Ok(Some(ZipFile { + file_name_raw: Cow::Owned(file_name_raw), data: Cow::Owned(result), reader: make_reader( compression_method, @@ -568,9 +571,12 @@ mod tests { let mut reader = Cursor::new(bytes); loop { - if read_zipfile_from_stream_with_compressed_size(&mut reader, compressed_size as u64) - .unwrap() - .is_none() + if read_zipfile_from_stream_with_compressed_size( + &mut reader, + u64::from(compressed_size), + ) + .unwrap() + .is_none() { break; } diff --git a/src/read/zip_archive.rs b/src/read/zip_archive.rs index e5aeb4857..754f4ea6c 100644 --- a/src/read/zip_archive.rs +++ b/src/read/zip_archive.rs @@ -20,7 +20,7 @@ use std::sync::Arc; /// Immutable metadata about a `ZipArchive`. #[derive(Debug)] pub struct ZipArchiveMetadata { - pub(crate) files: IndexMap, ZipFileData>, + pub(crate) files: IndexMap, ZipFileData>, pub(crate) offset: u64, pub(crate) dir_start: u64, // This isn't yet used anywhere, but it is here for use cases in the future. @@ -32,7 +32,7 @@ pub struct ZipArchiveMetadata { #[derive(Debug)] pub(crate) struct SharedBuilder { - pub(crate) files: Vec, + pub(crate) files: Vec<(Arc<[u8]>, ZipFileData)>, pub(super) offset: u64, pub(super) dir_start: u64, // This isn't yet used anywhere, but it is here for use cases in the future. @@ -47,8 +47,8 @@ impl SharedBuilder { zip64_extensible_data_sector: Option>, ) -> ZipArchiveMetadata { let mut index_map = IndexMap::with_capacity(self.files.len()); - self.files.into_iter().for_each(|file| { - index_map.insert(file.file_name_raw.clone(), file); + self.files.into_iter().for_each(|(file_name_raw, file)| { + index_map.insert(file_name_raw, file); }); ZipArchiveMetadata { files: index_map, @@ -89,7 +89,7 @@ pub struct ZipArchive { impl ZipArchive { pub(crate) fn from_finalized_writer( - files: IndexMap, ZipFileData>, + files: IndexMap, ZipFileData>, comment: Box<[u8]>, zip64_extensible_data_sector: Option>, reader: R, @@ -200,8 +200,8 @@ impl ZipArchive { let mut files = Vec::with_capacity(file_capacity); reader.seek(SeekFrom::Start(dir_info.directory_start))?; for _ in 0..dir_info.number_of_files { - let file = central_header_to_zip_file(reader, dir_info)?; - files.push(file); + let (file, file_name_raw) = central_header_to_zip_file(reader, dir_info)?; + files.push((file_name_raw, file)); } Ok(SharedBuilder { @@ -534,12 +534,13 @@ impl ZipArchive { /// Get a contained file by index without decompressing it pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult> { let reader = &mut self.reader; - let (_, data) = self + let (file_name_raw, data) = self .shared .files .get_index(file_number) .ok_or(ZipError::FileNotFound)?; Ok(ZipFile { + file_name_raw: Cow::Borrowed(file_name_raw), reader: ZipFileReader::Raw(data.find_content(reader)?), data: Cow::Borrowed(data), }) @@ -551,12 +552,11 @@ impl ZipArchive { file_number: usize, mut options: ZipReadOptions<'_>, ) -> ZipResult> { - let (_, data) = self + let (file_name_raw, data) = self .shared .files .get_index(file_number) .ok_or(ZipError::FileNotFound)?; - if options.ignore_encryption_flag { // Always use no password when we're ignoring the encryption flag. options.password = None; @@ -583,6 +583,7 @@ impl ZipArchive { }; Ok(ZipFile { + file_name_raw: Cow::Borrowed(file_name_raw), data: Cow::Borrowed(data), reader: make_reader( data.compression_method, diff --git a/src/types.rs b/src/types.rs index 2bcc8d6c4..090c31e9d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -184,9 +184,7 @@ pub struct ZipFileData { /// Size of the file when extracted pub uncompressed_size: u64, /// Name of the file - pub file_name: Box, - /// Raw file name. To be used when `file_name` was incorrectly decoded. - pub file_name_raw: Box<[u8]>, + pub file_name: Arc, /// Extra field usually used for storage expansion pub extra_field: Option>, /// Extra field only written to central directory @@ -405,8 +403,8 @@ impl ZipFileData { } #[allow(clippy::too_many_arguments)] - pub(crate) fn initialize_local_block( - name: &S, + pub(crate) fn initialize_local_block( + file_name: Arc, options: &FileOptions<'_, T>, raw_values: &ZipRawValues, header_start: u64, @@ -415,15 +413,10 @@ impl ZipFileData { compression_method: CompressionMethod, aes_mode: Option<(AesMode, AesVendorVersion, CompressionMethod)>, extra_field: &[u8], - ) -> Self - where - S: ToString, - { + ) -> Self { let permissions = options .permissions .unwrap_or(FileOptions::DEFAULT_FILE_PERMISSION); - let file_name: Box = name.to_string().into_boxed_str(); - let file_name_raw: Box<[u8]> = file_name.as_bytes().into(); let mut external_attributes = permissions << 16; let system = if (permissions & ffi::S_IFLNK) == ffi::S_IFLNK { System::Unix @@ -468,7 +461,6 @@ impl ZipFileData { compressed_size: raw_values.compressed_size, uncompressed_size: raw_values.uncompressed_size, file_name, // Never used for saving, but used as map key in insert_file_data() - file_name_raw, extra_field: Some(Arc::from(extra_field)), central_extra_field: options .extended_options @@ -492,7 +484,7 @@ impl ZipFileData { pub(crate) fn from_local_block( block: ZipLocalEntryBlock, reader: &mut R, - ) -> ZipResult { + ) -> ZipResult<(Self, Vec)> { let ZipLocalEntryBlock { version_made_by, flags, @@ -542,7 +534,7 @@ impl ZipFileData { return Err(e.into()); } - let file_name: Box = if is_utf8 { + let file_name: Arc = if is_utf8 { String::from_utf8_lossy(&file_name_raw).into() } else { file_name_raw @@ -552,7 +544,7 @@ impl ZipFileData { }; let (version_made_by, system) = System::extract_bytes(version_made_by); - Ok(ZipFileData { + let data = ZipFileData { system, version_made_by, flags, @@ -562,7 +554,6 @@ impl ZipFileData { compressed_size: compressed_size.into(), uncompressed_size: uncompressed_size.into(), file_name, - file_name_raw: file_name_raw.into(), extra_field: Some(Arc::from(extra_field.into_boxed_slice())), central_extra_field: None, file_comment: String::with_capacity(0).into_boxed_str(), // file comment is only available in the central directory @@ -580,12 +571,13 @@ impl ZipFileData { extra_fields: Vec::new(), extra_data_start: None, aes_extra_data_start: 0, - }) + }; + Ok((data, file_name_raw)) } - fn flags(&self) -> u16 { - let is_utf8 = std::str::from_utf8(&self.file_name_raw).is_ok(); // file_comment is always utf8 - let is_ascii = self.file_name_raw.is_ascii() && self.file_comment.is_ascii(); + fn flags(&self, file_name_raw: &[u8]) -> u16 { + let is_utf8 = std::str::from_utf8(file_name_raw).is_ok(); + let is_ascii = file_name_raw.is_ascii() && self.file_comment.is_ascii(); let utf8_bit: u16 = if is_utf8 && !is_ascii { ZipFlags::LanguageEncoding.as_u16() } else { @@ -614,7 +606,7 @@ impl ZipFileData { } } - pub(crate) fn local_block(&self) -> ZipResult { + pub(crate) fn local_block(&self, file_name_raw: &[u8]) -> ZipResult { let (compressed_size, uncompressed_size) = if self.is_using_data_descriptor() { (0, 0) } else { @@ -633,15 +625,14 @@ impl ZipFileData { .unwrap_or_else(DateTime::default_for_write); Ok(ZipLocalEntryBlock { version_made_by: self.version_needed(), - flags: self.flags(), + flags: self.flags(file_name_raw), compression_method: self.compression_method.serialize_to_u16(), last_mod_time: last_modified_time.timepart(), last_mod_date: last_modified_time.datepart(), crc32: self.crc32, compressed_size, uncompressed_size, - file_name_length: self - .file_name_raw + file_name_length: file_name_raw .len() .try_into() .map_err(std::io::Error::other)?, @@ -649,7 +640,7 @@ impl ZipFileData { }) } - pub(crate) fn block(&self) -> ZipResult { + pub(crate) fn block(&self, file_name_raw: &[u8]) -> ZipResult { let compressed_size = if self.large_file { spec::ZIP64_BYTES_THR as u32 } else { @@ -687,15 +678,14 @@ impl ZipFileData { Ok(ZipCentralEntryBlock { version_made_by: ((self.system as u16) << 8) | version_made_by, version_to_extract, - flags: self.flags(), + flags: self.flags(file_name_raw), compression_method: self.compression_method.serialize_to_u16(), last_mod_time: last_modified_time.timepart(), last_mod_date: last_modified_time.datepart(), crc32: self.crc32, compressed_size, uncompressed_size, - file_name_length: self - .file_name_raw + file_name_length: file_name_raw .len() .try_into() .map_err(std::io::Error::other)?, @@ -903,8 +893,7 @@ mod tests { crc32: 0, compressed_size: 0, uncompressed_size: 0, - file_name: file_name.clone().into_boxed_str(), - file_name_raw: file_name.into_bytes().into_boxed_slice(), + file_name: file_name.into(), extra_field: None, central_extra_field: None, file_comment: String::with_capacity(0).into_boxed_str(), diff --git a/src/write.rs b/src/write.rs index e1120f193..7bbeb102a 100644 --- a/src/write.rs +++ b/src/write.rs @@ -137,6 +137,7 @@ pub(crate) mod zip_writer { use core::fmt::{Debug, Formatter}; use indexmap::IndexMap; use std::io::{Seek, Write}; + use std::sync::Arc; /// ZIP archive generator /// @@ -173,7 +174,7 @@ pub(crate) mod zip_writer { /// ``` pub struct ZipWriter { pub(super) inner: GenericZipWriter, - pub(super) files: IndexMap, ZipFileData>, + pub(super) files: IndexMap, ZipFileData>, pub(super) stats: ZipWriterStats, pub(super) writing_to_file: bool, pub(super) writing_raw: bool, @@ -436,7 +437,13 @@ impl ExtendedFileOptions { } data.seek(SeekFrom::Current(-2))?; } - parse_single_extra_field(&mut ZipFileData::default(), &mut data, pos, disallow_zip64)?; + parse_single_extra_field( + &mut ZipFileData::default(), + &mut data, + pos, + disallow_zip64, + &mut Vec::new(), + )?; pos = data.position(); } Ok(()) @@ -894,12 +901,16 @@ impl ZipWriter { .try_inner_mut()? .seek(SeekFrom::Start(write_position))?; let mut new_data = src_data.clone(); - new_data.file_name_raw = dest_name.as_bytes().into(); - new_data.file_name = dest_name.into(); + let dest_name_string = dest_name.to_string(); + let dest_name_arc: Arc = dest_name_string.into(); + let dest_name_raw_arc: Arc<[u8]> = + unsafe { Arc::from_raw(Arc::into_raw(dest_name_arc.clone()) as *const [u8]) }; + let dest_name_raw = dest_name_raw_arc.as_ref(); + new_data.file_name = dest_name_arc; new_data.header_start = write_position; let extra_data_start = write_position + (size_of::() + size_of::()) as u64 - + new_data.file_name_raw.len() as u64; + + dest_name_raw.len() as u64; new_data.extra_data_start = Some(extra_data_start); if let Some(extra) = &src_data.extra_field { let stripped = strip_alignment_extra_field(extra, false); @@ -917,13 +928,13 @@ impl ZipWriter { new_data.data_start.take(); new_data.data_start.get_or_init(|| data_start); new_data.central_header_start = 0; - let block = new_data.local_block()?; - let index = self.insert_file_data(new_data)?; + let block = new_data.local_block(dest_name_raw)?; + let index = self.insert_file_data(dest_name_raw_arc.clone(), new_data)?; let new_data = &self.files[index]; let result: io::Result<()> = { let plain_writer = self.inner.try_inner_mut()?; block.write(plain_writer)?; - plain_writer.write_all(&new_data.file_name_raw)?; + plain_writer.write_all(dest_name_raw)?; if let Some(data) = &new_data.extra_field { plain_writer.write_all(data)?; } @@ -1276,8 +1287,12 @@ impl ZipWriter { } #[cfg(feature = "aes-crypto")] let aes_mode = aes_mode.map(super::aes::AesModeOptions::to_tuple); + let file_name: Arc = name.to_string().into(); + let file_name_raw_arc: Arc<[u8]> = + unsafe { Arc::from_raw(Arc::into_raw(file_name.clone()) as *const [u8]) }; + let file_name_raw = file_name_raw_arc.as_ref(); let mut file = ZipFileData::initialize_local_block( - name, + file_name, &options, &raw_values, header_start, @@ -1301,16 +1316,16 @@ impl ZipWriter { } file.version_made_by = file.version_made_by.max(file.version_needed() as u8); file.extra_data_start = Some(header_end); - let index = self.insert_file_data(file)?; + let index = self.insert_file_data(file_name_raw_arc.clone(), file)?; self.writing_to_file = true; let result: ZipResult<()> = { ExtendedFileOptions::validate_extra_data(&extra_data, false)?; let file = &mut self.files[index]; - let block = file.local_block()?; + let block = file.local_block(file_name_raw)?; let writer = self.inner.try_inner_mut()?; block.write(writer)?; // file name - writer.write_all(&file.file_name_raw)?; + writer.write_all(file_name_raw)?; if extra_data_len > 0 { writer.write_all(&extra_data)?; file.extra_field = Some(Arc::from(extra_data.into_boxed_slice())); @@ -1379,11 +1394,15 @@ impl ZipWriter { Ok(()) } - fn insert_file_data(&mut self, file: ZipFileData) -> ZipResult { - if self.files.contains_key(&file.file_name_raw) { + fn insert_file_data( + &mut self, + file_name_raw: Arc<[u8]>, + file: ZipFileData, + ) -> ZipResult { + if self.files.contains_key(file_name_raw.as_ref()) { return Err(invalid!("Duplicate filename: {}", file.file_name)); } - let (index, _) = self.files.insert_full(file.file_name_raw.clone(), file); + let (index, _) = self.files.insert_full(file_name_raw, file); Ok(index) } @@ -1403,9 +1422,8 @@ impl ZipWriter { let writer = self.inner.try_inner_mut()?; if !self.writing_raw { - let file = match self.files.last_mut() { - None => return Ok(()), - Some((_, f)) => f, + let Some((file_name_raw, file)) = self.files.last_mut() else { + return Ok(()); }; file.uncompressed_size = self.stats.bytes_written; @@ -1431,7 +1449,7 @@ impl ZipWriter { if file.is_using_data_descriptor() { file.write_data_descriptor(writer, self.auto_large_file)?; } else { - file.update_local_file_header(writer)?; + file.update_local_file_header(writer, file_name_raw)?; writer.seek(SeekFrom::Start(file_end))?; } } @@ -1900,8 +1918,8 @@ impl ZipWriter { let mut version_needed = u16::from(MIN_VERSION); let central_start = writer.stream_position()?; - for file in self.files.values() { - file.write_central_directory_header(writer)?; + for (filename_raw, file) in &self.files { + file.write_central_directory_header(writer, filename_raw)?; version_needed = version_needed.max(file.version_needed()); } let central_size = writer.stream_position()? - central_start; @@ -1977,10 +1995,13 @@ impl ZipWriter { } let src_index = self.index_by_name(src_name.as_bytes())?; let mut dest_data = self.files[src_index].clone(); - dest_data.file_name = dest_name.into(); - dest_data.file_name_raw = dest_name.as_bytes().into(); + let dest_name_string = dest_name.to_string(); + let dest_name_arc: Arc = dest_name_string.into(); + let dest_name_raw_arc: Arc<[u8]> = + unsafe { Arc::from_raw(Arc::into_raw(dest_name_arc.clone()) as *const [u8]) }; + dest_data.file_name = dest_name_arc; dest_data.central_header_start = 0; - self.insert_file_data(dest_data)?; + self.insert_file_data(dest_name_raw_arc, dest_data)?; Ok(()) } @@ -2441,6 +2462,7 @@ impl ZipFileData { pub(crate) fn update_local_file_header( &mut self, writer: &mut T, + file_name_raw: &[u8], ) -> ZipResult<()> { writer.seek(SeekFrom::Start( self.header_start + (size_of::() + offset_of!(ZipLocalEntryBlock, crc32)) as u64, @@ -2450,7 +2472,7 @@ impl ZipFileData { writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?; writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?; - self.update_local_zip64_extra_field(writer)?; + self.update_local_zip64_extra_field(writer, file_name_raw)?; // self.compressed_size = spec::ZIP64_BYTES_THR; // self.uncompressed_size = spec::ZIP64_BYTES_THR; @@ -2468,7 +2490,11 @@ impl ZipFileData { Ok(()) } - fn update_local_zip64_extra_field(&mut self, writer: &mut T) -> ZipResult<()> { + fn update_local_zip64_extra_field( + &mut self, + writer: &mut T, + file_name_raw: &[u8], + ) -> ZipResult<()> { let zip64_block = Zip64ExtendedInformation::local_header( self.large_file, self.uncompressed_size, @@ -2480,7 +2506,7 @@ impl ZipFileData { let zip64_extra_field_start = self.header_start + (size_of::() + size_of::()) as u64 - + self.file_name_raw.len() as u64; + + file_name_raw.len() as u64; writer.seek(SeekFrom::Start(zip64_extra_field_start))?; let zip64_block = zip64_block.serialize(); @@ -2488,8 +2514,12 @@ impl ZipFileData { Ok(()) } - pub(crate) fn write_central_directory_header(&self, writer: &mut T) -> ZipResult<()> { - let mut block = self.block()?; + pub(crate) fn write_central_directory_header( + &self, + writer: &mut T, + file_name_raw: &[u8], + ) -> ZipResult<()> { + let mut block = self.block(file_name_raw)?; let stripped_extra = if let Some(extra) = &self.extra_field { strip_alignment_extra_field(extra, true) } else { @@ -2513,7 +2543,7 @@ impl ZipFileData { block.write(writer)?; // file name - writer.write_all(&self.file_name_raw)?; + writer.write_all(file_name_raw)?; // extra field if let Some(zip64_extra_field) = zip64_extra_field_block { writer.write_all(&zip64_extra_field.serialize())?;