From c2ca03d33c1b3f83f7de6fd04f722b6a8fb68698 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 20:30:00 +0100 Subject: [PATCH] fix(indexer): reject truncated feeds --- crates/arr-indexer/src/search.rs | 18 ++++++++++++++---- .../tests/fixtures/broken-search.xml | 2 -- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/arr-indexer/src/search.rs b/crates/arr-indexer/src/search.rs index 277e28e..bbfd455 100644 --- a/crates/arr-indexer/src/search.rs +++ b/crates/arr-indexer/src/search.rs @@ -221,6 +221,7 @@ fn parse_releases(indexer_id: i64, body: &[u8]) -> Result, Se let mut item = None; let mut field = None; let mut releases = Vec::new(); + let mut depth = 0_u32; loop { match reader @@ -228,6 +229,7 @@ fn parse_releases(indexer_id: i64, body: &[u8]) -> Result, Se .map_err(|_| SearchError::InvalidResponse)? { Event::Start(element) => { + depth = depth.checked_add(1).ok_or(SearchError::InvalidResponse)?; let name = element.local_name(); if root.is_none() { root = Some(name.as_ref().to_vec()); @@ -268,6 +270,7 @@ fn parse_releases(indexer_id: i64, body: &[u8]) -> Result, Se } } Event::End(element) => { + depth = depth.checked_sub(1).ok_or(SearchError::InvalidResponse)?; if element.local_name().as_ref() == b"item" { if let Some(release) = item.take().and_then(|builder| builder.finish(indexer_id)) @@ -277,7 +280,8 @@ fn parse_releases(indexer_id: i64, body: &[u8]) -> Result, Se } field = None; } - Event::Eof => break, + Event::Eof if depth == 0 => break, + Event::Eof => return Err(SearchError::InvalidResponse), _ => {} } } @@ -298,15 +302,21 @@ fn read_element_attributes( let name = attribute(reader, element, b"name")?; let value = attribute(reader, element, b"value")?; match name.as_deref() { - Some("size") => builder.size = value.and_then(|value| value.parse().ok()), + Some("size") => { + builder.size = value.and_then(|value| value.parse().ok()).or(builder.size); + } Some("seeders" | "seed") => { - builder.seeders = value.and_then(|value| value.parse().ok()); + builder.seeders = value + .and_then(|value| value.parse().ok()) + .or(builder.seeders); } _ => {} } } b"enclosure" => { - builder.enclosure_url = attribute(reader, element, b"url")?; + if let Some(url) = attribute(reader, element, b"url")? { + builder.enclosure_url = Some(url); + } if builder.size.is_none() { builder.size = attribute(reader, element, b"length")?.and_then(|value| value.parse().ok()); diff --git a/crates/arr-indexer/tests/fixtures/broken-search.xml b/crates/arr-indexer/tests/fixtures/broken-search.xml index 28e8193..566a5d8 100644 --- a/crates/arr-indexer/tests/fixtures/broken-search.xml +++ b/crates/arr-indexer/tests/fixtures/broken-search.xml @@ -4,5 +4,3 @@ Broken tracker response broken-1 - -