From c67630c3afcb2fad65c1b8a31b343450cf1b5c79 Mon Sep 17 00:00:00 2001 From: n4n5 Date: Thu, 28 May 2026 18:21:10 +0200 Subject: [PATCH 1/5] fix: change directory attributes detection --- src/types.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/types.rs b/src/types.rs index 2dedda4e6..6ca286e2c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -26,8 +26,14 @@ pub(crate) use crate::format::aes::{AesMode, AesVendorVersion}; pub(crate) use crate::format::flags::System; pub(crate) mod ffi { + /// Mask + pub const S_IFMT: u32 = 0o170000; + + /// Directory pub const S_IFDIR: u32 = 0o0_040_000; + /// Regular pub const S_IFREG: u32 = 0o0_100_000; + /// Symbolic link pub const S_IFLNK: u32 = 0o0_120_000; } @@ -266,28 +272,35 @@ impl ZipFileData { return None; } let unix_mode = self.external_attributes >> 16; - if unix_mode != 0 { - // If the high 16 bits are non-zero, they probably contain Unix permissions. - // This happens for archives created on Windows by this crate or other tools, - // and is the only way to identify symlinks in such archives. - return Some(unix_mode); - } match self.system { System::Unix => Some(unix_mode), System::Dos => { + // For MS-DOS, the low order byte is the MS-DOS directory attribute byte. + let directory_attributes = self.external_attributes & 0xFFFF; // Interpret MS-DOS directory bit - let mut mode = if 0x10 == (self.external_attributes & 0x10) { + let mut mode = if 0x10 == (directory_attributes & 0x10) { ffi::S_IFDIR | 0o0775 + } else if (unix_mode & ffi::S_IFMT) == ffi::S_IFLNK { + ffi::S_IFLNK | 0o0777 } else { ffi::S_IFREG | 0o0664 }; - if 0x01 == (self.external_attributes & 0x01) { - // Read-only bit; strip write permissions + // Interpret MS-DOS read-only bit + if 0x01 == (directory_attributes & 0x01) { + // strip write permissions for read-only mode &= !0o222; } Some(mode) } - _ => None, + _ => { + if unix_mode != 0 { + // If the high 16 bits are non-zero, they probably contain Unix permissions. + // This happens for archives created on Windows by this crate or other tools, + // and is the only way to identify symlinks in such archives. + return Some(unix_mode); + } + None + }, } } From 58a03517ab6501e9ed2ee51a523d84b2cc5511b7 Mon Sep 17 00:00:00 2001 From: n4n5 Date: Thu, 28 May 2026 18:26:58 +0200 Subject: [PATCH 2/5] cleanup constants --- src/types.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types.rs b/src/types.rs index 6ca286e2c..88000c584 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,14 +27,14 @@ pub(crate) use crate::format::flags::System; pub(crate) mod ffi { /// Mask - pub const S_IFMT: u32 = 0o170000; + pub const S_IFMT: u32 = 0b1111_0000_0000_0000; - /// Directory - pub const S_IFDIR: u32 = 0o0_040_000; /// Regular - pub const S_IFREG: u32 = 0o0_100_000; + pub const S_IFREG: u32 = 0b1000_0000_0000_0000; // 0o0_100_000 + /// Directory + pub const S_IFDIR: u32 = 0b0100_0000_0000_0000; // 0o0_040_000 /// Symbolic link - pub const S_IFLNK: u32 = 0o0_120_000; + pub const S_IFLNK: u32 = 0b1010_0000_0000_0000; // 0o0_120_000 } pub(crate) struct ZipRawValues { From 0248f3e51103c7e06e1e4b2d73cebaf112bb202d Mon Sep 17 00:00:00 2001 From: n4n5 Date: Thu, 28 May 2026 18:38:39 +0200 Subject: [PATCH 3/5] cargo fmt --- src/types.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types.rs b/src/types.rs index 88000c584..b073120ec 100644 --- a/src/types.rs +++ b/src/types.rs @@ -294,13 +294,13 @@ impl ZipFileData { } _ => { if unix_mode != 0 { - // If the high 16 bits are non-zero, they probably contain Unix permissions. - // This happens for archives created on Windows by this crate or other tools, - // and is the only way to identify symlinks in such archives. + // If the high 16 bits are non-zero, they probably contain Unix permissions. + // This happens for archives created on Windows by this crate or other tools, + // and is the only way to identify symlinks in such archives. return Some(unix_mode); } None - }, + } } } From 7bec0023a67f8a1ffac3916a17d0d4dbceeb3de0 Mon Sep 17 00:00:00 2001 From: n4n5 Date: Thu, 28 May 2026 18:38:50 +0200 Subject: [PATCH 4/5] add test from issue --- tests/end_to_end.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs index 7489ad868..2c7147472 100644 --- a/tests/end_to_end.rs +++ b/tests/end_to_end.rs @@ -388,3 +388,41 @@ fn test_can_create_destination() -> zip::result::ZipResult<()> { assert!(dest.path().join("mimetype").exists()); Ok(()) } + +#[test] +fn unix_mode_dos() { + use std::io::Cursor; + use zip::ZipArchive; + // https://github.com/zip-rs/zip2/issues/737 + // unix_mode_0x081A4000.zip + let data = [ + 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x85, + 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x68, 0x65, + 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x4b, 0x01, 0x02, 0x2d, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x85, 0x11, 0x4a, 0x0d, 0x0b, + 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, // file name length + 0x0d, 0x00, // extra field length + 0x00, 0x00, // file comment length + 0x00, 0x00, // disk number start + 0x00, 0x00, // internal file attributes + 0x00, 0x00, // external file attributes + 0x00, 0x40, 0x1a, 0x08, // relative offset of local header + 0x00, 0x00, 0x00, 0x00, // filename: test_file.txt + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, + // end central header + 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x3b, 0x00, 0x00, + 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let mut archive = ZipArchive::new(Cursor::new(data)).unwrap(); + for i in 0..archive.len() { + let file = archive.by_index(i).unwrap(); + assert_eq!( + file.unix_mode(), + Some(33204), + "file {:?} has unix_mode {:?}", + file.name(), + file.unix_mode() + ); + } +} From 7e6adf18bf891bb9c723f21c62ee05e87fbad0ac Mon Sep 17 00:00:00 2001 From: n4n5 Date: Thu, 28 May 2026 20:32:25 +0200 Subject: [PATCH 5/5] fix --- src/types.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/types.rs b/src/types.rs index b073120ec..8b57a7af0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -26,9 +26,6 @@ pub(crate) use crate::format::aes::{AesMode, AesVendorVersion}; pub(crate) use crate::format::flags::System; pub(crate) mod ffi { - /// Mask - pub const S_IFMT: u32 = 0b1111_0000_0000_0000; - /// Regular pub const S_IFREG: u32 = 0b1000_0000_0000_0000; // 0o0_100_000 /// Directory @@ -280,8 +277,6 @@ impl ZipFileData { // Interpret MS-DOS directory bit let mut mode = if 0x10 == (directory_attributes & 0x10) { ffi::S_IFDIR | 0o0775 - } else if (unix_mode & ffi::S_IFMT) == ffi::S_IFLNK { - ffi::S_IFLNK | 0o0777 } else { ffi::S_IFREG | 0o0664 }; @@ -703,7 +698,7 @@ mod tests { external_attributes: (ffi::S_IFLNK | 0o777) << 16, ..ZipFileData::default() }; - assert_eq!(data.unix_mode(), Some(ffi::S_IFLNK | 0o777)); + assert_eq!(data.unix_mode(), Some(ffi::S_IFREG | 0o664)); data.system = System::Unknown; assert_eq!(data.unix_mode(), Some(ffi::S_IFLNK | 0o777));