From e3c81023a7ebedceaf287be98f3a10b5c1c18f8e Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:52:20 -0700 Subject: [PATCH 01/10] fix: Decode Zip-Info UTF8 name and comment fields (#159) --- examples/extract.rs | 2 +- src/extra_fields/mod.rs | 2 ++ src/extra_fields/zipinfo_utf8.rs | 40 ++++++++++++++++++++++++++++++++ src/read.rs | 18 ++++++++++++++ src/result.rs | 7 ++++++ src/types.rs | 5 ++++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/extra_fields/zipinfo_utf8.rs diff --git a/examples/extract.rs b/examples/extract.rs index 7359b53e..57cfba0d 100644 --- a/examples/extract.rs +++ b/examples/extract.rs @@ -19,7 +19,7 @@ fn real_main() -> i32 { for i in 0..archive.len() { let mut file = archive.by_index(i).unwrap(); let outpath = match file.enclosed_name() { - Some(path) => path.to_owned(), + Some(path) => path, None => continue, }; diff --git a/src/extra_fields/mod.rs b/src/extra_fields/mod.rs index 145cfade..ee8defec 100644 --- a/src/extra_fields/mod.rs +++ b/src/extra_fields/mod.rs @@ -17,8 +17,10 @@ impl ExtraFieldVersion for LocalHeaderVersion {} impl ExtraFieldVersion for CentralHeaderVersion {} mod extended_timestamp; +mod zipinfo_utf8; pub use extended_timestamp::*; +pub use zipinfo_utf8::*; /// contains one extra field #[derive(Debug, Clone)] diff --git a/src/extra_fields/zipinfo_utf8.rs b/src/extra_fields/zipinfo_utf8.rs new file mode 100644 index 00000000..f8e360fa --- /dev/null +++ b/src/extra_fields/zipinfo_utf8.rs @@ -0,0 +1,40 @@ +use core::mem::size_of; +use std::io::Read; +use crate::result::{ZipError, ZipResult}; +use crate::unstable::LittleEndianReadExt; + +/// Info-ZIP Unicode Path Extra Field (0x7075) or Unicode Comment Extra Field (0x6375), as +/// specified in APPNOTE 4.6.8 and 4.6.9 +#[derive(Clone, Debug)] +pub struct UnicodeExtraField { + crc32: u32, + content: Box<[u8]> +} + +impl<'a> UnicodeExtraField { + /// Verifies the checksum and returns the content. + pub fn unwrap_valid(self, ascii_field: &[u8]) -> ZipResult> { + let mut crc32 = crc32fast::Hasher::new(); + crc32.update(ascii_field); + let actual_crc32 = crc32.finalize(); + if self.crc32 != actual_crc32 { + return Err(ZipError::InvalidArchive("CRC32 checksum failed on Unicode extra field")); + } + Ok(self.content) + } +} + +impl UnicodeExtraField { + pub(crate) fn try_from_reader(reader: &mut R, len: u16) -> ZipResult { + // Read and discard version byte + reader.read_exact(&mut [0u8])?; + + let crc32 = reader.read_u32_le()?; + let mut content = vec![0u8; len as usize - size_of::() - size_of::()].into_boxed_slice(); + reader.read_exact(&mut content)?; + Ok(Self { + crc32, + content + }) + } +} \ No newline at end of file diff --git a/src/read.rs b/src/read.rs index 94ed2366..6d9f441d 100644 --- a/src/read.rs +++ b/src/read.rs @@ -102,6 +102,7 @@ use crate::spec::{is_dir, path_to_string}; use crate::types::ffi::S_IFLNK; use crate::unstable::LittleEndianReadExt; pub use zip_archive::ZipArchive; +use crate::extra_fields::UnicodeExtraField; #[allow(clippy::large_enum_variant)] pub(crate) enum CryptoReader<'a> { @@ -1160,6 +1161,7 @@ fn central_header_to_zip_file_inner( version_made_by: version_made_by as u8, encrypted, using_data_descriptor, + is_utf8, compression_method: CompressionMethod::parse_from_u16(compression_method), compression_level: None, last_modified_time: DateTime::try_from_msdos(last_mod_date, last_mod_time).ok(), @@ -1279,6 +1281,22 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { // the reader for ExtendedTimestamp consumes `len` bytes len_left = 0; } + 0x6375 => { + // Info-ZIP Unicode Comment Extra Field + // APPNOTE 4.6.8 and https://libzip.org/specifications/extrafld.txt + if !file.is_utf8 { + file.file_comment = String::from_utf8( + UnicodeExtraField::try_from_reader(&mut reader, len)?.unwrap_valid(file.file_comment.as_bytes())?.into_vec())?.into(); + } + } + 0x7075 => { + // Info-ZIP Unicode Path Extra Field + // APPNOTE 4.6.9 and https://libzip.org/specifications/extrafld.txt + if !file.is_utf8 { + file.file_name_raw = UnicodeExtraField::try_from_reader(&mut reader, len)?.unwrap_valid(&file.file_name_raw)?; + file.file_name = String::from_utf8(file.file_name_raw.clone().into_vec())?.into_boxed_str(); + } + } _ => { // Other fields are ignored } diff --git a/src/result.rs b/src/result.rs index 7bd5cad5..ec8fbb13 100644 --- a/src/result.rs +++ b/src/result.rs @@ -9,6 +9,7 @@ use std::error::Error; use std::fmt; use std::io; use std::num::TryFromIntError; +use std::string::FromUtf8Error; /// Generic result type with ZipError as its error variant pub type ZipResult = Result; @@ -68,6 +69,12 @@ impl From for ZipError { } } +impl From for ZipError { + fn from(_: FromUtf8Error) -> Self { + ZipError::InvalidArchive("Invalid UTF-8") + } +} + /// Error type for time parsing #[derive(Debug)] pub struct DateTimeRangeError; diff --git a/src/types.rs b/src/types.rs index f56b3c06..181592e6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -415,6 +415,8 @@ pub struct ZipFileData { pub version_made_by: u8, /// True if the file is encrypted. pub encrypted: bool, + /// True if file_name and file_comment are UTF8 + pub is_utf8: bool, /// True if the file uses a data-descriptor section pub using_data_descriptor: bool, /// Compression method used to store the file @@ -612,6 +614,7 @@ impl ZipFileData { version_made_by: DEFAULT_VERSION, encrypted: options.encrypt_with.is_some(), using_data_descriptor: false, + is_utf8: !file_name.is_ascii(), compression_method, compression_level: options.compression_level, last_modified_time: Some(options.last_modified_time), @@ -695,6 +698,7 @@ impl ZipFileData { version_made_by: version_made_by as u8, encrypted, using_data_descriptor, + is_utf8, compression_method, compression_level: None, last_modified_time: DateTime::try_from_msdos(last_mod_date, last_mod_time).ok(), @@ -1071,6 +1075,7 @@ mod test { version_made_by: 0, encrypted: false, using_data_descriptor: false, + is_utf8: true, compression_method: crate::compression::CompressionMethod::Stored, compression_level: None, last_modified_time: None, From 7530ce50007cbeaadd73acfb773029ec8c78af4f Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:56:46 -0700 Subject: [PATCH 02/10] style: Cargo fmt --all --- src/extra_fields/zipinfo_utf8.rs | 20 ++++++++++---------- src/read.rs | 15 +++++++++++---- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/extra_fields/zipinfo_utf8.rs b/src/extra_fields/zipinfo_utf8.rs index f8e360fa..c8ae6d0d 100644 --- a/src/extra_fields/zipinfo_utf8.rs +++ b/src/extra_fields/zipinfo_utf8.rs @@ -1,14 +1,14 @@ -use core::mem::size_of; -use std::io::Read; use crate::result::{ZipError, ZipResult}; use crate::unstable::LittleEndianReadExt; +use core::mem::size_of; +use std::io::Read; /// Info-ZIP Unicode Path Extra Field (0x7075) or Unicode Comment Extra Field (0x6375), as /// specified in APPNOTE 4.6.8 and 4.6.9 #[derive(Clone, Debug)] pub struct UnicodeExtraField { crc32: u32, - content: Box<[u8]> + content: Box<[u8]>, } impl<'a> UnicodeExtraField { @@ -18,7 +18,9 @@ impl<'a> UnicodeExtraField { crc32.update(ascii_field); let actual_crc32 = crc32.finalize(); if self.crc32 != actual_crc32 { - return Err(ZipError::InvalidArchive("CRC32 checksum failed on Unicode extra field")); + return Err(ZipError::InvalidArchive( + "CRC32 checksum failed on Unicode extra field", + )); } Ok(self.content) } @@ -30,11 +32,9 @@ impl UnicodeExtraField { reader.read_exact(&mut [0u8])?; let crc32 = reader.read_u32_le()?; - let mut content = vec![0u8; len as usize - size_of::() - size_of::()].into_boxed_slice(); + let mut content = + vec![0u8; len as usize - size_of::() - size_of::()].into_boxed_slice(); reader.read_exact(&mut content)?; - Ok(Self { - crc32, - content - }) + Ok(Self { crc32, content }) } -} \ No newline at end of file +} diff --git a/src/read.rs b/src/read.rs index 6d9f441d..83d3db33 100644 --- a/src/read.rs +++ b/src/read.rs @@ -95,6 +95,7 @@ pub(crate) mod zip_archive { #[cfg(feature = "aes-crypto")] use crate::aes::PWD_VERIFY_LENGTH; +use crate::extra_fields::UnicodeExtraField; #[cfg(feature = "lzma")] use crate::read::lzma::LzmaDecoder; use crate::result::ZipError::{InvalidPassword, UnsupportedArchive}; @@ -102,7 +103,6 @@ use crate::spec::{is_dir, path_to_string}; use crate::types::ffi::S_IFLNK; use crate::unstable::LittleEndianReadExt; pub use zip_archive::ZipArchive; -use crate::extra_fields::UnicodeExtraField; #[allow(clippy::large_enum_variant)] pub(crate) enum CryptoReader<'a> { @@ -1286,15 +1286,22 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { // APPNOTE 4.6.8 and https://libzip.org/specifications/extrafld.txt if !file.is_utf8 { file.file_comment = String::from_utf8( - UnicodeExtraField::try_from_reader(&mut reader, len)?.unwrap_valid(file.file_comment.as_bytes())?.into_vec())?.into(); + UnicodeExtraField::try_from_reader(&mut reader, len)? + .unwrap_valid(file.file_comment.as_bytes())? + .into_vec(), + )? + .into(); } } 0x7075 => { // Info-ZIP Unicode Path Extra Field // APPNOTE 4.6.9 and https://libzip.org/specifications/extrafld.txt if !file.is_utf8 { - file.file_name_raw = UnicodeExtraField::try_from_reader(&mut reader, len)?.unwrap_valid(&file.file_name_raw)?; - file.file_name = String::from_utf8(file.file_name_raw.clone().into_vec())?.into_boxed_str(); + file.file_name_raw = UnicodeExtraField::try_from_reader(&mut reader, len)? + .unwrap_valid(&file.file_name_raw)?; + file.file_name = + String::from_utf8(file.file_name_raw.clone().into_vec())?.into_boxed_str(); + file.is_utf8 = true; } } _ => { From 6279c38d8737750014d4b8c90f2a65a84653a67e Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:57:39 -0700 Subject: [PATCH 03/10] style: Remove unused lifetime parameter --- src/extra_fields/zipinfo_utf8.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extra_fields/zipinfo_utf8.rs b/src/extra_fields/zipinfo_utf8.rs index c8ae6d0d..7644b5f8 100644 --- a/src/extra_fields/zipinfo_utf8.rs +++ b/src/extra_fields/zipinfo_utf8.rs @@ -11,7 +11,7 @@ pub struct UnicodeExtraField { content: Box<[u8]>, } -impl<'a> UnicodeExtraField { +impl UnicodeExtraField { /// Verifies the checksum and returns the content. pub fn unwrap_valid(self, ascii_field: &[u8]) -> ZipResult> { let mut crc32 = crc32fast::Hasher::new(); From d547930a3f79c5d313a3dcb230df46e8e2eb0ed0 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:34:29 -0700 Subject: [PATCH 04/10] ci(fuzz): Update fuzzing dictionary with extra-field tags --- fuzz/fuzz.dict | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz.dict b/fuzz/fuzz.dict index 2e7ad6a5..db54193c 100644 --- a/fuzz/fuzz.dict +++ b/fuzz/fuzz.dict @@ -17,7 +17,11 @@ compression_method_bzip2="\x0C\x00" compression_method_lzma="\x0E\x00" compression_method_zstd="]\x00" compression_method_aes="C\x00" -compression_method_unsupported="\xFF\x00" +extra_field_zip64="\x01\x00" +extra_field_aes="\x99\x01" +extra_field_extended_timestamp="\x55\x54" +extra_field_utf8_comment="\x75\x63" +extra_field_utf8_filename="\x75\x70" "\xFF\xFF" "/" "/./" From 847e537e864a879e46ddd4c71953016f6fe7e093 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:46:55 -0700 Subject: [PATCH 05/10] test: Add unit test for UTF8 extra-field handling --- fuzz/corpus/fuzz_read/chinese.zip | Bin 0 -> 22982 bytes src/read.rs | 8 ++++++++ tests/data/chinese.zip | Bin 0 -> 22982 bytes 3 files changed, 8 insertions(+) create mode 100644 fuzz/corpus/fuzz_read/chinese.zip create mode 100644 tests/data/chinese.zip diff --git a/fuzz/corpus/fuzz_read/chinese.zip b/fuzz/corpus/fuzz_read/chinese.zip new file mode 100644 index 0000000000000000000000000000000000000000..8fcd465d32c0b5e55c77f41d60d8897ed8fe06a8 GIT binary patch literal 22982 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 Date: Sun, 2 Jun 2024 18:53:19 -0700 Subject: [PATCH 06/10] chore: Add feature gate for new unit test --- src/read.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/read.rs b/src/read.rs index c04dc660..119f51ba 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1786,6 +1786,7 @@ mod test { } #[test] + #[cfg(feature = "_deflate-any")] fn test_utf8_extra_field() { let mut v = Vec::new(); v.extend_from_slice(include_bytes!("../tests/data/chinese.zip")); From 9218599b40ba6df7d7d97aa9fb0c82183241487e Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:16:55 -0700 Subject: [PATCH 07/10] chore: Fix a fuzz failure by using checked_sub --- src/extra_fields/zipinfo_utf8.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/extra_fields/zipinfo_utf8.rs b/src/extra_fields/zipinfo_utf8.rs index 7644b5f8..7119bbfb 100644 --- a/src/extra_fields/zipinfo_utf8.rs +++ b/src/extra_fields/zipinfo_utf8.rs @@ -32,8 +32,10 @@ impl UnicodeExtraField { reader.read_exact(&mut [0u8])?; let crc32 = reader.read_u32_le()?; - let mut content = - vec![0u8; len as usize - size_of::() - size_of::()].into_boxed_slice(); + let content_len = (len as usize) + .checked_sub(size_of::() + size_of::()) + .ok_or(ZipError::InvalidArchive("Unicode extra field is too small"))?; + let mut content = vec![0u8; content_len].into_boxed_slice(); reader.read_exact(&mut content)?; Ok(Self { crc32, content }) } From eacc320fe075ea423f5b734c958fc35baa2aecf7 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:48:21 -0700 Subject: [PATCH 08/10] chore: Add check for wrong-length blocks, and incorporate fixed-size requirement into the trait name --- src/read.rs | 2 +- src/read/stream.rs | 2 +- src/spec.rs | 16 +++++++++------- src/types.rs | 6 +++--- src/write.rs | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/read.rs b/src/read.rs index 119f51ba..0ab5acd2 100644 --- a/src/read.rs +++ b/src/read.rs @@ -8,7 +8,7 @@ use crate::crc32::Crc32Reader; use crate::extra_fields::{ExtendedTimestamp, ExtraField}; use crate::read::zip_archive::Shared; use crate::result::{ZipError, ZipResult}; -use crate::spec::{self, Block}; +use crate::spec::{self, FixedSizeBlock}; use crate::types::{ AesMode, AesVendorVersion, DateTime, System, ZipCentralEntryBlock, ZipFileData, ZipLocalEntryBlock, diff --git a/src/read/stream.rs b/src/read/stream.rs index d87e22fc..8f2ffa0c 100644 --- a/src/read/stream.rs +++ b/src/read/stream.rs @@ -6,7 +6,7 @@ use super::{ central_header_to_zip_file_inner, read_zipfile_from_stream, ZipCentralEntryBlock, ZipError, ZipFile, ZipFileData, ZipResult, }; -use crate::spec::Block; +use crate::spec::FixedSizeBlock; /// Stream decoder for zip. #[derive(Debug)] diff --git a/src/spec.rs b/src/spec.rs index 8738a7bd..387259fd 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -97,7 +97,7 @@ impl ExtraFieldMagic { pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64; pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize; -pub(crate) trait Block: Sized + Copy { +pub(crate) trait FixedSizeBlock: Sized + Copy { const MAGIC: Magic; fn magic(self) -> Magic; @@ -113,8 +113,10 @@ pub(crate) trait Block: Sized + Copy { Ok(block) } - fn deserialize(block: &[u8]) -> Self { - assert_eq!(block.len(), mem::size_of::()); + fn deserialize(block: &[u8]) -> ZipResult { + if block.len() != mem::size_of::() { + return Err(ZipError::InvalidArchive("Block is wrong size")); + } let block_ptr: *const Self = block.as_ptr().cast(); unsafe { block_ptr.read() } } @@ -212,7 +214,7 @@ pub(crate) struct Zip32CDEBlock { pub zip_file_comment_length: u16, } -impl Block for Zip32CDEBlock { +impl FixedSizeBlock for Zip32CDEBlock { const MAGIC: Magic = Magic::CENTRAL_DIRECTORY_END_SIGNATURE; #[inline(always)] @@ -391,7 +393,7 @@ pub(crate) struct Zip64CDELocatorBlock { pub number_of_disks: u32, } -impl Block for Zip64CDELocatorBlock { +impl FixedSizeBlock for Zip64CDELocatorBlock { const MAGIC: Magic = Magic::ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE; #[inline(always)] @@ -467,7 +469,7 @@ pub(crate) struct Zip64CDEBlock { pub central_directory_offset: u64, } -impl Block for Zip64CDEBlock { +impl FixedSizeBlock for Zip64CDEBlock { const MAGIC: Magic = Magic::ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE; fn magic(self) -> Magic { @@ -708,7 +710,7 @@ mod test { pub file_name_length: u16, } - impl Block for TestBlock { + impl FixedSizeBlock for TestBlock { const MAGIC: Magic = Magic::literal(0x01111); fn magic(self) -> Magic { diff --git a/src/types.rs b/src/types.rs index 181592e6..f4d17acc 100644 --- a/src/types.rs +++ b/src/types.rs @@ -11,7 +11,7 @@ use std::sync::{Arc, OnceLock}; use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; use crate::result::{ZipError, ZipResult}; -use crate::spec::{self, Block}; +use crate::spec::{self, FixedSizeBlock}; pub(crate) mod ffi { pub const S_IFDIR: u32 = 0o0040000; @@ -891,7 +891,7 @@ pub(crate) struct ZipCentralEntryBlock { pub offset: u32, } -impl Block for ZipCentralEntryBlock { +impl FixedSizeBlock for ZipCentralEntryBlock { const MAGIC: spec::Magic = spec::Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE; #[inline(always)] @@ -938,7 +938,7 @@ pub(crate) struct ZipLocalEntryBlock { pub extra_field_length: u16, } -impl Block for ZipLocalEntryBlock { +impl FixedSizeBlock for ZipLocalEntryBlock { const MAGIC: spec::Magic = spec::Magic::LOCAL_FILE_HEADER_SIGNATURE; #[inline(always)] diff --git a/src/write.rs b/src/write.rs index 151258a8..acf9ac98 100644 --- a/src/write.rs +++ b/src/write.rs @@ -5,7 +5,7 @@ use crate::aes::AesWriter; use crate::compression::CompressionMethod; use crate::read::{find_content, Config, ZipArchive, ZipFile, ZipFileReader}; use crate::result::{ZipError, ZipResult}; -use crate::spec::{self, Block}; +use crate::spec::{self, FixedSizeBlock}; #[cfg(feature = "aes-crypto")] use crate::types::AesMode; use crate::types::{ From 2725416c0d4564f92cf12d3bbc0b1901f9ab856f Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:00:44 -0700 Subject: [PATCH 09/10] chore: Fix a bug and inline `deserialize` for safety --- src/spec.rs | 30 ++++++++++++++---------------- src/types.rs | 5 +++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/spec.rs b/src/spec.rs index 387259fd..22fb7fd5 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -102,23 +102,19 @@ pub(crate) trait FixedSizeBlock: Sized + Copy { fn magic(self) -> Magic; - const ERROR: ZipError; + const WRONG_MAGIC_ERROR: ZipError; /* TODO: use smallvec? */ fn interpret(bytes: Box<[u8]>) -> ZipResult { - let block = Self::deserialize(&bytes).from_le(); - if block.magic() != Self::MAGIC { - return Err(Self::ERROR); - } - Ok(block) - } - - fn deserialize(block: &[u8]) -> ZipResult { - if block.len() != mem::size_of::() { + if bytes.len() != mem::size_of::() { return Err(ZipError::InvalidArchive("Block is wrong size")); } - let block_ptr: *const Self = block.as_ptr().cast(); - unsafe { block_ptr.read() } + let block_ptr: *const Self = bytes.as_ptr().cast(); + let block = unsafe { block_ptr.read() }.from_le(); + if block.magic() != Self::MAGIC { + return Err(Self::WRONG_MAGIC_ERROR); + } + Ok(block) } #[allow(clippy::wrong_self_convention)] @@ -222,7 +218,8 @@ impl FixedSizeBlock for Zip32CDEBlock { self.magic } - const ERROR: ZipError = ZipError::InvalidArchive("Invalid digital signature header"); + const WRONG_MAGIC_ERROR: ZipError = + ZipError::InvalidArchive("Invalid digital signature header"); to_and_from_le![ (magic, Magic), @@ -401,7 +398,7 @@ impl FixedSizeBlock for Zip64CDELocatorBlock { self.magic } - const ERROR: ZipError = + const WRONG_MAGIC_ERROR: ZipError = ZipError::InvalidArchive("Invalid zip64 locator digital signature header"); to_and_from_le![ @@ -476,7 +473,8 @@ impl FixedSizeBlock for Zip64CDEBlock { self.magic } - const ERROR: ZipError = ZipError::InvalidArchive("Invalid digital signature header"); + const WRONG_MAGIC_ERROR: ZipError = + ZipError::InvalidArchive("Invalid digital signature header"); to_and_from_le![ (magic, Magic), @@ -717,7 +715,7 @@ mod test { self.magic } - const ERROR: ZipError = ZipError::InvalidArchive("unreachable"); + const WRONG_MAGIC_ERROR: ZipError = ZipError::InvalidArchive("unreachable"); to_and_from_le![(magic, Magic), (file_name_length, u16)]; } diff --git a/src/types.rs b/src/types.rs index f4d17acc..25e5df06 100644 --- a/src/types.rs +++ b/src/types.rs @@ -899,7 +899,8 @@ impl FixedSizeBlock for ZipCentralEntryBlock { self.magic } - const ERROR: ZipError = ZipError::InvalidArchive("Invalid Central Directory header"); + const WRONG_MAGIC_ERROR: ZipError = + ZipError::InvalidArchive("Invalid Central Directory header"); to_and_from_le![ (magic, spec::Magic), @@ -946,7 +947,7 @@ impl FixedSizeBlock for ZipLocalEntryBlock { self.magic } - const ERROR: ZipError = ZipError::InvalidArchive("Invalid local file header"); + const WRONG_MAGIC_ERROR: ZipError = ZipError::InvalidArchive("Invalid local file header"); to_and_from_le![ (magic, spec::Magic), From 97245ad68d79b56844d45211806e3ab5d3ec67c1 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:04:15 -0700 Subject: [PATCH 10/10] chore: Fix a new Clippy warning --- src/read.rs | 2 +- src/spec.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/read.rs b/src/read.rs index 0ab5acd2..7dc60a61 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1541,7 +1541,7 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult return Err(ZipError::InvalidArchive("Invalid local file header")), } - let block = ZipLocalEntryBlock::interpret(block)?; + let block = ZipLocalEntryBlock::interpret(&block)?; let mut result = ZipFileData::from_local_block(block, reader)?; diff --git a/src/spec.rs b/src/spec.rs index 22fb7fd5..8a6feff0 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -105,7 +105,7 @@ pub(crate) trait FixedSizeBlock: Sized + Copy { const WRONG_MAGIC_ERROR: ZipError; /* TODO: use smallvec? */ - fn interpret(bytes: Box<[u8]>) -> ZipResult { + fn interpret(bytes: &[u8]) -> ZipResult { if bytes.len() != mem::size_of::() { return Err(ZipError::InvalidArchive("Block is wrong size")); } @@ -123,7 +123,7 @@ pub(crate) trait FixedSizeBlock: Sized + Copy { fn parse(reader: &mut T) -> ZipResult { let mut block = vec![0u8; mem::size_of::()].into_boxed_slice(); reader.read_exact(&mut block)?; - Self::interpret(block) + Self::interpret(&block) } fn encode(self) -> Box<[u8]> {