Skip to content
Open
Show file tree
Hide file tree
Changes from 107 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
6572884
add
Its-Just-Nans May 4, 2026
5d2c43b
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans May 4, 2026
15248f5
remove useless compression_method in settings
Its-Just-Nans May 4, 2026
b5f6d56
fmt
Its-Just-Nans May 4, 2026
a61a694
add
Its-Just-Nans May 4, 2026
4ec7c11
push
Its-Just-Nans May 4, 2026
f35a6ec
Merge branch 'master' into rewrite-aes-settings
Its-Just-Nans May 4, 2026
1f5aa77
fix
Its-Just-Nans May 4, 2026
0c3ea80
fix
Its-Just-Nans May 4, 2026
4e28a55
Merge branch 'rewrite-aes-settings' into rewrite-aes-settings-2
Its-Just-Nans May 4, 2026
20c8e19
add
Its-Just-Nans May 4, 2026
67b3de2
cleanup
Its-Just-Nans May 4, 2026
a0a9c95
revert
Its-Just-Nans May 4, 2026
e86d776
fix variable
Its-Just-Nans May 4, 2026
e38826f
better cleanup
Its-Just-Nans May 4, 2026
2299a76
add comment
Its-Just-Nans May 4, 2026
7800212
refactor: change the vendor version
Its-Just-Nans May 4, 2026
66cdb81
allow for stream with AES
Its-Just-Nans May 4, 2026
25d789b
Merge branch 'master' into rewrite-aes-settings-2
Its-Just-Nans May 4, 2026
72b2a30
Merge branch 'rewrite-aes-settings-2' into rewrite-aes-settings-3
Its-Just-Nans May 4, 2026
7363824
sync
Its-Just-Nans May 4, 2026
f466e9a
Merge branch 'rewrite-aes-settings-2' into rewrite-aes-settings-3
Its-Just-Nans May 4, 2026
c1cdccf
fix clippy
Its-Just-Nans May 4, 2026
3d6f36b
Merge branch 'rewrite-aes-settings-2' into rewrite-aes-settings-3
Its-Just-Nans May 4, 2026
cf941b1
fmt
Its-Just-Nans May 4, 2026
ab1dc08
cleanup
Its-Just-Nans May 4, 2026
d7b8ce0
Merge branch 'master' into rewrite-aes-settings-3
Its-Just-Nans May 5, 2026
b357072
cargo clippy
Its-Just-Nans May 5, 2026
74c5031
cleanup
Its-Just-Nans May 5, 2026
7b13674
fix clippy
Its-Just-Nans May 5, 2026
2ea512f
Merge branch 'rewrite-aes-settings-4' into rewrite-extra-fields
Its-Just-Nans May 5, 2026
f97b093
Merge branch 'master' into rewrite-aes-settings-4
Its-Just-Nans May 5, 2026
6dc3243
add
Its-Just-Nans May 5, 2026
95348bf
sync
Its-Just-Nans May 5, 2026
37a16f7
Merge branch 'rewrite-aes-settings-4' of github.com:zip-rs/zip2 into …
Its-Just-Nans May 5, 2026
f4a5778
Merge branch 'rewrite-aes-settings-4' into rewrite-extra-fields
Its-Just-Nans May 5, 2026
11686f8
fmt
Its-Just-Nans May 5, 2026
6189e6b
refactor: update some extra field
Its-Just-Nans May 5, 2026
98f6055
Update src/write.rs
Its-Just-Nans May 5, 2026
ee55cd3
Update src/write.rs
Its-Just-Nans May 5, 2026
871baa3
Update src/write.rs
Pr0methean May 5, 2026
6264c79
cleanup gemini
Its-Just-Nans May 6, 2026
a77dc58
remove magic
Its-Just-Nans May 6, 2026
9e547ce
Merge branch 'master' into update-extra-field
Its-Just-Nans May 7, 2026
e2fd83f
add test update_aes
Its-Just-Nans May 7, 2026
bb14ec5
refactor: move to format directory
Its-Just-Nans May 7, 2026
9aa4770
fix some docs
Its-Just-Nans May 7, 2026
778506c
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans May 7, 2026
b95254c
Merge branch 'refactor-zip-format' into rewrite-extra-fields
Its-Just-Nans May 7, 2026
dc75dc2
fmt
Its-Just-Nans May 7, 2026
3391ebe
cargo fmrt
Its-Just-Nans May 7, 2026
e534c95
Merge branch 'refactor-zip-format' into rewrite-extra-fields
Its-Just-Nans May 7, 2026
9fee34c
sync
Its-Just-Nans May 7, 2026
0916719
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans May 9, 2026
112cb87
update imports
Its-Just-Nans May 9, 2026
86e3eb7
rewrite a little
Its-Just-Nans May 11, 2026
50c794a
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans May 11, 2026
7dac85a
more change
Its-Just-Nans May 11, 2026
2a24162
fmt
Its-Just-Nans May 11, 2026
ad30a8f
add more code
Its-Just-Nans May 11, 2026
fb72f74
update
Its-Just-Nans May 11, 2026
d247b48
fix
Its-Just-Nans May 11, 2026
4e53592
refactor: change extra_data to extra_fields
Its-Just-Nans May 11, 2026
c557aa3
add arbitrary
Its-Just-Nans May 11, 2026
d2a6903
fix
Its-Just-Nans May 11, 2026
dff9f14
chang
Its-Just-Nans May 11, 2026
d137d10
remove ignore
Its-Just-Nans May 11, 2026
6a94280
refactor: better code for next step
Its-Just-Nans May 11, 2026
16e4fbd
clippy
Its-Just-Nans May 11, 2026
82a4cb1
refactor: change zip64 parsing
Its-Just-Nans May 11, 2026
5446827
Merge branch 'refactor-zip64' into rewrite-extra-fields
Its-Just-Nans May 11, 2026
4798441
Merge branch 'refactor-extra-data' into rewrite-extra-fields
Its-Just-Nans May 11, 2026
f2acd59
cleanup
Its-Just-Nans May 11, 2026
6de54db
more progress & cargo fmt
Its-Just-Nans May 11, 2026
33a15ba
updat
Its-Just-Nans May 12, 2026
7a1b44b
add deprecated
Its-Just-Nans May 12, 2026
2f61a2c
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans May 12, 2026
89f3a48
update
Its-Just-Nans May 13, 2026
db8637a
update
Its-Just-Nans May 13, 2026
901eff6
add
Its-Just-Nans May 13, 2026
2f81cff
now I need to comply with tests
Its-Just-Nans May 13, 2026
4e0700e
more
Its-Just-Nans May 13, 2026
4ca9ca9
cleanup
Its-Just-Nans May 13, 2026
b49d565
apply the extra fields
Its-Just-Nans May 13, 2026
93312d6
update
Its-Just-Nans May 14, 2026
95f9f7a
add zip
Its-Just-Nans May 14, 2026
2802019
handle zip64
Its-Just-Nans May 14, 2026
da61384
write zip64
Its-Just-Nans May 14, 2026
9c5d75b
fix some tests
Its-Just-Nans May 14, 2026
da49f39
debug and fix some tests
Its-Just-Nans May 14, 2026
3a8a582
remove old NoOp
Its-Just-Nans May 14, 2026
c22f612
fix zip64 and cleanup
Its-Just-Nans May 15, 2026
f21e1be
Update src/extra_fields/mod.rs
Its-Just-Nans May 18, 2026
8e8bc6e
Update src/extra_fields/mod.rs
Its-Just-Nans May 18, 2026
7e82dbe
fix: update
Its-Just-Nans May 19, 2026
d68d57f
Merge branch 'master' into refactor-extra-data
Its-Just-Nans May 19, 2026
edd138d
Merge branch 'master' into refactor-extra-data
Its-Just-Nans Jun 7, 2026
7cf479a
Merge branch 'refactor-extra-data' into rewrite-extra-fields
Its-Just-Nans Jun 7, 2026
ef4bcc3
Merge branch 'master' into refactor-extra-data
Its-Just-Nans Jun 18, 2026
528357e
fix: move to CustomExtraField
Its-Just-Nans Jun 18, 2026
5b05880
merge update
Its-Just-Nans Jun 21, 2026
b1b6bff
add comment
Its-Just-Nans Jun 21, 2026
58eefb7
Merge branch 'refactor-extra-data' into rewrite-extra-fields
Its-Just-Nans Jun 22, 2026
5be419f
merge again
Its-Just-Nans Jun 22, 2026
032a324
add test
Its-Just-Nans Jun 26, 2026
e136db3
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans Jul 8, 2026
a310092
little cleanup
Its-Just-Nans Jul 8, 2026
e89633d
return None when empty
Its-Just-Nans Jul 8, 2026
9ac374d
fix import
Its-Just-Nans Jul 8, 2026
f2536cf
better fix because Vec is no alloc by default
Its-Just-Nans Jul 8, 2026
693636c
use is_err()
Its-Just-Nans Jul 8, 2026
ea6f407
add cfg attr
Its-Just-Nans Jul 8, 2026
9d360c3
add not
Its-Just-Nans Jul 8, 2026
6b70893
add comment
Its-Just-Nans Jul 8, 2026
85fe3d5
fix: some gemini
Its-Just-Nans Jul 9, 2026
7dbe6cd
handle too long extra field when starting entry
Its-Just-Nans Jul 9, 2026
fabe9ff
fix: typos
Its-Just-Nans Jul 9, 2026
0dabe17
Merge branch 'master' into rewrite-extra-fields
Its-Just-Nans Jul 11, 2026
2eac315
fix: add the filtering of the extra fields
Its-Just-Nans Jul 23, 2026
b3c8ea5
rename
Its-Just-Nans Jul 23, 2026
e1aa874
use ExtraFields::parse
Its-Just-Nans Jul 23, 2026
e169694
chore: typo
Its-Just-Nans Jul 23, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ target
\.idea/
/fuzz_read/out/
/fuzz_write/out/
zipdetails
Comment thread
Its-Just-Nans marked this conversation as resolved.
27 changes: 14 additions & 13 deletions src/extra_fields/aex_encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ use crate::result::{ZipError, ZipResult, invalid, invalid_archive_const};
use crate::types::AesVendorVersion;
use crate::unstable::LittleEndianReadExt;

