mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-18 23:50:35 +00:00
fix(zip): handle broken symlinks and improve extraction infomation
deps: update zip to 4.2.0
This commit is contained in:
parent
945ffde551
commit
4479314bc2
108
Cargo.lock
generated
108
Cargo.lock
generated
@ -93,6 +93,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.16"
|
||||
@ -126,12 +135,6 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.63.0"
|
||||
@ -452,9 +455,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "core_affinity"
|
||||
@ -529,6 +532,17 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
@ -570,6 +584,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
@ -611,9 +631,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.0"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-sys",
|
||||
@ -690,8 +710,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.13.3+wasi-0.2.2",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
@ -731,6 +753,12 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@ -786,6 +814,16 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.16.0"
|
||||
@ -1164,27 +1202,14 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.11.0"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"password-hash",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2135,21 +2160,44 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"byteorder",
|
||||
"arbitrary",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"getrandom 0.3.1",
|
||||
"hmac",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -42,7 +42,7 @@ tempfile = "3.10.1"
|
||||
time = { version = "0.3.36", default-features = false }
|
||||
unrar = { version = "0.5.7", optional = true }
|
||||
xz2 = "0.1.7"
|
||||
zip = { version = "0.6.6", default-features = false, features = [
|
||||
zip = { version = "4.2", default-features = false, features = [
|
||||
"time",
|
||||
"aes-crypto",
|
||||
] }
|
||||
@ -76,7 +76,7 @@ test-strategy = "0.4.0"
|
||||
|
||||
[features]
|
||||
default = ["unrar", "use_zlib", "use_zstd_thin", "bzip3"]
|
||||
use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-zlib"]
|
||||
use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-flate2-zlib"]
|
||||
use_zstd_thin = ["zstd/thin"]
|
||||
allow_piped_choice = []
|
||||
|
||||
|
@ -41,9 +41,7 @@ where
|
||||
|
||||
for idx in 0..archive.len() {
|
||||
let mut file = match password {
|
||||
Some(password) => archive
|
||||
.by_index_decrypt(idx, password)?
|
||||
.map_err(|_| zip::result::ZipError::UnsupportedArchive("Password required to decrypt file"))?,
|
||||
Some(password) => archive.by_index_decrypt(idx, password)?,
|
||||
None => archive.by_index(idx)?,
|
||||
};
|
||||
let file_path = match file.enclosed_name() {
|
||||
@ -65,6 +63,9 @@ where
|
||||
info(format!("File {} extracted to \"{}\"", idx, file_path.display()));
|
||||
}
|
||||
fs::create_dir_all(&file_path)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
unix_set_permissions(&file_path, &file)?;
|
||||
}
|
||||
_is_file @ false => {
|
||||
if let Some(path) = file_path.parent() {
|
||||
@ -74,15 +75,6 @@ where
|
||||
}
|
||||
let file_path = strip_cur_dir(file_path.as_path());
|
||||
|
||||
// same reason is in _is_dir: long, often not needed text
|
||||
if !quiet {
|
||||
info(format!(
|
||||
"extracted ({}) {:?}",
|
||||
Bytes::new(file.size()),
|
||||
file_path.display(),
|
||||
));
|
||||
}
|
||||
|
||||
let mode = file.unix_mode();
|
||||
let is_symlink = mode.is_some_and(|mode| mode & 0o170000 == 0o120000);
|
||||
|
||||
@ -90,6 +82,10 @@ where
|
||||
let mut target = String::new();
|
||||
file.read_to_string(&mut target)?;
|
||||
|
||||
if !quiet {
|
||||
info(format!("linking {} -> {}", file_path.display(), target));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(&target, file_path)?;
|
||||
#[cfg(windows)]
|
||||
@ -97,14 +93,21 @@ where
|
||||
} else {
|
||||
let mut output_file = fs::File::create(file_path)?;
|
||||
io::copy(&mut file, &mut output_file)?;
|
||||
}
|
||||
|
||||
set_last_modified_time(&file, file_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
unix_set_permissions(&file_path, &file)?;
|
||||
}
|
||||
|
||||
// same reason is in _is_dir: long, often not needed text
|
||||
if !quiet {
|
||||
info(format!(
|
||||
"extracted ({}) {:?}",
|
||||
Bytes::new(file.size()),
|
||||
file_path.display(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unpacked_files += 1;
|
||||
}
|
||||
@ -136,9 +139,7 @@ where
|
||||
for idx in 0..archive.len() {
|
||||
let file_in_archive = (|| {
|
||||
let zip_result = match password.clone() {
|
||||
Some(password) => archive
|
||||
.by_index_decrypt(idx, &password)?
|
||||
.map_err(|_| zip::result::ZipError::UnsupportedArchive("Password required to decrypt file")),
|
||||
Some(password) => archive.by_index_decrypt(idx, &password),
|
||||
None => archive.by_index(idx),
|
||||
};
|
||||
|
||||
@ -147,7 +148,7 @@ where
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
let path = file.enclosed_name().unwrap_or(&*file.mangled_name()).to_owned();
|
||||
let path = file.enclosed_name().unwrap_or_else(|| file.mangled_name()).to_owned();
|
||||
let is_dir = file.is_dir();
|
||||
|
||||
Ok(FileInArchive { path, is_dir })
|
||||
@ -174,7 +175,7 @@ where
|
||||
let mut writer = zip::ZipWriter::new(writer);
|
||||
// always use ZIP64 to allow compression of files larger than 4GB
|
||||
// the format is widely supported and the extra 20B is negligible in most cases
|
||||
let options = zip::write::FileOptions::default().large_file(true);
|
||||
let options = zip::write::SimpleFileOptions::default().large_file(true);
|
||||
let output_handle = Handle::from_path(output_path);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
@ -286,7 +287,7 @@ where
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn display_zip_comment_if_exists(file: &ZipFile) {
|
||||
fn display_zip_comment_if_exists<R: Read>(file: &ZipFile<'_, R>) {
|
||||
let comment = file.comment();
|
||||
if !comment.is_empty() {
|
||||
// Zip file comments seem to be pretty rare, but if they are used,
|
||||
@ -311,23 +312,26 @@ fn get_last_modified_time(file: &fs::File) -> DateTime {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn set_last_modified_time(zip_file: &ZipFile, path: &Path) -> crate::Result<()> {
|
||||
let modification_time = zip_file.last_modified().to_time();
|
||||
|
||||
let Ok(time_in_seconds) = modification_time else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
fn set_last_modified_time<R: Read>(zip_file: &ZipFile<'_, R>, path: &Path) -> crate::Result<()> {
|
||||
// Extract modification time from zip file and convert to FileTime
|
||||
let file_time = zip_file
|
||||
.last_modified()
|
||||
.and_then(|datetime| OffsetDateTime::try_from(datetime).ok())
|
||||
.map(|time| {
|
||||
// Zip does not support nanoseconds, so we can assume zero here
|
||||
let modification_time = FileTime::from_unix_time(time_in_seconds.unix_timestamp(), 0);
|
||||
FileTime::from_unix_time(time.unix_timestamp(), 0)
|
||||
});
|
||||
|
||||
// Set the modification time if available
|
||||
if let Some(modification_time) = file_time {
|
||||
set_file_mtime(path, modification_time)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn unix_set_permissions(file_path: &Path, file: &ZipFile) -> crate::Result<()> {
|
||||
fn unix_set_permissions<R: Read>(file_path: &Path, file: &ZipFile<'_, R>) -> crate::Result<()> {
|
||||
use std::fs::Permissions;
|
||||
|
||||
if let Some(mode) = file.unix_mode() {
|
||||
|
14
src/error.rs
14
src/error.rs
@ -27,11 +27,11 @@ pub enum Error {
|
||||
/// NEEDS MORE CONTEXT
|
||||
AlreadyExists { error_title: String },
|
||||
/// From zip::result::ZipError::InvalidArchive
|
||||
InvalidZipArchive(&'static str),
|
||||
InvalidZipArchive(String),
|
||||
/// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied
|
||||
PermissionDenied { error_title: String },
|
||||
/// From zip::result::ZipError::UnsupportedArchive
|
||||
UnsupportedZipArchive(&'static str),
|
||||
UnsupportedZipArchive(String),
|
||||
/// We don't support compressing the root folder.
|
||||
CompressingRootFolder,
|
||||
/// Specialized walkdir's io::Error wrapper with additional information on the error
|
||||
@ -218,11 +218,17 @@ impl From<zip::result::ZipError> for Error {
|
||||
use zip::result::ZipError;
|
||||
match err {
|
||||
ZipError::Io(io_err) => Self::from(io_err),
|
||||
ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
|
||||
ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename.to_string()),
|
||||
ZipError::FileNotFound => Self::Custom {
|
||||
reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
|
||||
},
|
||||
ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
|
||||
ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename.to_string()),
|
||||
ZipError::InvalidPassword => Self::InvalidPassword {
|
||||
reason: "The provided password is incorrect".to_string(),
|
||||
},
|
||||
_ => Self::Custom {
|
||||
reason: FinalError::with_title("Unexpected error in zip archive").detail(err.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user