From 85e0945c9df3c4b564c4d971a1245a9bbef98c7c Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Fri, 13 Mar 2026 08:38:38 +0100 Subject: [PATCH] =?UTF-8?q?fix(parsers,api):=20skipper=20les=20entr=C3=A9e?= =?UTF-8?q?s=20ZIP=20corrompues=20au=20lieu=20d'=C3=A9chouer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Une seule entrée illisible dans le central directory ne doit pas bloquer l'analyse de tout le livre. Le count et la première page lisible sont retournés même si certaines entrées sont endommagées. Co-Authored-By: Claude Sonnet 4.6 --- apps/api/src/pages.rs | 11 +++++++---- crates/parsers/src/lib.rs | 27 ++++++++++++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/apps/api/src/pages.rs b/apps/api/src/pages.rs index 53e8059..206ce8a 100644 --- a/apps/api/src/pages.rs +++ b/apps/api/src/pages.rs @@ -391,10 +391,13 @@ fn extract_cbz_page(abs_path: &str, page_number: u32, allow_fallback: bool) -> R let mut image_names: Vec = Vec::new(); for i in 0..archive.len() { - let entry = archive.by_index(i).map_err(|e| { - error!("Failed to read CBZ entry {} in {}: {}", i, abs_path, e); - ApiError::internal(format!("cbz entry read failed: {e}")) - })?; + let entry = match archive.by_index(i) { + Ok(e) => e, + Err(e) => { + warn!("Skipping corrupted CBZ entry {} in {}: {}", i, abs_path, e); + continue; + } + }; let name = entry.name().to_ascii_lowercase(); if is_image_name(&name) { image_names.push(entry.name().to_string()); diff --git a/crates/parsers/src/lib.rs b/crates/parsers/src/lib.rs index b88480a..46b0405 100644 --- a/crates/parsers/src/lib.rs +++ b/crates/parsers/src/lib.rs @@ -181,7 +181,10 @@ fn analyze_cbz(path: &Path, allow_fallback: bool) -> Result<(i32, Vec)> { let mut image_names: Vec = Vec::new(); for i in 0..archive.len() { - let entry = archive.by_index(i).context("cannot read cbz entry")?; + let entry = match archive.by_index(i) { + Ok(e) => e, + Err(_) => continue, // skip corrupted entries + }; let name = entry.name().to_ascii_lowercase(); if is_image_name(&name) { image_names.push(entry.name().to_string()); @@ -189,16 +192,22 @@ fn analyze_cbz(path: &Path, allow_fallback: bool) -> Result<(i32, Vec)> { } image_names.sort_by(|a, b| natord::compare(a, b)); + if image_names.is_empty() { + return Err(anyhow::anyhow!("no images found in cbz: {}", path.display())); + } + + // Try images in order until one reads successfully (first pages can be corrupted too) let count = image_names.len() as i32; - let first_image = image_names.first().context("no images found in cbz")?; + for first_image in &image_names { + if let Ok(mut entry) = archive.by_name(first_image) { + let mut buf = Vec::new(); + if entry.read_to_end(&mut buf).is_ok() && !buf.is_empty() { + return Ok((count, buf)); + } + } + } - let mut entry = archive - .by_name(first_image) - .context("cannot read first image")?; - let mut buf = Vec::new(); - entry.read_to_end(&mut buf)?; - - Ok((count, buf)) + Err(anyhow::anyhow!("all entries unreadable in cbz: {}", path.display())) } /// Fallback for ZIP files whose central directory can't be parsed (e.g. NTFS extra fields).