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).
pub fn analyze_book(path: &Path, format: BookFormat, pdf_render_scale: u32) -> Result<(i32, Vec<u8>)> {
match format {
BookFormat::Cbz => analyze_cbz(path),
BookFormat::Cbr => analyze_cbr(path),
BookFormat::Cbz => analyze_cbz(path, true),
BookFormat::Cbr => analyze_cbr(path, true),
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)
.with_context(|| format!("cannot open cbz: {}", path.display()))?;
let mut archive = match zip::ZipArchive::new(file) {
Ok(a) => a,
Err(e) => {
if allow_fallback {
// 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!(
"invalid cbz archive and RAR fallback also failed for {}: ZIP={}, RAR={}",
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();
@@ -198,7 +201,7 @@ fn analyze_cbz(path: &Path) -> Result<(i32, Vec<u8>)> {
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)
let mut image_names: Vec<String> = {
let archive = unrar::Archive::new(path)
@@ -209,8 +212,8 @@ fn analyze_cbr(path: &Path) -> Result<(i32, Vec<u8>)> {
Ok(a) => a,
Err(e) => {
let e_str = e.to_string();
if e_str.contains("Not a RAR archive") || e_str.contains("bad archive") {
return analyze_cbz(path).map_err(|zip_err| {
if allow_fallback && (e_str.contains("Not a RAR archive") || e_str.contains("bad archive")) {
return analyze_cbz(path, false).map_err(|zip_err| {
anyhow::anyhow!(
"not a RAR archive and ZIP fallback also failed for {}: RAR={}, ZIP={}",
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>> {
match format {
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),
}
}