#[derive(Copy, Clone)]
#[repr(packed, C)]
pub(crate) struct AexEncryption {
pub(crate) version: u16,
aes_mode: u8,
compression_method: u16,
#[derive(Copy, Clone, Debug)]
pub struct AexEncryption {
pub(crate) aes_vendor_version: AesVendorVersion,
pub(crate) aes_mode: AesMode,
pub(crate) compression_method: CompressionMethod,
pub(crate) aes_extra_field_start: Option<usize>,
}

impl AexEncryption {
Expand All @@ -31,14 +31,15 @@ impl AexEncryption {

#[inline]
pub(crate) fn new(
version: AesVendorVersion,
aes_vendor_version: AesVendorVersion,
aes_mode: AesMode,
compression_method: CompressionMethod,
) -> Self {
Self {
version: version.as_u16(),
aes_mode: aes_mode.as_u8(),
compression_method: compression_method.serialize_to_u16(),
aes_vendor_version,
aes_mode,
compression_method,
aes_extra_field_start: None,
}
}

Expand All @@ -50,10 +51,10 @@ impl AexEncryption {
}

pub fn write_data<T: Write>(self, writer: &mut T) -> ZipResult<()> {
writer.write_all(&u16::to_le_bytes(self.version))?;
writer.write_all(&self.aes_vendor_version.as_u16().to_le_bytes())?;
writer.write_all(&u16::to_le_bytes(Self::VENDOR_ID))?;
writer.write_all(&u8::to_le_bytes(self.aes_mode))?;
writer.write_all(&u16::to_le_bytes(self.compression_method))?;
writer.write_all(&self.aes_mode.as_u8().to_le_bytes())?;
writer.write_all(&self.compression_method.serialize_to_u16().to_le_bytes())?;
Ok(())
}

Expand Down
315 changes: 315 additions & 0 deletions src/extra_fields/extra_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
//! Code related to the `ExtraField` enum

use crate::extra_fields::AexEncryption;
use crate::extra_fields::CustomExtraField;
use crate::extra_fields::ExtendedTimestamp;
use crate::extra_fields::Ntfs;
use crate::extra_fields::UnicodeExtraField;
use crate::extra_fields::UsedExtraField;
use crate::extra_fields::Zip64ExtendedInformation;
use crate::format::flags::ZipFlags;
use crate::result::ZipResult;
use crate::result::invalid;
use crate::spec::BlockGetter;
use crate::types::ZipFileData;
use crate::unstable::LittleEndianReadExt;
use core::mem;
use std::io::ErrorKind;
use std::io::{Cursor, Read, Write};

/// contains one extra field
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ExtraField {
/// NTFS extra field
Ntfs(Ntfs),

/// extended timestamp, as described in <https://libzip.org/specifications/extrafld.txt>
ExtendedTimestamp(ExtendedTimestamp),

/// AeX Encryption
AeXEncryption(AexEncryption),
/// Zip64 Information
Zip64ExtendedInformation {
/// uncompressed size
uncompressed_size: Option<u64>,
/// compressed size
compressed_size: Option<u64>,
/// header start
header_start: Option<u64>,
},
/// Unicode Comment
UnicodeComment(UnicodeExtraField),
/// UnicodePath
UnicodePath(UnicodeExtraField),
/// Data Stream Alignment
DataStreamAlignment(u64),
/// Custom extra field
Custom(CustomExtraField),
}

/// Extra fields list
#[derive(Debug, Clone, Default)]
pub struct ExtraFields {
pub(crate) inner: Vec<ExtraField>,
}

impl ExtraFields {
pub(crate) fn parse<B: BlockGetter>(buff: &[u8], block: &B) -> ZipResult<Self> {
let mut reader = Cursor::new(buff);
let mut extra_fields = Vec::new();
while (reader.position() as usize) < buff.len() {
let parsed_extra_field = ExtraField::parse(&mut reader, block)?;
let Some(parsed_extra_field) = parsed_extra_field else {
break;
};
extra_fields.push(parsed_extra_field);
}
Ok(Self {
inner: extra_fields,
})
}

pub(crate) fn local_extra_fields_mut(&mut self) -> std::slice::IterMut<'_, ExtraField> {
self.inner.iter_mut()
}

pub(crate) fn local_extra_fields(&self) -> std::slice::Iter<'_, ExtraField> {
self.inner.iter()
}

pub(crate) fn central_extra_fields(&self) -> std::slice::Iter<'_, ExtraField> {
self.inner.iter()
}
Comment thread
Its-Just-Nans marked this conversation as resolved.
Outdated
}

