fix(parsers): corriger récursion infinie CBZ↔CBR causant un stack overflow

analyze_cbz et analyze_cbr se rappelaient mutuellement sans garde quand
un fichier échouait les deux formats → stack overflow à l'analyse.
Ajout d'un paramètre allow_fallback=false pour briser la boucle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 23:15:35 +01:00
parent e73498cc60
commit 3e3e0154fa

View File

@@ -152,20 +152,21 @@ pub fn parse_metadata(
/// `pdf_render_scale`: max dimension used for PDF rasterization; 0 means use default (400). /// `pdf_render_scale`: max dimension used for PDF rasterization; 0 means use default (400).
pub fn analyze_book(path: &Path, format: BookFormat, pdf_render_scale: u32) -> Result<(i32, Vec<u8>)> { pub fn analyze_book(path: &Path, format: BookFormat, pdf_render_scale: u32) -> Result<(i32, Vec<u8>)> {
match format { match format {
BookFormat::Cbz => analyze_cbz(path), BookFormat::Cbz => analyze_cbz(path, true),
BookFormat::Cbr => analyze_cbr(path), BookFormat::Cbr => analyze_cbr(path, true),
BookFormat::Pdf => analyze_pdf(path, pdf_render_scale), BookFormat::Pdf => analyze_pdf(path, pdf_render_scale),
} }
} }
fn analyze_cbz(path: &Path) -> Result<(i32, Vec<u8>)> { fn analyze_cbz(path: &Path, allow_fallback: bool) -> Result<(i32, Vec<u8>)> {
let file = std::fs::File::open(path) let file = std::fs::File::open(path)
.with_context(|| format!("cannot open cbz: {}", path.display()))?; .with_context(|| format!("cannot open cbz: {}", path.display()))?;
let mut archive = match zip::ZipArchive::new(file) { let mut archive = match zip::ZipArchive::new(file) {
Ok(a) => a, Ok(a) => a,
Err(e) => { Err(e) => {
if allow_fallback {
// Some .cbz files are actually RAR archives with the wrong extension — fallback to CBR parser // Some .cbz files are actually RAR archives with the wrong extension — fallback to CBR parser
return analyze_cbr(path).map_err(|rar_err| { return analyze_cbr(path, false).map_err(|rar_err| {
anyhow::anyhow!( anyhow::anyhow!(
"invalid cbz archive and RAR fallback also failed for {}: ZIP={}, RAR={}", "invalid cbz archive and RAR fallback also failed for {}: ZIP={}, RAR={}",
path.display(), path.display(),
@@ -174,6 +175,8 @@ fn analyze_cbz(path: &Path) -> Result<(i32, Vec<u8>)> {
) )
}); });
} }
return Err(anyhow::anyhow!("invalid cbz archive for {}: {}", path.display(), e));
}
}; };
let mut image_names: Vec<String> = Vec::new(); let mut image_names: Vec<String> = Vec::new();
@@ -198,7 +201,7 @@ fn analyze_cbz(path: &Path) -> Result<(i32, Vec<u8>)> {
Ok((count, buf)) Ok((count, buf))
} }
fn analyze_cbr(path: &Path) -> Result<(i32, Vec<u8>)> { fn analyze_cbr(path: &Path, allow_fallback: bool) -> Result<(i32, Vec<u8>)> {
// Pass 1: list all image names via unrar (in-process, no subprocess) // Pass 1: list all image names via unrar (in-process, no subprocess)
let mut image_names: Vec<String> = { let mut image_names: Vec<String> = {
let archive = unrar::Archive::new(path) let archive = unrar::Archive::new(path)
@@ -209,8 +212,8 @@ fn analyze_cbr(path: &Path) -> Result<(i32, Vec<u8>)> {
Ok(a) => a, Ok(a) => a,
Err(e) => { Err(e) => {
let e_str = e.to_string(); let e_str = e.to_string();
if e_str.contains("Not a RAR archive") || e_str.contains("bad archive") { if allow_fallback && (e_str.contains("Not a RAR archive") || e_str.contains("bad archive")) {
return analyze_cbz(path).map_err(|zip_err| { return analyze_cbz(path, false).map_err(|zip_err| {
anyhow::anyhow!( anyhow::anyhow!(
"not a RAR archive and ZIP fallback also failed for {}: RAR={}, ZIP={}", "not a RAR archive and ZIP fallback also failed for {}: RAR={}, ZIP={}",
path.display(), path.display(),
@@ -371,7 +374,7 @@ fn is_image_name(name: &str) -> bool {
pub fn extract_first_page(path: &Path, format: BookFormat) -> Result<Vec<u8>> { pub fn extract_first_page(path: &Path, format: BookFormat) -> Result<Vec<u8>> {
match format { match format {
BookFormat::Cbz => extract_cbz_first_page(path), BookFormat::Cbz => extract_cbz_first_page(path),
BookFormat::Cbr => analyze_cbr(path).map(|(_, bytes)| bytes), BookFormat::Cbr => analyze_cbr(path, true).map(|(_, bytes)| bytes),
BookFormat::Pdf => analyze_pdf(path, 0).map(|(_, bytes)| bytes), BookFormat::Pdf => analyze_pdf(path, 0).map(|(_, bytes)| bytes),
} }
} }