fix: Extract directory contents on Unix even if the directory doesn't have write permission (https://github.com/zip-rs/zip-old/issues/423)

This commit is contained in:
Chris Hennick 2024-05-10 14:27:25 -07:00
parent e5690877aa
commit 2ea4e5059f
No known key found for this signature in database
GPG key ID: DA47AABA4961C509
4 changed files with 44 additions and 8 deletions

View file

@ -57,6 +57,7 @@ walkdir = "2.5.0"
time = { workspace = true, features = ["formatting", "macros"] }
anyhow = "1"
clap = { version = "=4.4.18", features = ["derive"] }
tempdir = "0.3.7"
[features]
aes-crypto = ["aes", "constant_time_eq", "hmac", "pbkdf2", "sha1", "rand", "zeroize"]

View file

@ -13,6 +13,7 @@ use crate::types::{AesMode, AesVendorVersion, DateTime, System, ZipFileData};
use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator};
use indexmap::IndexMap;
use std::borrow::Cow;
use std::fs::{create_dir_all, Permissions};
use std::io::{self, copy, prelude::*, sink};
use std::ops::Deref;
use std::path::{Path, PathBuf};
@ -661,7 +662,8 @@ impl<R: Read + Seek> ZipArchive<R> {
/// may be left on disk.
pub fn extract<P: AsRef<Path>>(&mut self, directory: P) -> ZipResult<()> {
use std::fs;
#[cfg(unix)]
let mut files_by_unix_mode = Vec::new();
for i in 0..self.len() {
let mut file = self.by_index(i)?;
let filepath = file
@ -671,25 +673,46 @@ impl<R: Read + Seek> ZipArchive<R> {
let outpath = directory.as_ref().join(filepath);
if file.is_dir() {
fs::create_dir_all(&outpath)?;
Self::make_writable_dir_all(&outpath)?;
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(p)?;
}
Self::make_writable_dir_all(p)?;
}
let mut outfile = fs::File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}
// Get and Set permissions
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
// Check for real permissions, which we'll set in a second pass
if let Some(mode) = file.unix_mode() {
fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
files_by_unix_mode.push((outpath.clone(), mode));
}
}
}
#[cfg(unix)]
{
use std::cmp::Reverse;
use std::os::unix::fs::PermissionsExt;
if files_by_unix_mode.len() > 1 {
// Ensure we update children's permissions before making a parent unwritable
files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone()));
}
for (path, mode) in files_by_unix_mode.into_iter() {
fs::set_permissions(&path, fs::Permissions::from_mode(mode))?;
}
}
Ok(())
}
fn make_writable_dir_all<T: AsRef<Path>>(outpath: T) -> Result<(), ZipError> {
create_dir_all(outpath.as_ref())?;
#[cfg(unix)]
{
// Dirs must be writable until all normal files are extracted
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(outpath.as_ref(), Permissions::from_mode(0o755))?;
}
Ok(())
}

Binary file not shown.

12
tests/repro_old423.rs Normal file
View file

@ -0,0 +1,12 @@
use std::env::temp_dir;
use std::io;
use zip::result::ZipResult;
use zip::ZipArchive;
#[test]
fn repro_old423() -> ZipResult<()> {
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("data/lin-ub_iwd-v11.zip"));
let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
archive.extract(temp_dir())
}