impl ExtraField {
pub(crate) fn parse<R: Read, B: BlockGetter>(
reader: &mut R,
file: &B,
) -> ZipResult<Option<Self>> {
let extra_field_header_id = match reader.read_u16_le() {
Ok(value) => value,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e.into()),
};
let decoded_extra_field = UsedExtraField::try_from(extra_field_header_id);
let len = match decoded_extra_field {
Ok(known_field) => match reader.read_u16_le() {
Ok(len) => len,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
return Err(invalid!("Extra field {} header truncated", known_field));
}
Err(e) => return Err(e.into()),
},
Err(()) => {
match reader.read_u16_le() {
Ok(len) => len,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None), // early return, most likely a padding
Err(_e) => {
// Consume remaining bytes to avoid infinite loop in caller
let mut buf = Vec::new();
let _ = reader.read_to_end(&mut buf);
Comment thread
Its-Just-Nans marked this conversation as resolved.
Outdated
return Ok(None);
}
}
}
};
let parsed_extra_field = match decoded_extra_field {
// Zip64 extended information extra field
Ok(UsedExtraField::Zip64ExtendedInfo) => {
let (new_uncomp, new_comp, new_head) = Zip64ExtendedInformation::parse(
reader,
len,
file.get_uncompressed_size(),
file.get_compressed_size(),
file.get_header_start(),
)?;
ExtraField::Zip64ExtendedInformation {
uncompressed_size: Some(new_uncomp),
compressed_size: Some(new_comp),
header_start: Some(new_head),
}
}
Ok(UsedExtraField::Ntfs) => {
// NTFS extra field
ExtraField::Ntfs(Ntfs::try_from_reader(reader, len)?)
}
Ok(UsedExtraField::AeXEncryption) => {
// AES
let (new_aes_enc, inner_compression) = AexEncryption::parse(reader, len)?;
ExtraField::AeXEncryption(AexEncryption::new(
new_aes_enc.1,
new_aes_enc.0,
inner_compression,
))
}
Ok(UsedExtraField::ExtendedTimestamp) => {
ExtraField::ExtendedTimestamp(ExtendedTimestamp::try_from_reader(reader, len)?)
}
Ok(UsedExtraField::UnicodeComment) => {
// Info-ZIP Unicode Comment Extra Field
// APPNOTE 4.6.8 and https://libzip.org/specifications/extrafld.txt
let unicode = UnicodeExtraField::try_from_reader(reader, len)?;
ExtraField::UnicodeComment(unicode)
}
Ok(UsedExtraField::UnicodePath) => {
// Info-ZIP Unicode Path Extra Field
// APPNOTE 4.6.9 and https://libzip.org/specifications/extrafld.txt
let unicode = UnicodeExtraField::try_from_reader(reader, len)?;
ExtraField::UnicodePath(unicode)
}
_ => {
let mut buf = vec![0u8; len as usize];
if let Err(e) = reader.read_exact(&mut buf) {
if e.kind() == ErrorKind::UnexpectedEof {
return Err(invalid!("Extra field content truncated"));
}
return Err(e.into());
}
ExtraField::Custom(CustomExtraField::new(false, extra_field_header_id, &buf))
// Other fields are ignored
}
};
Ok(Some(parsed_extra_field))
}

