Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
79f0d36
use bytes direclty
Its-Just-Nans Apr 11, 2026
f1eb6c4
cargo fmt
Its-Just-Nans Apr 11, 2026
7f4e340
fix
Its-Just-Nans Apr 11, 2026
f6ae0a3
also on read
Its-Just-Nans Apr 12, 2026
3edd51f
add as bytes
Its-Just-Nans Apr 12, 2026
f717f24
fmt
Its-Just-Nans Apr 12, 2026
93a93e3
one alloc filename
Its-Just-Nans Apr 15, 2026
9c5e529
add stuff
Its-Just-Nans Apr 21, 2026
9dc94f8
remove file_name_raw
Its-Just-Nans Apr 21, 2026
29885aa
revert
Its-Just-Nans Apr 21, 2026
6224eb8
update
Its-Just-Nans Apr 21, 2026
e516464
cleanup
Its-Just-Nans Apr 21, 2026
57b1530
Merge branch 'master' into only-file-name
Its-Just-Nans Apr 23, 2026
14d1494
clippy
Its-Just-Nans Apr 23, 2026
116d876
Update src/read.rs
Its-Just-Nans Apr 23, 2026
b2b8190
add
Its-Just-Nans Apr 24, 2026
f16d5f6
Merge branch 'master' into only-file-name
Pr0methean Apr 25, 2026
929dc88
fix name
Its-Just-Nans Apr 25, 2026
d21f52f
bump
Its-Just-Nans Apr 25, 2026
5b719d0
Merge branch 'master' into only-file-name
Its-Just-Nans Apr 25, 2026
374c133
Merge branch 'master' into only-file-name
Its-Just-Nans Apr 27, 2026
a2383ab
Merge branch 'master' into only-file-name
Pr0methean Apr 30, 2026
cd937cc
Rewrite using let-else
Pr0methean Apr 30, 2026
d1fdada
Fix clippy warnings
Pr0methean Apr 30, 2026
d6b3960
style: cargo fmt --all
Pr0methean Apr 30, 2026
500f90c
refactor: Replace Box<[u8]> with Arc<[u8]> so raw and UTF-8 filename …
Pr0methean Apr 30, 2026
6481930
cargo clippy --fix
Pr0methean Apr 30, 2026
a3a0428
cargo fmt --all
Pr0methean Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/extra_fields/extended_timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(&[
Expand All @@ -270,17 +270,17 @@ 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]
fn test_extended_timestamp() {
// 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);

Expand All @@ -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));
}
}
8 changes: 2 additions & 6 deletions src/extra_fields/zipinfo_utf8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down
39 changes: 22 additions & 17 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
Expand Down Expand Up @@ -455,13 +456,13 @@ impl<R: Read + Seek> ZipArchive<R> {
pub(crate) fn central_header_to_zip_file<R: Read + Seek>(
reader: &mut R,
central_directory: &CentralDirectoryInfo,
) -> ZipResult<ZipFileData> {
) -> ZipResult<(ZipFileData, Box<[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,
Expand All @@ -471,12 +472,12 @@ pub(crate) fn central_header_to_zip_file<R: Read + Seek>(
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<R: Read>(reader: &mut R, len: usize) -> ZipResult<Box<[u8]>> {
let mut data = vec![0; len].into_boxed_slice();
fn read_variable_length_byte_field<R: Read>(reader: &mut R, len: usize) -> ZipResult<Vec<u8>> {
let mut data = vec![0; len];
if let Err(e) = reader.read_exact(&mut data) {
if e.kind() == io::ErrorKind::UnexpectedEof {
return Err(invalid!(
Expand All @@ -494,7 +495,7 @@ fn central_header_to_zip_file_inner<R: Read>(
archive_offset: u64,
central_header_start: u64,
block: ZipCentralEntryBlock,
) -> ZipResult<ZipFileData> {
) -> ZipResult<(ZipFileData, Box<[u8]>)> {
let ZipCentralEntryBlock {
// magic,
version_made_by,
Expand All @@ -518,7 +519,7 @@ fn central_header_to_zip_file_inner<R: Read>(

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<str> = if is_utf8 {
Expand All @@ -544,7 +545,6 @@ fn central_header_to_zip_file_inner<R: Read>(
uncompressed_size: uncompressed_size.into(),
flags,
file_name,
file_name_raw,
extra_field: Some(Arc::from(extra_field)),
central_extra_field: None,
file_comment,
Expand All @@ -558,7 +558,7 @@ fn central_header_to_zip_file_inner<R: Read>(
aes_extra_data_start: 0,
extra_fields: Vec::new(),
};
parse_extra_field(&mut result)?;
parse_extra_field(&mut result, &mut file_name_raw)?;

let aes_enabled = result.compression_method == CompressionMethod::AES;
if aes_enabled && result.aes_mode.is_none() {
Expand All @@ -571,10 +571,13 @@ fn central_header_to_zip_file_inner<R: Read>(
.checked_add(archive_offset)
.ok_or(invalid!("Archive header is too large"))?;

Ok(result)
Ok((result, file_name_raw.into()))
}

pub(crate) fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
pub(crate) fn parse_extra_field(
file: &mut ZipFileData,
file_name_raw: &mut Vec<u8>,
) -> 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] {
Expand All @@ -589,7 +592,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;
Expand Down Expand Up @@ -622,6 +626,7 @@ pub(crate) fn parse_single_extra_field<R: Read>(
reader: &mut R,
bytes_already_read: u64,
disallow_zip64: bool,
file_name_raw: &mut Vec<u8>,
) -> ZipResult<bool> {
let kind = match reader.read_u16_le() {
Ok(kind) => kind,
Expand Down Expand Up @@ -699,10 +704,10 @@ pub(crate) fn parse_single_extra_field<R: Read>(
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.file_name = String::from_utf8(file_name.to_vec())?.into_boxed_str();
*file_name_raw = file_name.into_vec();
file.flags |= ZipFlags::LanguageEncoding.as_u16();
}
_ => {
Expand Down Expand Up @@ -800,7 +805,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,
Expand Down
25 changes: 15 additions & 10 deletions src/read/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ impl<R: Read> ZipStreamReader<R> {

// 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
Expand Down Expand Up @@ -133,7 +133,7 @@ pub trait ZipStreamVisitor {

/// Additional metadata for the file.
#[derive(Debug)]
pub struct ZipStreamFileMetadata(ZipFileData);
pub struct ZipStreamFileMetadata(ZipFileData, Box<[u8]>);

impl ZipStreamFileMetadata {
/// Get the name of the file
Expand All @@ -156,7 +156,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.
Expand Down Expand Up @@ -244,9 +244,9 @@ pub fn read_zipfile_from_stream<R: Read>(reader: &mut R) -> ZipResult<Option<Zip

let block = block.from_le();

let mut result = ZipFileData::from_local_block(block, reader)?;
let (mut result, mut file_name_raw) = ZipFileData::from_local_block(block, reader)?;

match parse_extra_field(&mut result) {
match parse_extra_field(&mut result, &mut file_name_raw) {
Ok(..) | Err(ZipError::Io(..)) => {}
Err(e) => return Err(e),
}
Expand All @@ -263,6 +263,7 @@ pub fn read_zipfile_from_stream<R: Read>(reader: &mut R) -> ZipResult<Option<Zip
} = result;

Ok(Some(ZipFile {
file_name_raw: Cow::Owned(file_name_raw),
data: Cow::Owned(result),
reader: make_reader(
compression_method,
Expand Down Expand Up @@ -295,7 +296,7 @@ pub fn read_zipfile_from_stream_with_compressed_size<R: io::Read>(

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() {
Expand All @@ -316,6 +317,7 @@ pub fn read_zipfile_from_stream_with_compressed_size<R: io::Read>(
} = result;

Ok(Some(ZipFile {
file_name_raw: Cow::Owned(file_name_raw),
data: Cow::Owned(result),
reader: make_reader(
compression_method,
Expand Down Expand Up @@ -568,9 +570,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;
}
Expand Down
17 changes: 9 additions & 8 deletions src/read/zip_archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct ZipArchiveMetadata {

#[derive(Debug)]
pub(crate) struct SharedBuilder {
pub(crate) files: Vec<super::ZipFileData>,
pub(crate) files: Vec<(Box<[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.
Expand All @@ -47,8 +47,8 @@ impl SharedBuilder {
zip64_extensible_data_sector: Option<Box<[u8]>>,
) -> 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,
Expand Down Expand Up @@ -200,8 +200,8 @@ impl<R: Read + Seek> ZipArchive<R> {
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 {
Expand Down Expand Up @@ -534,12 +534,13 @@ impl<R: Read + Seek> ZipArchive<R> {
/// Get a contained file by index without decompressing it
pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult<ZipFile<'_, R>> {
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),
})
Expand All @@ -551,12 +552,11 @@ impl<R: Read + Seek> ZipArchive<R> {
file_number: usize,
mut options: ZipReadOptions<'_>,
) -> ZipResult<ZipFile<'_, R>> {
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;
Expand All @@ -583,6 +583,7 @@ impl<R: Read + Seek> ZipArchive<R> {
};

Ok(ZipFile {
file_name_raw: Cow::Borrowed(file_name_raw),
data: Cow::Borrowed(data),
reader: make_reader(
data.compression_method,
Expand Down
Loading
Loading