fix(redact): rewritten form XObjects drop /PCAdded (#5868)

Page content streams rewritten by redaction or hidden-content removal
already dropped /PCAdded, the source text of an added item, but a form
XObject whose content was rewritten kept its original dictionary,
/PCAdded included, so the removed text stayed readable in the saved file.
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-02 23:31:39 -05:00
1 parent 661ded72bb
commit 69ddcd89bf
2 files changed
+31

No files matched your search

+5
View File
@@ -861,6 +861,11 @@ fn xobject(
}
let mut dict = s.dict.clone();
dict.remove(b"Length");
if new_data.is_some() {
// As for page content streams: the source text of an added item (Edit ▸ Add
// content) would keep what the rewrite removed.
dict.remove(b"PCAdded");
}
let mut cleaned_res = res.clone();
// Install rewritten nested forms before finding live property names: a new form may
// still inherit its caller's resources, and its new Do name must resolve here.
+26
View File
@@ -3871,3 +3871,29 @@ fn a_reply_on_another_page_to_a_removed_popup_goes_too() {
assert!(!bytes.windows(gone.len()).any(|w| w == gone), "{} survived", String::from_utf8_lossy(gone));
}
}
#[test]
fn a_rewritten_form_xobject_drops_the_source_text_of_added_content() {
// A form drawing invisible text whose dictionary repeats it in /PCAdded.
let form = stream(
"/Type /XObject /Subtype /Form /BBox [0 0 300 300] /Resources << /Font << /F1 5 0 R >> >> /PCMark /Added /PCAdded << /Kind /Text /Text (LEAK_FORM_SOURCE) >>",
b"BT /F1 10 Tf 3 Tr 10 100 Td (HIDDEN) Tj ET BT /F1 10 Tf 10 200 Td (Shown) Tj ET",
);
let mut doc = one_page(b"/X Do", "/XObject << /X 7 0 R >>", vec![form]);
sanitize::remove_hidden(&mut doc, &[sanitize::Hidden::HiddenText]).unwrap();
let bytes = write_full(&doc, &SaveOptions { object_streams: false, ..SaveOptions::default() }).unwrap();
assert!(!bytes.windows(16).any(|w| w == b"LEAK_FORM_SOURCE"), "the form's added-content source text survived");
}
#[test]
fn a_redacted_form_xobject_drops_the_source_text_of_added_content() {
let form = stream(
"/Type /XObject /Subtype /Form /BBox [0 0 300 300] /Resources << /Font << /F1 5 0 R >> >> /PCMark /Added /PCAdded << /Kind /Text /Text (AB1234CD) >>",
b"BT /F1 10 Tf 10 100 Td (AB1234CD) Tj ET",
);
let mut doc = one_page(b"/X Do", "/XObject << /X 7 0 R >>", vec![form]);
mark(&mut doc, 0, &[[20.0, 95.0, 40.0, 110.0]], "");
apply(&mut doc, None).unwrap();
let bytes = write_full(&doc, &SaveOptions { object_streams: false, ..SaveOptions::default() }).unwrap();
assert!(!bytes.windows(4).any(|w| w == b"1234"), "redacted text left in the saved file");
}