Skip to content

Commit 08cef35

Browse files
refactor files_by_unix_mode
1 parent 98de40b commit 08cef35

File tree

1 file changed

+53
-36
lines changed

1 file changed

+53
-36
lines changed

src/read.rs

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator
2020
use crate::ZIP64_BYTES_THR;
2121
use indexmap::IndexMap;
2222
use std::borrow::Cow;
23+
use std::collections::BTreeMap;
2324
use std::ffi::OsStr;
2425
use std::fs::create_dir_all;
2526
use std::io::{self, copy, prelude::*, sink, SeekFrom};
@@ -536,6 +537,33 @@ impl<'a> TryFrom<&'a CentralDirectoryEndInfo> for CentralDirectoryInfo {
536537
}
537538
}
538539

540+
/// Store all entries which specify a numeric "mode" which is familiar to POSIX operating systems.
541+
#[cfg(unix)]
542+
#[derive(Default, Debug)]
543+
struct FilesByUnixMode {
544+
map: BTreeMap<PathBuf, u32>,
545+
}
546+
547+
#[cfg(unix)]
548+
impl FilesByUnixMode {
549+
pub fn add_mode(&mut self, path: PathBuf, mode: u32) {
550+
// We don't print a warning or consider it remotely out of the ordinary to receive two
551+
// separate modes for the same path: just take the later one.
552+
let _ = self.map.insert(path, mode);
553+
}
554+
555+
// Child nodes will be sorted later lexicographically, so reversing the order puts them first.
556+
pub fn all_perms_with_children_first(
557+
self,
558+
) -> impl IntoIterator<Item = (PathBuf, std::fs::Permissions)> {
559+
use std::os::unix::fs::PermissionsExt;
560+
self.map
561+
.into_iter()
562+
.rev()
563+
.map(|(p, m)| (p, std::fs::Permissions::from_mode(m)))
564+
}
565+
}
566+
539567
impl<R> ZipArchive<R> {
540568
pub(crate) fn from_finalized_writer(
541569
files: IndexMap<Box<str>, ZipFileData>,
@@ -885,33 +913,30 @@ impl<R: Read + Seek> ZipArchive<R> {
885913
.transpose()?;
886914

887915
#[cfg(unix)]
888-
let mut files_by_unix_mode = Vec::new();
916+
let mut files_by_unix_mode = FilesByUnixMode::default();
889917

890918
for i in 0..self.len() {
891919
let mut file = self.by_index(i)?;
892920

893921
let mut outpath = directory.clone();
922+
/* TODO: the control flow of this method call and subsequent expectations about the
923+
* values in this loop is extremely difficult to follow. It also appears to
924+
* perform a nested loop upon extracting every single file entry? Why does it
925+
* accept two arguments that point to the same directory path, one mutable? */
894926
file.safe_prepare_path(directory.as_ref(), &mut outpath, root_dir.as_ref())?;
895927

896928
let mut symlink_target: Option<Vec<u8>> = None;
897-
cfg_if! {
898-
if #[cfg(any(unix, windows))] {
899-
if file.is_symlink() {
900-
let mut target = Vec::with_capacity(file.size() as usize);
901-
file.read_to_end(&mut target)?;
902-
symlink_target = Some(target);
903-
}
904-
} else {
905-
if file.is_symlink() {
906-
todo!("no support for symlinks outside of unix or windows platforms");
907-
}
908-
}
929+
#[cfg(any(unix, windows))]
930+
if file.is_symlink() {
931+
let mut target = Vec::with_capacity(file.size() as usize);
932+
file.read_to_end(&mut target)?;
933+
symlink_target = Some(target);
909934
}
935+
910936
if symlink_target.is_none() && file.is_dir() {
911937
crate::read::make_writable_dir_all(&outpath)?;
912938
continue;
913939
}
914-
915940
if let Some(target) = symlink_target {
916941
drop(file);
917942
make_symlink(&outpath, &target, &self.shared.files)?;
@@ -920,36 +945,28 @@ impl<R: Read + Seek> ZipArchive<R> {
920945
let mut outfile = fs::File::create(&outpath)?;
921946

922947
io::copy(&mut file, &mut outfile)?;
948+
949+
// Check for real permissions, which we'll set in a second pass.
923950
#[cfg(unix)]
924-
{
925-
// Check for real permissions, which we'll set in a second pass
926-
if let Some(mode) = file.unix_mode() {
927-
files_by_unix_mode.push((outpath.clone(), mode));
928-
}
951+
if let Some(mode) = file.unix_mode() {
952+
files_by_unix_mode.add_mode(outpath, mode);
929953
}
954+
955+
// Set original timestamp.
930956
#[cfg(feature = "chrono")]
931-
{
932-
// Set original timestamp.
933-
if let Some(last_modified) = file.last_modified() {
934-
if let Some(t) = datetime_to_systemtime(&last_modified) {
935-
outfile.set_modified(t)?;
936-
}
957+
if let Some(last_modified) = file.last_modified() {
958+
if let Some(t) = datetime_to_systemtime(&last_modified) {
959+
outfile.set_modified(t)?;
937960
}
938961
}
939962
}
940-
#[cfg(unix)]
941-
{
942-
use std::cmp::Reverse;
943-
use std::os::unix::fs::PermissionsExt;
944963

945-
if files_by_unix_mode.len() > 1 {
946-
// Ensure we update children's permissions before making a parent unwritable
947-
files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone()));
948-
}
949-
for (path, mode) in files_by_unix_mode.into_iter() {
950-
fs::set_permissions(&path, fs::Permissions::from_mode(mode))?;
951-
}
964+
// Ensure we update children's permissions before making a parent unwritable.
965+
#[cfg(unix)]
966+
for (path, perms) in files_by_unix_mode.all_perms_with_children_first() {
967+
std::fs::set_permissions(path, perms)?;
952968
}
969+
953970
Ok(())
954971
}
955972

0 commit comments

Comments
 (0)