fix(parsers,api): skipper les entrées ZIP corrompues au lieu d'échouer

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 08:38:38 +01:00
parent efc2773199
commit 85e0945c9d
2 changed files with 25 additions and 13 deletions

View File

@@ -391,10 +391,13 @@ fn extract_cbz_page(abs_path: &str, page_number: u32, allow_fallback: bool) -> R
let mut image_names: Vec<String> = Vec::new(); let mut image_names: Vec<String> = Vec::new();
for i in 0..archive.len() { for i in 0..archive.len() {
let entry = archive.by_index(i).map_err(|e| { let entry = match archive.by_index(i) {
error!("Failed to read CBZ entry {} in {}: {}", i, abs_path, e); Ok(e) => e,
ApiError::internal(format!("cbz entry read failed: {e}")) Err(e) => {
})?; warn!("Skipping corrupted CBZ entry {} in {}: {}", i, abs_path, e);
continue;
}
};
let name = entry.name().to_ascii_lowercase(); let name = entry.name().to_ascii_lowercase();
if is_image_name(&name) { if is_image_name(&name) {
image_names.push(entry.name().to_string()); image_names.push(entry.name().to_string());

View File

@@ -181,7 +181,10 @@ fn analyze_cbz(path: &Path, allow_fallback: bool) -> Result<(i32, Vec<u8>)> {
let mut image_names: Vec<String> = Vec::new(); let mut image_names: Vec<String> = Vec::new();
for i in 0..archive.len() { 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(); let name = entry.name().to_ascii_lowercase();
if is_image_name(&name) { if is_image_name(&name) {
image_names.push(entry.name().to_string()); image_names.push(entry.name().to_string());
@@ -189,16 +192,22 @@ fn analyze_cbz(path: &Path, allow_fallback: bool) -> Result<(i32, Vec<u8>)> {
} }
image_names.sort_by(|a, b| natord::compare(a, b)); 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 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 entry = archive
.by_name(first_image)
.context("cannot read first image")?;
let mut buf = Vec::new(); let mut buf = Vec::new();
entry.read_to_end(&mut buf)?; if entry.read_to_end(&mut buf).is_ok() && !buf.is_empty() {
return Ok((count, 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). /// Fallback for ZIP files whose central directory can't be parsed (e.g. NTFS extra fields).