diff --git a/Cargo.lock b/Cargo.lock index d92e4b2ab..c8c2538c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,7 +1202,7 @@ dependencies = [ "tempfile", "test-strategy", "time", - "unrar", + "unrar-ng", "xz2", "zip", "zstd", @@ -1830,22 +1830,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] -name = "unrar" -version = "0.5.8" +name = "unrar-ng" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ec61343a630d2b50d13216dea5125e157d3fc180a7d3f447d22fe146b648fc" +checksum = "7881a7cefee603cdd23e6acd424ee62c64e088d39688f60d20649d978de55f41" dependencies = [ "bitflags 2.8.0", "regex", - "unrar_sys", + "unrar-ng-sys", "widestring", ] [[package]] -name = "unrar_sys" -version = "0.5.8" +name = "unrar-ng-sys" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b77675b883cfbe6bf41e6b7a5cd6008e0a83ba497de3d96e41a064bbeead765" +checksum = "afa06861a15d69012d3c1035510e9ca416031ae37b6d27bb7e3ab1a2984e1230" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index 7ae38f409..38e179190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ strum = { version = "0.28.0", features = ["derive"] } tar = "0.4.42" tempfile = "3.10.1" time = { version = "0.3.36", default-features = false } -unrar = { version = "0.5.7", optional = true } +unrar = { package = "unrar-ng", version = "0.7.6", optional = true } xz2 = "0.1.7" zip = { version = "6", default-features = false, features = [ "time", diff --git a/src/archive/rar.rs b/src/archive/rar.rs index 5c92af413..acc55575b 100644 --- a/src/archive/rar.rs +++ b/src/archive/rar.rs @@ -1,14 +1,17 @@ //! Contains RAR-specific building and unpacking functions -use std::path::Path; +use std::path::{Path, PathBuf}; -use unrar::Archive; +use unrar::{ + Archive, ExtractEvent, + error::{Code, UnrarError, When}, +}; use crate::{ - error::{Error, Result}, + error::{Error, FinalError, Result}, info, list::{FileInArchive, ListFileType}, - utils::BytesFmt, + utils::{BytesFmt, PathFmt}, }; /// Unpacks the archive given by `archive_path` into the folder given by `output_folder`. @@ -19,24 +22,46 @@ pub fn unpack_archive(archive_path: &Path, output_folder: &Path, password: Optio None => Archive::new(archive_path), }; - let mut archive = archive.open_for_processing()?; - let mut files_unpacked = 0; + let archive = archive.open_for_processing()?; + + let mut files_unpacked: u64 = 0; + let mut first_err: Option<(PathBuf, i32)> = None; - while let Some(header) = archive.read_header()? { - let entry = header.entry(); - archive = if entry.is_file() { + let cb_result = archive.extract_all_with_callback(output_folder, |event| match event { + ExtractEvent::Ok { filename, size } => { + info!("extracted ({}) {}", BytesFmt(size), PathFmt(&filename)); + files_unpacked += 1; + true + } + ExtractEvent::Err { filename, error_code } => { + first_err = Some((filename, error_code)); + // Returning false cancels the rest of the extraction so any + // additional per-file errors don't get silently swallowed. + false + } + ExtractEvent::LargeDictWarning { + dict_size_kb, + max_dict_size_kb, + } => { info!( - "extracted ({}) {}", - BytesFmt(entry.unpacked_size), - entry.filename.display(), + "archive requires {} KiB dictionary; this build supports up to {} KiB", + dict_size_kb, max_dict_size_kb, ); - files_unpacked += 1; - header.extract_with_base(output_folder)? - } else { - header.skip()? - }; - } + // Reject the oversized dictionary so the DLL fails the + // operation with Code::LargeDict instead of silently + // proceeding with a result it cannot actually produce. + false + } + _ => true, + }); + if let Some((path, code)) = first_err { + let inner = UnrarError::from(Code::from(code), When::Process).to_string(); + return Err(Error::Custom { + reason: FinalError::with_title(format!("failed to extract {}", PathFmt(&path))).detail(inner), + }); + } + let _status = cb_result?; Ok(files_unpacked) } diff --git a/src/error.rs b/src/error.rs index c05e93190..688d6612d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -249,7 +249,7 @@ impl From for Error { impl From for Error { fn from(err: unrar::error::UnrarError) -> Self { Self::Custom { - reason: FinalError::with_title("Unexpected error in rar archive").detail(format!("{:?}", err.code)), + reason: FinalError::with_title("Unexpected error in rar archive").detail(err.to_string()), } } }