From d21db763f1dab62ceb2c06f2a8eb0d1a479a8a4d Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sun, 24 Mar 2024 06:25:25 +0000 Subject: [PATCH] feat: support decompress and list zip file --- Cargo.lock | 67 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/archive/zip.rs | 26 ++++++++++++--- src/commands/decompress.rs | 4 +-- src/commands/list.rs | 4 +-- 5 files changed, 93 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f786111..cf27574 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bit-set" version = "0.5.3" @@ -353,6 +359,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core_affinity" version = "0.8.1" @@ -455,6 +467,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -639,6 +652,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "ignore" version = "0.4.22" @@ -951,6 +973,29 @@ dependencies = [ "syn", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1246,6 +1291,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1319,6 +1375,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.77" @@ -1743,10 +1805,15 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ + "aes", "byteorder", + "constant_time_eq", "crc32fast", "crossbeam-utils", "flate2", + "hmac", + "pbkdf2", + "sha1", "time", ] diff --git a/Cargo.toml b/Cargo.toml index 7e6be0d..aaa1fba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ tempfile = "3.10.1" time = { version = "0.3.36", default-features = false } unrar = { version = "0.5.6", optional = true } xz2 = "0.1.7" -zip = { version = "0.6.6", default-features = false, features = ["time"] } +zip = { version = "0.6.6", default-features = false, features = ["time", "aes-crypto"] } zstd = { version = "0.13.2", default-features = false, features = ["zstdmt"]} [target.'cfg(not(unix))'.dependencies] diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 6df7308..24575f1 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -9,12 +9,14 @@ use std::{ sync::mpsc, thread, }; +use bstr::ByteSlice; use filetime_creation::{set_file_mtime, FileTime}; use fs_err as fs; use same_file::Handle; use time::OffsetDateTime; -use zip::{read::ZipFile, DateTime, ZipArchive}; +use zip::{self, read::ZipFile, DateTime, ZipArchive}; +use zip::result::InvalidPassword; use crate::{ error::FinalError, @@ -28,7 +30,7 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `output_folder`. /// Assumes that output_folder is empty -pub fn unpack_archive(mut archive: ZipArchive, output_folder: &Path, quiet: bool) -> crate::Result +pub fn unpack_archive(mut archive: ZipArchive, output_folder: &Path, password: Option>, quiet: bool) -> crate::Result where R: Read + Seek, { @@ -36,8 +38,14 @@ where let mut unpacked_files = 0; + let password = password.as_ref().map(|p| p.as_ref().to_owned()); + for idx in 0..archive.len() { - let mut file = archive.by_index(idx)?; + let mut file = match password.clone() { + Some(password) => archive.by_index_decrypt(idx, password.to_owned().as_bytes()).unwrap() + .map_err(|_| zip::result::ZipError::UnsupportedArchive("Password required to decrypt file"))?, + None => archive.by_index(idx)?, + }; let file_path = match file.enclosed_name() { Some(path) => path.to_owned(), None => continue, @@ -92,7 +100,7 @@ where } /// List contents of `archive`, returning a vector of archive entries -pub fn list_archive(mut archive: ZipArchive) -> impl Iterator> +pub fn list_archive(mut archive: ZipArchive, password: Option>) -> impl Iterator> where R: Read + Seek + Send + 'static, { @@ -105,11 +113,19 @@ where } } + let password = password.as_ref().map(|p| p.as_ref().to_owned()); + let (tx, rx) = mpsc::channel(); thread::spawn(move || { for idx in 0..archive.len() { let maybe_file_in_archive = (|| { - let file = match archive.by_index(idx) { + let zip_result = match password.clone() { + Some(password) => archive.by_index_decrypt(idx, password.to_owned().clone().as_bytes()).unwrap() + .map_err(|_| zip::result::ZipError::UnsupportedArchive("Password required to decrypt file")), + None => archive.by_index(idx), + }; + + let file = match zip_result { Ok(f) => f, Err(e) => return Some(Err(e.into())), }; diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index a2e5c58..ebb1482 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -63,7 +63,7 @@ pub fn decompress_file( }; let zip_archive = zip::ZipArchive::new(reader)?; let files_unpacked = if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, quiet), + |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, password, quiet), output_dir, &output_file_path, question_policy, @@ -157,7 +157,7 @@ pub fn decompress_file( let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, quiet), + |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, password, quiet), output_dir, &output_file_path, question_policy, diff --git a/src/commands/list.rs b/src/commands/list.rs index b3396e1..5f8da70 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -35,7 +35,7 @@ pub fn list_archive_contents( // Any other Zip decompression done can take up the whole RAM and freeze ouch. if let &[Zip] = formats.as_slice() { let zip_archive = zip::ZipArchive::new(reader)?; - let files = crate::archive::zip::list_archive(zip_archive); + let files = crate::archive::zip::list_archive(zip_archive, password); list::list_files(archive_path, files, list_options)?; return Ok(()); @@ -82,7 +82,7 @@ pub fn list_archive_contents( io::copy(&mut reader, &mut vec)?; let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; - Box::new(crate::archive::zip::list_archive(zip_archive)) + Box::new(crate::archive::zip::list_archive(zip_archive, password)) } #[cfg(feature = "unrar")] Rar => {