From f0865193a8822217b8bf0e661165149362d16ecb Mon Sep 17 00:00:00 2001 From: VoiceLessQ Date: Thu, 29 Oct 2026 13:51:29 -0200 Subject: [PATCH] PDF import: a page's content listed in an array object is named as content A page's Contents can be an array written as an object of its own (/Contents 6 0 R). Its streams were named "a stream in it": the reference was compared with the stream, not the array's items. From the review of #4603. --- crates/engine/tests/import_fuzz.rs | 20 +++++++++++++++----- crates/pdf/src/pages.rs | 7 ++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/engine/tests/import_fuzz.rs b/crates/engine/tests/import_fuzz.rs index f6bf5e3a9..9459762a7 100644 --- a/crates/engine/tests/import_fuzz.rs +++ b/crates/engine/tests/import_fuzz.rs @@ -3850,8 +3850,8 @@ fn pdf_a_huge_form_drawn_over_and_over_is_refused_in_bounded_time() { #[test] fn a_stream_past_the_cap_is_named() { let image = "/Type /XObject /Subtype /Image /Width 10 /Height 10 /ColorSpace /DeviceRGB /BitsPerComponent 8"; - let page = |entries: &str| { - format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R /Resources << /XObject << /Im 5 0 R >> >> {entries} >>") + let page = |contents: &str, entries: &str| { + format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents {contents} /Resources << /XObject << /Im 5 0 R >> >> {entries} >>") .into_bytes() }; let plain = |content: &str| format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len() + 1).into_bytes(); @@ -3864,17 +3864,27 @@ fn a_stream_past_the_cap_is_named() { let files = [ ( "an image", - pdf(page(""), vec![plain("q 10 0 0 10 0 0 cm /Im Do Q"), packed_as_is(image, &spaces)]), + pdf(page("4 0 R", ""), vec![plain("q 10 0 0 10 0 0 cm /Im Do Q"), packed_as_is(image, &spaces)]), "an image of 10 × 10 px in it (object 5) would decode to more than 256 MB", ), ( "a page's content", - pdf(page(""), vec![packed_as_is("", &spaces), packed(image, "\0\0\0")]), + pdf(page("4 0 R", ""), vec![packed_as_is("", &spaces), packed(image, "\0\0\0")]), + "a page's content in it (object 4) would decode to more than 256 MB", + ), + ( + "a page's content in an array", + pdf(page("[4 0 R]", ""), vec![packed_as_is("", &spaces), packed(image, "\0\0\0")]), + "a page's content in it (object 4) would decode to more than 256 MB", + ), + ( + "a page's content in an array of its own", + pdf(page("6 0 R", ""), vec![packed_as_is("", &spaces), packed(image, "\0\0\0"), b"[4 0 R]".to_vec()]), "a page's content in it (object 4) would decode to more than 256 MB", ), ( "the editing data", - pdf(page(editing), vec![plain("0 0 5 5 re f"), packed(image, "\0\0\0"), packed_as_is("", &spaces)]), + pdf(page("4 0 R", editing), vec![plain("0 0 5 5 re f"), packed(image, "\0\0\0"), packed_as_is("", &spaces)]), "its editing data (object 6) would decode to more than 256 MB", ), ]; diff --git a/crates/pdf/src/pages.rs b/crates/pdf/src/pages.rs index 34ea963ce..a40b1c8ac 100644 --- a/crates/pdf/src/pages.rs +++ b/crates/pdf/src/pages.rs @@ -193,9 +193,10 @@ fn stream_kind(pdf: &Pdf, id: ObjectIdentifier, dict: &Dict<'_>) -> String { } let is = |m: Option>>| matches!(m, Some(MaybeRef::Ref(r)) if ObjectIdentifier::from(r) == id); let pages = pdf.pages(); - let content = pages.iter().any(|page| match page.raw().get_raw::>(b"Contents") { - Some(MaybeRef::NotRef(Object::Array(a))) => items(&a).any(|m| is(Some(m))), - other => is(other), + // `Contents` is a stream or an array of them, the array in the page or an object of its own. + let content = pages.iter().any(|page| match page.raw().get::>(b"Contents") { + Some(a) => items(&a).any(|m| is(Some(m))), + None => is(page.raw().get_raw::>(b"Contents")), }); if content { return "a page's content in it".to_string();