From d7590ebfbefe8d92d18c08adbc9665a57ef9b64c Mon Sep 17 00:00:00 2001 From: Marco Arnulfo <103679419+marcoarnulfo@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:38:01 +0200 Subject: [PATCH] fix(content): count an unreadable inline image with no EI as skipped (#1318) (#1418) Follow-up to #1404. An inline image whose parameters can't be read and that has no EI after them ran to the end of the stream as a BI op with no data, and those bytes were not counted. It is now skipped like one with an EI, its bytes counted in Parsed::skipped. A stream that ends inside the parameters (BI /W 1) still keeps its BI op. --- crates/content/src/lib.rs | 13 +++++++------ crates/content/src/tests.rs | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index 61845527..4a8f077f 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -149,7 +149,7 @@ pub fn parse(data: &[u8]) -> Parsed { } /// Read an inline image starting after `BI`: (parameters, data, position after `EI`). `Err` holds -/// the position after the `EI` of an image whose parameters can't be read. +/// the position after the `EI` (or the end of the stream) of an image whose parameters can't be read. fn inline_image(data: &[u8], pos: usize) -> Result<(Dict, Vec, usize), usize> { let mut lx = Lexer::new(data, pos); let mut dict = Dict::new(); @@ -164,11 +164,12 @@ fn inline_image(data: &[u8], pos: usize) -> Result<(Dict, Vec, usize), usize _ => None, }; let Some((k, v)) = entry else { - // Without an `EI` after it, the image runs to the end of the stream, as before. - return match end_of_image(data, at) { - Some((_, end)) => Err(end), - None => Ok((dict, Vec::new(), data.len())), - }; + // A stream that ends inside the parameters is cut short, not unreadable: keep what was read. + if at >= data.len() { + return Ok((dict, Vec::new(), data.len())); + } + // Without an `EI` after it, the image runs to the end of the stream. + return Err(end_of_image(data, at).map_or(data.len(), |(_, end)| end)); }; dict.set(k, v); } diff --git a/crates/content/src/tests.rs b/crates/content/src/tests.rs index 858c0e11..99f40d4e 100644 --- a/crates/content/src/tests.rs +++ b/crates/content/src/tests.rs @@ -62,6 +62,22 @@ fn an_unreadable_inline_image_is_skipped_to_its_end() { assert_eq!(control.ops.len(), 6); } +/// Issue #1318, follow-up: without an `EI`, an unreadable inline image runs to the end of the +/// stream, and those bytes are counted. A readable one whose data is cut short is kept, data and +/// all, so nothing of it is skipped. +#[test] +fn an_unreadable_inline_image_without_ei_is_counted() { + let p = parse(b"q BI /W 4 H 4 ID aa"); + let names: Vec<&[u8]> = p.ops.iter().map(|o| o.op.as_slice()).collect(); + assert_eq!(names, [&b"q"[..]]); + assert_eq!(p.skipped, b"BI /W 4 H 4 ID aa".len()); + let control = parse(b"q BI /W 4 /H 4 ID aa"); + assert_eq!(control.skipped, 0); + let names: Vec<&[u8]> = control.ops.iter().map(|o| o.op.as_slice()).collect(); + assert_eq!(names, [&b"q"[..], b"BI"]); + assert_eq!(control.ops[1].inline.as_ref().map(|(_, data)| data.as_slice()), Some(&b"aa"[..])); +} + #[test] fn matrices_compose_and_invert() { let a = Matrix([2.0, 0.0, 0.0, 3.0, 10.0, 20.0]);