├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── .github └── workflows │ └── check.yaml ├── src └── main.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | pcrypt 3 | test -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcrypt" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["pouriya.jahanbakhsh@gmail.com"] 6 | description = "A utility to Archive (zip) + Encrypt (AES-256) + Compress (Zstd) directory files and vice versa" 7 | 8 | [dependencies] 9 | # Commandline argument parser: 10 | clap = {version = "4.5.35", default-features = false, features = ["std", "derive", "help", "error-context"]} 11 | # Zip & AES & Zstd: 12 | zip = {version = "2.6.1", default-features=false, features = ["aes-crypto", "zstd", "bzip2"]} 13 | # Time library to create archive filenames: 14 | chrono = {version = "0.4.40", features = ["std"]} 15 | # Progressbars: 16 | indicatif = "0.17.11" 17 | # Signal handler to detect Ctrl+C: 18 | ctrlc = "3.4.6" 19 | # To Read input password: 20 | rpassword = "7.3.1" 21 | # Error handling: 22 | thiserror = "2.0.12" 23 | anyhow = "1.0.97" 24 | 25 | [features] 26 | default = [] 27 | password-from-env = [] 28 | 29 | [profile.release] 30 | opt-level = 3 31 | overflow-checks = false 32 | lto = true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET=$(shell rustup target list | grep 'installed' | awk '$$1 != "" {print $$1; exit}') 2 | EXE=pcrypt 3 | 4 | all: dev fmt clippy test prod 5 | 6 | dev: 7 | cargo build --features password-from-env --target ${TARGET} 8 | @ cp ./target/${TARGET}/debug/pcrypt ./${EXE} 9 | @ echo "Built pcrypt dev" 10 | 11 | fmt: 12 | cargo fmt --check 13 | @ echo "Checked format style" 14 | 15 | clippy: 16 | cargo clippy --no-deps 17 | @ echo "Checked clippy issues" 18 | 19 | test: dev 20 | @ rm -rf test && mkdir -p test/contents && mkdir -p test/decrypted 21 | openssl rand -base64 -out test/contents/file.txt 36700160 # 35MB 22 | openssl rand -base64 -out test/contents/txt.file 36700160 23 | mkdir -p test/contents/ignore && echo XYZ > test/contents/ignore/ignored.txt 24 | cd test && PCRYPT_PASSWORD="P" ../${EXE} archive -z=-7 contents 25 | mv test/contents*.pcrypt.zip test/archived.pcrypt.zip 26 | cd test/decrypted && PCRYPT_PASSWORD="P" ../../${EXE} extract ../archived.pcrypt.zip 27 | cmp test/contents/file.txt test/decrypted/file.txt 28 | cmp test/contents/txt.file test/decrypted/txt.file 29 | @ if [ -d "test/decrypted/ignore" ]; then echo "ignore directory exists"; exit 1; fi 30 | @ echo "Test successful" 31 | 32 | prod: 33 | cargo build --release --target ${TARGET} 34 | @ cp ./target/${TARGET}/release/pcrypt ./${EXE} 35 | @ echo "Built pcrypt production" 36 | 37 | .PHONY: all dev fmt clippy test prod -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PCrypt 2 | A utility to Archive (zip) + Encrypt (AES-256) + Compress (Zstd) directory files and vice versa 3 | 4 | ![pcrypt](https://github.com/user-attachments/assets/140c4ba1-bc08-41f9-8ea2-ee0f9adfbf70) 5 | 6 | 7 | # Usage 8 | ```sh 9 | ~ $ pcrypt --help 10 | ``` 11 | ```text 12 | A utility to Archive (zip) + Encrypt (AES-256) + Compress (Zstd) directory files and vice versa 13 | 14 | Usage: 15 | 16 | Commands: 17 | archive Archive + Encrypt + Compress files of an input directory (only first level of files) 18 | extract Extract + Decrypt + Decompress contents of an archive file 19 | help Print this message or the help of the given subcommand(s) 20 | 21 | Options: 22 | -h, --help Print help 23 | -V, --version Print version 24 | ``` 25 | 26 | ### Archive Usage 27 | ```sh 28 | ~ $ pcrypt archive --help 29 | ``` 30 | ```text 31 | Archive + Encrypt + Compress files of an input directory (only first level of files) 32 | 33 | Usage: 34 | 35 | Arguments: 36 | 37 | Directory path to archive 38 | 39 | Options: 40 | -z 41 | Zstd compression level (between -7 - 22) 42 | 43 | [default: 7] 44 | 45 | --compression-method 46 | Compression method 47 | 48 | [default: zstd] 49 | 50 | Possible values: 51 | - zstd: Fast and efficeint but (for now) you have to decompress archives only using this app 52 | - bzip2: VERY SLOW (compared to `zstd`), but you can decompress archive via well-known tools like 7z 53 | 54 | -h, --help 55 | Print help (see a summary with '-h') 56 | ``` 57 | 58 | ### Extract Usage 59 | ```sh 60 | ~ $ pcrypt extract --help 61 | ``` 62 | ```text 63 | Extract + Decrypt + Decompress contents of an archive file 64 | 65 | Usage: 66 | 67 | Arguments: 68 | Archived .pcrypt.zip file path to extract 69 | 70 | Options: 71 | -h, --help Print help 72 | ``` 73 | 74 | 75 | # Installation 76 | Download & Extract (& `chmod` on unix) executables from [Release page](https://github.com/pouriya/pcrypt/releases). -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | tags-ignore: 6 | - "*.*.*" 7 | paths-ignore: 8 | - "**.md" 9 | branches-ignore: 10 | - "documentation" 11 | pull_request: 12 | branches: 13 | - "master" 14 | paths-ignore: 15 | - "**.md" 16 | 17 | jobs: 18 | build: 19 | name: ${{ matrix.name }} 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | include: 25 | - os: macos-13 26 | target: x86_64-apple-darwin 27 | name: macOS 13 (x86-64) 28 | - os: macos-14 29 | target: x86_64-apple-darwin 30 | name: macOS 14 (x86-64) 31 | - os: macos-15 32 | target: x86_64-apple-darwin 33 | name: macOS 15 (x86-64) 34 | 35 | - os: ubuntu-22.04 36 | target: x86_64-unknown-linux-gnu 37 | name: Ubuntu 22.04 GNU (x86-64) 38 | - os: ubuntu-22.04 39 | target: x86_64-unknown-linux-musl 40 | name: Ubuntu 22.04 Musl (x86-64) 41 | - os: ubuntu-24.04 42 | target: x86_64-unknown-linux-gnu 43 | name: Ubuntu 24.04 GNU (x86-64) 44 | - os: ubuntu-24.04 45 | target: x86_64-unknown-linux-musl 46 | name: Ubuntu 24.04 Musl (x86-64) 47 | 48 | - os: windows-2019 49 | target: x86_64-pc-windows-gnu 50 | name: Windows 2019 GNU (x86-64) 51 | - os: windows-2019 52 | target: x86_64-pc-windows-msvc 53 | name: Windows 2019 MSVC (x86-64) 54 | - os: windows-2022 55 | target: x86_64-pc-windows-gnu 56 | name: Windows 2022 GNU (x86-64) 57 | - os: windows-2022 58 | target: x86_64-pc-windows-msvc 59 | name: Windows 2022 MSVC (x86-64) 60 | - os: windows-2025 61 | target: x86_64-pc-windows-gnu 62 | name: Windows 2025 GNU (x86-64) 63 | - os: windows-2025 64 | target: x86_64-pc-windows-msvc 65 | name: Windows 2025 MSVC (x86-64) 66 | 67 | steps: 68 | - uses: actions/checkout@v4 69 | 70 | - name: Installing macOS dependencies 71 | if: contains(matrix.os, 'macos-') 72 | run: | 73 | brew install openssl@1.1 74 | brew install make 75 | rustup target add ${{ matrix.target }} 76 | - name: Installing Windows dependencies 77 | if: contains(matrix.os, 'windows-') 78 | run: | 79 | rustup target add ${{ matrix.target }} 80 | - name: Installing Linux dependencies for `musl` 81 | if: contains(matrix.os, 'ubuntu-') && contains(matrix.target, '-musl') 82 | run: | 83 | sudo apt-get update 84 | sudo apt-get install -y -qq musl-dev musl-tools 85 | rustup target add ${{ matrix.target }} 86 | export TARGET_CC=x86_64-linux-musl-gcc 87 | 88 | - name: Lint 89 | run: make fmt TARGET=${{ matrix.target }} 90 | 91 | - name: Test 92 | run: | 93 | make test TARGET=${{ matrix.target }} EXE=pcrypt-${{ matrix.os }}-${{ matrix.target }} 94 | 95 | - name: Build 96 | run: | 97 | make prod TARGET=${{ matrix.target }} EXE=pcrypt-${{ matrix.os }}-${{ matrix.target }} 98 | 99 | - name: Archive artifacts 100 | uses: actions/upload-artifact@v4 101 | with: 102 | name: pcrypt-${{ matrix.os }}-${{ matrix.target }} 103 | path: ./pcrypt-* 104 | retention-days: 10 105 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use chrono::DateTime; 2 | use clap::{Parser, Subcommand, ValueEnum}; 3 | use std::{ 4 | fs, 5 | io::{BufReader, Error as IoError, Read, Write}, 6 | path::{Path, PathBuf}, 7 | sync::{atomic, Arc}, 8 | time::SystemTime, 9 | }; 10 | use zip::result::ZipError; 11 | 12 | #[cfg(not(target_os = "windows"))] 13 | use std::os::unix::fs::MetadataExt; 14 | #[cfg(target_os = "windows")] 15 | use std::os::windows::fs::MetadataExt; 16 | 17 | const ZSTD_COMPRESSION_LEVEL: i64 = 7; 18 | const PROGRESSBAR_TEMPLATE: &str = 19 | "{msg:<30} | [{elapsed_precise:^8}] | {bytes:>11}/{total_bytes:<11} | ~{eta:^6} | [{wide_bar}]"; 20 | const PROGRESSBAR_BAR_CHARACTERS: &str = "=>-"; 21 | #[cfg(not(target_os = "windows"))] 22 | const READ_WRITE_BUFFER_SIZE: usize = 1048576; 23 | #[cfg(target_os = "windows")] 24 | const READ_WRITE_BUFFER_SIZE: usize = 524288; 25 | 26 | #[derive(Parser, Debug)] 27 | #[command(version, about, author, long_about = None)] 28 | struct CommandLineOptions { 29 | #[command(subcommand)] 30 | command: Commands, 31 | } 32 | 33 | #[derive(Subcommand, Debug)] 34 | enum Commands { 35 | /// Archive + Encrypt + Compress files of an input directory (only first level of files) 36 | Archive { 37 | /// Directory path to archive 38 | #[arg()] 39 | directory: String, 40 | /// Zstd compression level (between -7 - 22) 41 | #[arg(short, value_parser = zstd_compression_level_parser, default_value_t = ZSTD_COMPRESSION_LEVEL)] 42 | zstd_compression_level: i64, 43 | /// Compression method. 44 | #[arg(long, value_enum, default_value_t = CompressionMethod::Zstd)] 45 | compression_method: CompressionMethod, 46 | }, 47 | /// Extract + Decrypt + Decompress contents of an archive file 48 | Extract { 49 | /// Archived .pcrypt.zip file path to extract 50 | #[arg()] 51 | archived_file: String, 52 | }, 53 | } 54 | 55 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 56 | enum CompressionMethod { 57 | /// Fast and efficeint but (for now) you have to decompress archives only using this app. 58 | Zstd, 59 | /// VERY SLOW (compared to `zstd`), but you can decompress archive via well-known tools like 7z. 60 | Bzip2, 61 | } 62 | 63 | type Result = std::result::Result; 64 | 65 | #[derive(Debug, thiserror::Error)] 66 | pub enum Error { 67 | #[error("Directory {directory:?} does not exists")] 68 | DirectoryNotFound { directory: PathBuf }, 69 | #[error("Compressed file {filename:?} does not exists")] 70 | ArchivedFileNotFound { filename: PathBuf }, 71 | #[error("{directory:?} is not a valid directory")] 72 | NotADirectory { directory: PathBuf }, 73 | #[error("{filename:?} is not a regular file")] 74 | NotAFile { filename: PathBuf }, 75 | #[error("{filename:?} is not archived via this application")] 76 | NotArchivedByMe { filename: PathBuf }, 77 | #[error("Could not search inside directory {directory:?}")] 78 | SearchDirectory { directory: PathBuf, source: IoError }, 79 | #[error("{filename:?} is not a valid zip archive")] 80 | NotAZip { filename: PathBuf, source: ZipError }, 81 | #[error("There is no file to archive inside {directory:?}")] 82 | NothingToArchive { directory: PathBuf }, 83 | #[error("There is no file to extract inside {filename:?}")] 84 | NothingToDeExtract { filename: PathBuf }, 85 | #[error("Could not normalize path {path:?}")] 86 | NormalizePath { path: PathBuf, source: IoError }, 87 | #[error("Archive file {filename:?} already exists")] 88 | FileAlreadyExists { filename: PathBuf }, 89 | #[error("Could not detect directory name from {directory:?}")] 90 | DetectDirectoryName { directory: PathBuf }, 91 | #[error("Could not create file {filename:?}")] 92 | CreateFile { filename: PathBuf, source: IoError }, 93 | #[error("Could not create zip file")] 94 | ZipArchive { source: ZipError }, 95 | #[error("Could not read file {filename:?}")] 96 | ReadFile { filename: PathBuf, source: IoError }, 97 | #[error("Could not write into file {filename:?}")] 98 | WriteFile { filename: PathBuf, source: IoError }, 99 | #[error("Could not write to zip file")] 100 | ZipWrite { source: IoError }, 101 | #[error("Could not read archive file {filename:?} from zip archive")] 102 | ZipRead { filename: PathBuf, source: ZipError }, 103 | #[error("Stopped")] 104 | Stopped, 105 | #[error("Could not read user password from input")] 106 | ReadPassword { source: IoError }, 107 | } 108 | 109 | fn main() -> anyhow::Result<()> { 110 | let commandline_options = CommandLineOptions::parse(); 111 | let running = Arc::new(atomic::AtomicBool::new(true)); 112 | let ctrlc_running = running.clone(); 113 | if let Err(error) = ctrlc::set_handler(move || { 114 | ctrlc_running.store(false, atomic::Ordering::SeqCst); 115 | }) { 116 | eprintln!("Could not setup Ctrl-C handler: {error}") 117 | }; 118 | #[cfg(not(feature = "password-from-env"))] 119 | let password_func = || -> Result { 120 | rpassword::prompt_password("Enter Password: ") 121 | .map_err(|error| Error::ReadPassword { source: error }) 122 | }; 123 | // For test environment: 124 | #[cfg(feature = "password-from-env")] 125 | let password_func = || -> Result { 126 | std::env::var("PCRYPT_PASSWORD").map_err(|error| Error::ReadPassword { 127 | source: std::io::Error::new(std::io::ErrorKind::NotFound, error), 128 | }) 129 | }; 130 | match commandline_options.command { 131 | Commands::Archive { 132 | directory, 133 | zstd_compression_level, 134 | compression_method, 135 | } => archive( 136 | directory, 137 | zstd_compression_level, 138 | compression_method, 139 | running.clone(), 140 | password_func, 141 | ), 142 | Commands::Extract { archived_file } => { 143 | extract(archived_file, running.clone(), password_func) 144 | } 145 | } 146 | .map_err(|error| anyhow::anyhow!(error)) 147 | } 148 | 149 | fn zstd_compression_level_parser(v: &str) -> std::result::Result { 150 | v.parse() 151 | .map_err(|_| format!("could not convert {v:?} to number")) 152 | .and_then(|n| { 153 | if !(-7..=22).contains(&n) { 154 | Err("Zstd compression level MUST be between -7 - 22".into()) 155 | } else { 156 | Ok(n) 157 | } 158 | }) 159 | } 160 | 161 | #[inline(always)] 162 | fn is_running(running: &Arc) -> Result<()> { 163 | if running.load(atomic::Ordering::Relaxed) { 164 | Ok(()) 165 | } else { 166 | Err(Error::Stopped) 167 | } 168 | } 169 | 170 | #[inline(always)] 171 | fn reduce_to_30_characters>(x: P) -> String { 172 | let x = x.as_ref().to_str().unwrap_or_default().to_string(); 173 | let character_count = x.chars().count(); 174 | if character_count > 30 { 175 | return format!( 176 | "{}...{}", 177 | x.chars().take(13).collect::(), 178 | x.chars().skip(character_count - 13).collect::() 179 | ); 180 | }; 181 | x 182 | } 183 | 184 | fn archive>( 185 | directory: D, 186 | zstd_compression_level: i64, 187 | compression_method: CompressionMethod, 188 | running: Arc, 189 | read_password: impl Fn() -> Result, 190 | ) -> Result<()> { 191 | let directory = directory.as_ref(); 192 | let directory = directory 193 | .canonicalize() 194 | .map_err(|error| Error::NormalizePath { 195 | path: directory.to_path_buf(), 196 | source: error, 197 | })?; 198 | let directory_name = if let Some(directory_name) = directory.file_name() { 199 | if let Some(directory_name) = directory_name.to_str() { 200 | directory_name.to_string() 201 | } else { 202 | return Err(Error::DetectDirectoryName { directory }); 203 | } 204 | } else { 205 | return Err(Error::DetectDirectoryName { directory }); 206 | }; 207 | is_running(&running)?; 208 | 209 | if !directory.exists() { 210 | return Err(Error::DirectoryNotFound { 211 | directory: directory.clone(), 212 | }); 213 | }; 214 | if !directory.is_dir() { 215 | return Err(Error::NotADirectory { 216 | directory: directory.clone(), 217 | }); 218 | }; 219 | is_running(&running)?; 220 | 221 | let file_list: Vec<_> = directory 222 | .read_dir() 223 | .map_err(|error| Error::SearchDirectory { 224 | directory: directory.clone(), 225 | source: error, 226 | })? 227 | .filter_map(|entry_result| { 228 | if let Err(error) = entry_result { 229 | eprintln!("could not detect directory entry: {error}"); 230 | None 231 | } else { 232 | entry_result.ok() 233 | } 234 | }) 235 | .map(|entry| entry.path()) 236 | .filter_map(|path| { 237 | if path.is_file() { 238 | Some(path) 239 | } else if path.is_dir() { 240 | eprintln!("skip archiving directory {path:?}"); 241 | None 242 | } else { 243 | eprintln!("skip archiving unhandled file {path:?}"); 244 | None 245 | } 246 | }) 247 | .filter_map(|filepath| { 248 | if filepath.ends_with(".pcrypt.zip") { 249 | eprintln!("skipping already archivied PCrypt file {filepath:?}"); 250 | None 251 | } else { 252 | Some(filepath) 253 | } 254 | }) 255 | .filter_map(|filepath| { 256 | if let Some(filename) = filepath.file_name() { 257 | if let Some(filename) = filename.to_str() { 258 | let filename = filename.to_string(); 259 | Some((filepath, filename)) 260 | } else { 261 | eprintln!("Could not convert filename {filepath:?} to string"); 262 | None 263 | } 264 | } else { 265 | eprintln!("Could not detect filename in {filepath:?}"); 266 | None 267 | } 268 | }) 269 | .collect(); 270 | if file_list.is_empty() { 271 | return Err(Error::NothingToArchive { 272 | directory: directory.clone(), 273 | }); 274 | } 275 | let time = SystemTime::now(); 276 | let utc_datetime: DateTime = time.into(); 277 | let utc_datetime_string = format!("{}", utc_datetime.format("%Y-%m-%d-%H-%M-%S-%b-%a")); 278 | let pcrypt_filename = PathBuf::from(format!( 279 | "{directory_name}-{utc_datetime_string}-{}.pcrypt.zip", 280 | file_list.len() 281 | )); 282 | is_running(&running)?; 283 | let password = read_password()?; 284 | println!( 285 | "Attempt to Archive + Encrypt + Compress {} file(s) into {pcrypt_filename:?}", 286 | file_list.len() 287 | ); 288 | 289 | let pcrypt_file = fs::File::create(&pcrypt_filename).map_err(|error| Error::CreateFile { 290 | filename: pcrypt_filename.clone(), 291 | source: error, 292 | })?; 293 | let mut zip = zip::ZipWriter::new(pcrypt_file); 294 | let (compression_method, compression_level) = match compression_method { 295 | CompressionMethod::Zstd => (zip::CompressionMethod::Zstd, zstd_compression_level), 296 | CompressionMethod::Bzip2 => (zip::CompressionMethod::Bzip2, 3), 297 | }; 298 | let options = zip::write::SimpleFileOptions::default() 299 | .compression_method(compression_method) 300 | .compression_level(Some(compression_level)) 301 | .large_file(true) 302 | .with_aes_encryption(zip::AesMode::Aes256, &password) 303 | .unix_permissions(0o664); 304 | is_running(&running).inspect_err(|_| { 305 | let _ = zip.abort_file(); 306 | let _ = fs::remove_file(&pcrypt_filename); 307 | })?; 308 | 309 | let multi_progress_bar = indicatif::MultiProgress::new(); 310 | file_list 311 | .into_iter() 312 | .try_for_each(|(filepath, filename)| { 313 | zip.start_file(&filename, options) 314 | .map_err(|error| Error::ZipArchive { source: error })?; 315 | let mut file = fs::OpenOptions::new() 316 | .read(true) 317 | .open(&filepath) 318 | .map_err(|error| Error::ReadFile { 319 | filename: filepath.clone(), 320 | source: error, 321 | })?; 322 | is_running(&running)?; 323 | 324 | #[cfg(not(target_os = "windows"))] 325 | let file_size = file 326 | .metadata() 327 | .map(|metadata| metadata.size()) 328 | .unwrap_or_default(); 329 | #[cfg(target_os = "windows")] 330 | let file_size = file 331 | .metadata() 332 | .map(|metadata| metadata.file_size()) 333 | .unwrap_or_default(); 334 | let progress_bar = indicatif::ProgressBar::new(file_size) 335 | .with_message(reduce_to_30_characters(filename)); 336 | progress_bar.set_style( 337 | indicatif::ProgressStyle::with_template(PROGRESSBAR_TEMPLATE) 338 | .unwrap() 339 | .progress_chars(PROGRESSBAR_BAR_CHARACTERS), 340 | ); 341 | let progress_bar = multi_progress_bar.add(progress_bar); 342 | let mut buffer: [u8; READ_WRITE_BUFFER_SIZE] = [0; READ_WRITE_BUFFER_SIZE]; 343 | is_running(&running)?; 344 | 345 | loop { 346 | let bytes_read = file.read(&mut buffer).map_err(|error| Error::ReadFile { 347 | filename: filepath.clone(), 348 | source: error, 349 | })?; 350 | progress_bar.inc(bytes_read as u64); 351 | if bytes_read == 0 { 352 | progress_bar.finish(); 353 | break; 354 | } 355 | zip.write_all(&buffer[0..bytes_read]) 356 | .map_err(|error| Error::ZipWrite { source: error })?; 357 | is_running(&running)?; 358 | } 359 | Ok(()) 360 | }) 361 | .inspect_err(|_| { 362 | let _ = zip.abort_file(); 363 | let _ = fs::remove_file(&pcrypt_filename); 364 | })?; 365 | zip.finish().map_err(|error| { 366 | let _ = fs::remove_file(&pcrypt_filename); 367 | Error::ZipArchive { source: error } 368 | })?; 369 | Ok(()) 370 | } 371 | 372 | fn extract>( 373 | archived_filename: F, 374 | running: Arc, 375 | read_password: impl Fn() -> Result, 376 | ) -> Result<()> { 377 | let archived_filename = archived_filename.as_ref(); 378 | let archived_filename = 379 | archived_filename 380 | .canonicalize() 381 | .map_err(|error| Error::NormalizePath { 382 | path: archived_filename.to_path_buf(), 383 | source: error, 384 | })?; 385 | is_running(&running)?; 386 | 387 | if !archived_filename.exists() { 388 | return Err(Error::ArchivedFileNotFound { 389 | filename: archived_filename.clone(), 390 | }); 391 | }; 392 | if !archived_filename.is_file() { 393 | return Err(Error::NotAFile { 394 | filename: archived_filename.to_path_buf(), 395 | }); 396 | }; 397 | if archived_filename.ends_with(".pcrypt.zip") { 398 | return Err(Error::NotArchivedByMe { 399 | filename: archived_filename.clone(), 400 | }); 401 | } 402 | is_running(&running)?; 403 | 404 | let archived_file = fs::File::open(&archived_filename).map_err(|error| Error::ReadFile { 405 | filename: archived_filename.clone(), 406 | source: error, 407 | })?; 408 | let reader = BufReader::new(archived_file); 409 | let mut archive = zip::ZipArchive::new(reader).map_err(|error| Error::NotAZip { 410 | filename: archived_filename.clone(), 411 | source: error, 412 | })?; 413 | if archive.is_empty() { 414 | return Err(Error::NothingToDeExtract { 415 | filename: archived_filename.clone(), 416 | }); 417 | }; 418 | is_running(&running)?; 419 | (0..archive.len()).try_for_each(|index| { 420 | let archive_file = archive.by_index_raw(index).unwrap(); 421 | let archive_filename = archive_file.mangled_name(); 422 | if archive_filename.exists() { 423 | return Err(Error::FileAlreadyExists { 424 | filename: archive_filename, 425 | }); 426 | }; 427 | Ok(()) 428 | })?; 429 | is_running(&running)?; 430 | let password = read_password()?; 431 | println!( 432 | "Attempt to Extract + Decrypt + Decompress {} files from {archived_filename:?}", 433 | archive.len() 434 | ); 435 | 436 | let multi_progress_bar = indicatif::MultiProgress::new(); 437 | let mut file_list = Vec::with_capacity(archive.len()); 438 | (0..archive.len()) 439 | .try_for_each(|index| { 440 | let archive_filename = archive.by_index_raw(index).unwrap().mangled_name(); 441 | let mut archive_file = archive 442 | .by_index_decrypt(index, password.as_bytes()) 443 | .map_err(|error| Error::ZipRead { 444 | filename: archive_filename.clone(), 445 | source: error, 446 | })?; 447 | let mut file = fs::OpenOptions::new() 448 | .create_new(true) 449 | .write(true) 450 | .open(&archive_filename) 451 | .map_err(|error| Error::CreateFile { 452 | filename: archive_filename.clone(), 453 | source: error, 454 | })?; 455 | file_list.push(archive_filename.clone()); 456 | is_running(&running)?; 457 | 458 | let file_size = archive_file.size(); 459 | let progress_bar = indicatif::ProgressBar::new(file_size) 460 | .with_message(reduce_to_30_characters(&archive_filename)); 461 | progress_bar.set_style( 462 | indicatif::ProgressStyle::with_template(PROGRESSBAR_TEMPLATE) 463 | .unwrap() 464 | .progress_chars(PROGRESSBAR_BAR_CHARACTERS), 465 | ); 466 | let progress_bar = multi_progress_bar.add(progress_bar); 467 | let mut buffer: [u8; READ_WRITE_BUFFER_SIZE] = [0; READ_WRITE_BUFFER_SIZE]; 468 | is_running(&running)?; 469 | loop { 470 | let bytes_read = 471 | archive_file 472 | .read(&mut buffer) 473 | .map_err(|error| Error::ReadFile { 474 | filename: archive_filename.clone(), 475 | source: error, 476 | })?; 477 | progress_bar.inc(bytes_read as u64); 478 | if bytes_read == 0 { 479 | progress_bar.finish(); 480 | break; 481 | } 482 | file.write_all(&buffer[0..bytes_read]) 483 | .map_err(|error| Error::WriteFile { 484 | filename: archive_filename.clone(), 485 | source: error, 486 | })?; 487 | is_running(&running)?; 488 | } 489 | Ok(()) 490 | }) 491 | .inspect_err(|_| { 492 | file_list.into_iter().for_each(|filename| { 493 | let _ = fs::remove_file(&filename); 494 | eprintln!("Removed extracted file {filename:?}"); 495 | }); 496 | })?; 497 | Ok(()) 498 | } 499 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aes" 7 | version = "0.8.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | ] 15 | 16 | [[package]] 17 | name = "android-tzdata" 18 | version = "0.1.1" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 21 | 22 | [[package]] 23 | name = "android_system_properties" 24 | version = "0.1.5" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 27 | dependencies = [ 28 | "libc", 29 | ] 30 | 31 | [[package]] 32 | name = "anstyle" 33 | version = "1.0.10" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 36 | 37 | [[package]] 38 | name = "anyhow" 39 | version = "1.0.97" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 42 | 43 | [[package]] 44 | name = "arbitrary" 45 | version = "1.4.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 48 | dependencies = [ 49 | "derive_arbitrary", 50 | ] 51 | 52 | [[package]] 53 | name = "autocfg" 54 | version = "1.4.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 57 | 58 | [[package]] 59 | name = "bitflags" 60 | version = "2.9.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 63 | 64 | [[package]] 65 | name = "block-buffer" 66 | version = "0.10.4" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 69 | dependencies = [ 70 | "generic-array", 71 | ] 72 | 73 | [[package]] 74 | name = "bumpalo" 75 | version = "3.17.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 78 | 79 | [[package]] 80 | name = "bzip2" 81 | version = "0.5.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" 84 | dependencies = [ 85 | "bzip2-sys", 86 | ] 87 | 88 | [[package]] 89 | name = "bzip2-sys" 90 | version = "0.1.13+1.0.8" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" 93 | dependencies = [ 94 | "cc", 95 | "pkg-config", 96 | ] 97 | 98 | [[package]] 99 | name = "cc" 100 | version = "1.2.18" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" 103 | dependencies = [ 104 | "jobserver", 105 | "libc", 106 | "shlex", 107 | ] 108 | 109 | [[package]] 110 | name = "cfg-if" 111 | version = "1.0.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 114 | 115 | [[package]] 116 | name = "cfg_aliases" 117 | version = "0.2.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 120 | 121 | [[package]] 122 | name = "chrono" 123 | version = "0.4.40" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 126 | dependencies = [ 127 | "android-tzdata", 128 | "iana-time-zone", 129 | "js-sys", 130 | "num-traits", 131 | "wasm-bindgen", 132 | "windows-link", 133 | ] 134 | 135 | [[package]] 136 | name = "cipher" 137 | version = "0.4.4" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 140 | dependencies = [ 141 | "crypto-common", 142 | "inout", 143 | ] 144 | 145 | [[package]] 146 | name = "clap" 147 | version = "4.5.35" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" 150 | dependencies = [ 151 | "clap_builder", 152 | "clap_derive", 153 | ] 154 | 155 | [[package]] 156 | name = "clap_builder" 157 | version = "4.5.35" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" 160 | dependencies = [ 161 | "anstyle", 162 | "clap_lex", 163 | ] 164 | 165 | [[package]] 166 | name = "clap_derive" 167 | version = "4.5.32" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 170 | dependencies = [ 171 | "heck", 172 | "proc-macro2", 173 | "quote", 174 | "syn", 175 | ] 176 | 177 | [[package]] 178 | name = "clap_lex" 179 | version = "0.7.4" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 182 | 183 | [[package]] 184 | name = "console" 185 | version = "0.15.11" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 188 | dependencies = [ 189 | "encode_unicode", 190 | "libc", 191 | "once_cell", 192 | "unicode-width", 193 | "windows-sys 0.59.0", 194 | ] 195 | 196 | [[package]] 197 | name = "constant_time_eq" 198 | version = "0.3.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" 201 | 202 | [[package]] 203 | name = "core-foundation-sys" 204 | version = "0.8.7" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 207 | 208 | [[package]] 209 | name = "cpufeatures" 210 | version = "0.2.17" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 213 | dependencies = [ 214 | "libc", 215 | ] 216 | 217 | [[package]] 218 | name = "crc32fast" 219 | version = "1.4.2" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 222 | dependencies = [ 223 | "cfg-if", 224 | ] 225 | 226 | [[package]] 227 | name = "crossbeam-utils" 228 | version = "0.8.21" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 231 | 232 | [[package]] 233 | name = "crypto-common" 234 | version = "0.1.6" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 237 | dependencies = [ 238 | "generic-array", 239 | "typenum", 240 | ] 241 | 242 | [[package]] 243 | name = "ctrlc" 244 | version = "3.4.6" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" 247 | dependencies = [ 248 | "nix", 249 | "windows-sys 0.59.0", 250 | ] 251 | 252 | [[package]] 253 | name = "derive_arbitrary" 254 | version = "1.4.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" 257 | dependencies = [ 258 | "proc-macro2", 259 | "quote", 260 | "syn", 261 | ] 262 | 263 | [[package]] 264 | name = "digest" 265 | version = "0.10.7" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 268 | dependencies = [ 269 | "block-buffer", 270 | "crypto-common", 271 | "subtle", 272 | ] 273 | 274 | [[package]] 275 | name = "encode_unicode" 276 | version = "1.0.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 279 | 280 | [[package]] 281 | name = "equivalent" 282 | version = "1.0.2" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 285 | 286 | [[package]] 287 | name = "generic-array" 288 | version = "0.14.7" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 291 | dependencies = [ 292 | "typenum", 293 | "version_check", 294 | ] 295 | 296 | [[package]] 297 | name = "getrandom" 298 | version = "0.3.2" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 301 | dependencies = [ 302 | "cfg-if", 303 | "js-sys", 304 | "libc", 305 | "r-efi", 306 | "wasi", 307 | "wasm-bindgen", 308 | ] 309 | 310 | [[package]] 311 | name = "hashbrown" 312 | version = "0.15.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 315 | 316 | [[package]] 317 | name = "heck" 318 | version = "0.5.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 321 | 322 | [[package]] 323 | name = "hmac" 324 | version = "0.12.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 327 | dependencies = [ 328 | "digest", 329 | ] 330 | 331 | [[package]] 332 | name = "iana-time-zone" 333 | version = "0.1.63" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 336 | dependencies = [ 337 | "android_system_properties", 338 | "core-foundation-sys", 339 | "iana-time-zone-haiku", 340 | "js-sys", 341 | "log", 342 | "wasm-bindgen", 343 | "windows-core", 344 | ] 345 | 346 | [[package]] 347 | name = "iana-time-zone-haiku" 348 | version = "0.1.2" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 351 | dependencies = [ 352 | "cc", 353 | ] 354 | 355 | [[package]] 356 | name = "indexmap" 357 | version = "2.9.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 360 | dependencies = [ 361 | "equivalent", 362 | "hashbrown", 363 | ] 364 | 365 | [[package]] 366 | name = "indicatif" 367 | version = "0.17.11" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 370 | dependencies = [ 371 | "console", 372 | "number_prefix", 373 | "portable-atomic", 374 | "unicode-width", 375 | "web-time", 376 | ] 377 | 378 | [[package]] 379 | name = "inout" 380 | version = "0.1.4" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 383 | dependencies = [ 384 | "generic-array", 385 | ] 386 | 387 | [[package]] 388 | name = "jobserver" 389 | version = "0.1.33" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 392 | dependencies = [ 393 | "getrandom", 394 | "libc", 395 | ] 396 | 397 | [[package]] 398 | name = "js-sys" 399 | version = "0.3.77" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 402 | dependencies = [ 403 | "once_cell", 404 | "wasm-bindgen", 405 | ] 406 | 407 | [[package]] 408 | name = "libc" 409 | version = "0.2.171" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 412 | 413 | [[package]] 414 | name = "log" 415 | version = "0.4.27" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 418 | 419 | [[package]] 420 | name = "memchr" 421 | version = "2.7.4" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 424 | 425 | [[package]] 426 | name = "nix" 427 | version = "0.29.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 430 | dependencies = [ 431 | "bitflags", 432 | "cfg-if", 433 | "cfg_aliases", 434 | "libc", 435 | ] 436 | 437 | [[package]] 438 | name = "num-traits" 439 | version = "0.2.19" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 442 | dependencies = [ 443 | "autocfg", 444 | ] 445 | 446 | [[package]] 447 | name = "number_prefix" 448 | version = "0.4.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 451 | 452 | [[package]] 453 | name = "once_cell" 454 | version = "1.21.3" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 457 | 458 | [[package]] 459 | name = "pbkdf2" 460 | version = "0.12.2" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 463 | dependencies = [ 464 | "digest", 465 | "hmac", 466 | ] 467 | 468 | [[package]] 469 | name = "pcrypt" 470 | version = "1.0.0" 471 | dependencies = [ 472 | "anyhow", 473 | "chrono", 474 | "clap", 475 | "ctrlc", 476 | "indicatif", 477 | "rpassword", 478 | "thiserror", 479 | "zip", 480 | ] 481 | 482 | [[package]] 483 | name = "pkg-config" 484 | version = "0.3.32" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 487 | 488 | [[package]] 489 | name = "portable-atomic" 490 | version = "1.11.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 493 | 494 | [[package]] 495 | name = "proc-macro2" 496 | version = "1.0.94" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 499 | dependencies = [ 500 | "unicode-ident", 501 | ] 502 | 503 | [[package]] 504 | name = "quote" 505 | version = "1.0.40" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 508 | dependencies = [ 509 | "proc-macro2", 510 | ] 511 | 512 | [[package]] 513 | name = "r-efi" 514 | version = "5.2.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 517 | 518 | [[package]] 519 | name = "rpassword" 520 | version = "7.3.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" 523 | dependencies = [ 524 | "libc", 525 | "rtoolbox", 526 | "windows-sys 0.48.0", 527 | ] 528 | 529 | [[package]] 530 | name = "rtoolbox" 531 | version = "0.0.2" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" 534 | dependencies = [ 535 | "libc", 536 | "windows-sys 0.48.0", 537 | ] 538 | 539 | [[package]] 540 | name = "rustversion" 541 | version = "1.0.20" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 544 | 545 | [[package]] 546 | name = "sha1" 547 | version = "0.10.6" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 550 | dependencies = [ 551 | "cfg-if", 552 | "cpufeatures", 553 | "digest", 554 | ] 555 | 556 | [[package]] 557 | name = "shlex" 558 | version = "1.3.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 561 | 562 | [[package]] 563 | name = "subtle" 564 | version = "2.6.1" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 567 | 568 | [[package]] 569 | name = "syn" 570 | version = "2.0.100" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 573 | dependencies = [ 574 | "proc-macro2", 575 | "quote", 576 | "unicode-ident", 577 | ] 578 | 579 | [[package]] 580 | name = "thiserror" 581 | version = "2.0.12" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 584 | dependencies = [ 585 | "thiserror-impl", 586 | ] 587 | 588 | [[package]] 589 | name = "thiserror-impl" 590 | version = "2.0.12" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 593 | dependencies = [ 594 | "proc-macro2", 595 | "quote", 596 | "syn", 597 | ] 598 | 599 | [[package]] 600 | name = "typenum" 601 | version = "1.18.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 604 | 605 | [[package]] 606 | name = "unicode-ident" 607 | version = "1.0.18" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 610 | 611 | [[package]] 612 | name = "unicode-width" 613 | version = "0.2.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 616 | 617 | [[package]] 618 | name = "version_check" 619 | version = "0.9.5" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 622 | 623 | [[package]] 624 | name = "wasi" 625 | version = "0.14.2+wasi-0.2.4" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 628 | dependencies = [ 629 | "wit-bindgen-rt", 630 | ] 631 | 632 | [[package]] 633 | name = "wasm-bindgen" 634 | version = "0.2.100" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 637 | dependencies = [ 638 | "cfg-if", 639 | "once_cell", 640 | "rustversion", 641 | "wasm-bindgen-macro", 642 | ] 643 | 644 | [[package]] 645 | name = "wasm-bindgen-backend" 646 | version = "0.2.100" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 649 | dependencies = [ 650 | "bumpalo", 651 | "log", 652 | "proc-macro2", 653 | "quote", 654 | "syn", 655 | "wasm-bindgen-shared", 656 | ] 657 | 658 | [[package]] 659 | name = "wasm-bindgen-macro" 660 | version = "0.2.100" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 663 | dependencies = [ 664 | "quote", 665 | "wasm-bindgen-macro-support", 666 | ] 667 | 668 | [[package]] 669 | name = "wasm-bindgen-macro-support" 670 | version = "0.2.100" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 673 | dependencies = [ 674 | "proc-macro2", 675 | "quote", 676 | "syn", 677 | "wasm-bindgen-backend", 678 | "wasm-bindgen-shared", 679 | ] 680 | 681 | [[package]] 682 | name = "wasm-bindgen-shared" 683 | version = "0.2.100" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 686 | dependencies = [ 687 | "unicode-ident", 688 | ] 689 | 690 | [[package]] 691 | name = "web-time" 692 | version = "1.1.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 695 | dependencies = [ 696 | "js-sys", 697 | "wasm-bindgen", 698 | ] 699 | 700 | [[package]] 701 | name = "windows-core" 702 | version = "0.61.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" 705 | dependencies = [ 706 | "windows-implement", 707 | "windows-interface", 708 | "windows-link", 709 | "windows-result", 710 | "windows-strings", 711 | ] 712 | 713 | [[package]] 714 | name = "windows-implement" 715 | version = "0.60.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 718 | dependencies = [ 719 | "proc-macro2", 720 | "quote", 721 | "syn", 722 | ] 723 | 724 | [[package]] 725 | name = "windows-interface" 726 | version = "0.59.1" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 729 | dependencies = [ 730 | "proc-macro2", 731 | "quote", 732 | "syn", 733 | ] 734 | 735 | [[package]] 736 | name = "windows-link" 737 | version = "0.1.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 740 | 741 | [[package]] 742 | name = "windows-result" 743 | version = "0.3.2" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 746 | dependencies = [ 747 | "windows-link", 748 | ] 749 | 750 | [[package]] 751 | name = "windows-strings" 752 | version = "0.4.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" 755 | dependencies = [ 756 | "windows-link", 757 | ] 758 | 759 | [[package]] 760 | name = "windows-sys" 761 | version = "0.48.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 764 | dependencies = [ 765 | "windows-targets 0.48.5", 766 | ] 767 | 768 | [[package]] 769 | name = "windows-sys" 770 | version = "0.59.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 773 | dependencies = [ 774 | "windows-targets 0.52.6", 775 | ] 776 | 777 | [[package]] 778 | name = "windows-targets" 779 | version = "0.48.5" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 782 | dependencies = [ 783 | "windows_aarch64_gnullvm 0.48.5", 784 | "windows_aarch64_msvc 0.48.5", 785 | "windows_i686_gnu 0.48.5", 786 | "windows_i686_msvc 0.48.5", 787 | "windows_x86_64_gnu 0.48.5", 788 | "windows_x86_64_gnullvm 0.48.5", 789 | "windows_x86_64_msvc 0.48.5", 790 | ] 791 | 792 | [[package]] 793 | name = "windows-targets" 794 | version = "0.52.6" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 797 | dependencies = [ 798 | "windows_aarch64_gnullvm 0.52.6", 799 | "windows_aarch64_msvc 0.52.6", 800 | "windows_i686_gnu 0.52.6", 801 | "windows_i686_gnullvm", 802 | "windows_i686_msvc 0.52.6", 803 | "windows_x86_64_gnu 0.52.6", 804 | "windows_x86_64_gnullvm 0.52.6", 805 | "windows_x86_64_msvc 0.52.6", 806 | ] 807 | 808 | [[package]] 809 | name = "windows_aarch64_gnullvm" 810 | version = "0.48.5" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 813 | 814 | [[package]] 815 | name = "windows_aarch64_gnullvm" 816 | version = "0.52.6" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 819 | 820 | [[package]] 821 | name = "windows_aarch64_msvc" 822 | version = "0.48.5" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 825 | 826 | [[package]] 827 | name = "windows_aarch64_msvc" 828 | version = "0.52.6" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 831 | 832 | [[package]] 833 | name = "windows_i686_gnu" 834 | version = "0.48.5" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 837 | 838 | [[package]] 839 | name = "windows_i686_gnu" 840 | version = "0.52.6" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 843 | 844 | [[package]] 845 | name = "windows_i686_gnullvm" 846 | version = "0.52.6" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 849 | 850 | [[package]] 851 | name = "windows_i686_msvc" 852 | version = "0.48.5" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 855 | 856 | [[package]] 857 | name = "windows_i686_msvc" 858 | version = "0.52.6" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 861 | 862 | [[package]] 863 | name = "windows_x86_64_gnu" 864 | version = "0.48.5" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 867 | 868 | [[package]] 869 | name = "windows_x86_64_gnu" 870 | version = "0.52.6" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 873 | 874 | [[package]] 875 | name = "windows_x86_64_gnullvm" 876 | version = "0.48.5" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 879 | 880 | [[package]] 881 | name = "windows_x86_64_gnullvm" 882 | version = "0.52.6" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 885 | 886 | [[package]] 887 | name = "windows_x86_64_msvc" 888 | version = "0.48.5" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 891 | 892 | [[package]] 893 | name = "windows_x86_64_msvc" 894 | version = "0.52.6" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 897 | 898 | [[package]] 899 | name = "wit-bindgen-rt" 900 | version = "0.39.0" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 903 | dependencies = [ 904 | "bitflags", 905 | ] 906 | 907 | [[package]] 908 | name = "zeroize" 909 | version = "1.8.1" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 912 | dependencies = [ 913 | "zeroize_derive", 914 | ] 915 | 916 | [[package]] 917 | name = "zeroize_derive" 918 | version = "1.4.2" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 921 | dependencies = [ 922 | "proc-macro2", 923 | "quote", 924 | "syn", 925 | ] 926 | 927 | [[package]] 928 | name = "zip" 929 | version = "2.6.1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" 932 | dependencies = [ 933 | "aes", 934 | "arbitrary", 935 | "bzip2", 936 | "constant_time_eq", 937 | "crc32fast", 938 | "crossbeam-utils", 939 | "getrandom", 940 | "hmac", 941 | "indexmap", 942 | "memchr", 943 | "pbkdf2", 944 | "sha1", 945 | "zeroize", 946 | "zstd", 947 | ] 948 | 949 | [[package]] 950 | name = "zstd" 951 | version = "0.13.3" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 954 | dependencies = [ 955 | "zstd-safe", 956 | ] 957 | 958 | [[package]] 959 | name = "zstd-safe" 960 | version = "7.2.4" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 963 | dependencies = [ 964 | "zstd-sys", 965 | ] 966 | 967 | [[package]] 968 | name = "zstd-sys" 969 | version = "2.0.15+zstd.1.5.7" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" 972 | dependencies = [ 973 | "cc", 974 | "pkg-config", 975 | ] 976 | --------------------------------------------------------------------------------