pub(crate) fn size(&self, is_local_header: bool) -> usize {
match self {
// Zip64 extended information extra field
ExtraField::Zip64ExtendedInformation {
uncompressed_size,
compressed_size,
header_start,
} => {
let mut size = mem::size_of::<UsedExtraField>() + mem::size_of::<u16>();
if uncompressed_size.is_some() {
size += mem::size_of::<u64>();
}
if compressed_size.is_some() {
size += mem::size_of::<u64>();
}
if !is_local_header && header_start.is_some() {
size += mem::size_of::<u64>();
}
size
}
ExtraField::Ntfs(_ntfs) => {
// NTFS extra field
0
}
ExtraField::AeXEncryption { .. } => AexEncryption::FULL_SIZE,
ExtraField::ExtendedTimestamp(_extended_timestamp) => {
// nothing to do
0
}
ExtraField::UnicodeComment(unicode_comment) => unicode_comment.full_size(),
ExtraField::UnicodePath(unicode_path) => unicode_path.full_size(),
ExtraField::Custom(custom) => custom.len_with_header(),
_ => 0,
}
}

pub(crate) fn write<W: Write>(&self, writer: &mut W, is_local_header: bool) -> ZipResult<()> {
match self {
// Zip64 extended information extra field
ExtraField::Zip64ExtendedInformation {
compressed_size,
uncompressed_size,
header_start,
} => {
let magic = UsedExtraField::Zip64ExtendedInfo.as_u16();
writer.write_all(&magic.to_le_bytes())?;
let size = self.size(is_local_header);
let size = size - mem::size_of::<u16>() - mem::size_of::<u16>();
let size = size as u16;
writer.write_all(&size.to_le_bytes())?;
if let Some(uncomp_size) = uncompressed_size {
writer.write_all(&uncomp_size.to_le_bytes())?;
}
if let Some(comp_size) = compressed_size {
writer.write_all(&comp_size.to_le_bytes())?;
}
if !is_local_header && let Some(head_start) = header_start {
writer.write_all(&head_start.to_le_bytes())?;
}
Comment thread
Its-Just-Nans marked this conversation as resolved.
}
ExtraField::AeXEncryption(aex) => {
aex.write(writer)?;
}
ExtraField::Custom(custom) => {
custom.write(writer)?;
}
ExtraField::UnicodeComment(unicode_comment) => {
let magic = UsedExtraField::UnicodeComment.as_u16();
writer.write_all(&magic.to_le_bytes())?;
unicode_comment.write(writer)?;
}
ExtraField::UnicodePath(unicode_path) => {
let magic = UsedExtraField::UnicodePath.as_u16();
writer.write_all(&magic.to_le_bytes())?;
unicode_path.write(writer)?;
}
_ => {
// nothing to do
}
}
Ok(())
}
}

impl ZipFileData {
pub(crate) fn apply_extra_fields(&mut self, file_name_raw: &mut Vec<u8>) -> ZipResult<()> {
for one_extra_field in &self.extra_fields.inner {
match one_extra_field {
// Zip64 extended information extra field
ExtraField::Zip64ExtendedInformation {
uncompressed_size,
compressed_size,
header_start,
} => {
self.large_file = true;
if let Some(uncomp_size) = *uncompressed_size {
self.uncompressed_size = uncomp_size;
}
if let Some(comp_size) = *compressed_size {
self.compressed_size = comp_size;
}
if let Some(head_start) = *header_start {
self.header_start = head_start;
}
}
ExtraField::AeXEncryption(AexEncryption {
aes_mode,
aes_vendor_version,
compression_method,
..
}) => {
self.aes_mode = Some((*aes_mode, *aes_vendor_version));
self.compression_method = *compression_method;
}
ExtraField::UnicodeComment(unicode)
// Info-ZIP Unicode Comment Extra Field
// APPNOTE 4.6.8 and https://libzip.org/specifications/extrafld.txt
// If the CRC check fails, this Unicode Comment extra field SHOULD be ignored and
// the File Comment field in the header SHOULD be used instead.
if unicode.is_crc32_valid(self.file_comment.as_bytes()) => {
self.file_comment = String::from_utf8(unicode.content.to_vec())?.into();
}
Comment thread
Its-Just-Nans marked this conversation as resolved.
Outdated
ExtraField::UnicodePath(unicode)
// Info-ZIP Unicode Path Extra Field
// APPNOTE 4.6.9 and https://libzip.org/specifications/extrafld.txt
// If the CRC check fails, this UTF-8 Path Extra Field SHOULD be ignored and
// the File Name field in the header SHOULD be used instead.
if unicode.is_crc32_valid(file_name_raw) => {
*file_name_raw = unicode.content.to_vec();
self.flags |= ZipFlags::LanguageEncoding.as_u16();
}
Comment thread
Its-Just-Nans marked this conversation as resolved.
Outdated
_ => {
// nothing to do
}
}
}
Ok(())
}
}
Loading
Loading