Private data takes OPI links and alternate images (#6457)

This commit is contained in:
oracola91 authored and GitHub committed 2026-11-09 08:51:40 +08:00
1 parent d0083c09a4
commit 34f9bea0ad
2 files changed
+39 -5

No files matched your search

+13 -5
View File
@@ -28,7 +28,8 @@ pub enum Hidden {
Bookmarks,
/// Links, actions (open action, additional actions, what bookmarks run) and document JavaScript.
LinksActionsScripts,
/// Private data of other applications (`/PieceInfo`).
/// Private data of other applications (`/PieceInfo`), and prepress data on images and Forms
/// (`/OPI`, `/Alternates`).
PrivateData,
}
@@ -253,9 +254,16 @@ fn outline_action_count(doc: &Document, cat: &Dict) -> usize {
/// Entries that hold a category's data wherever they appear, not only on the catalog and pages:
/// XMP on images, fonts and Forms, private data on Forms, associated files on pages and
/// annotations, additional actions on fields.
const OWNED: [(Hidden, &[u8]); 4] =
[(Hidden::Metadata, b"Metadata"), (Hidden::PrivateData, b"PieceInfo"), (Hidden::Attachments, b"AF"), (Hidden::LinksActionsScripts, b"AA")];
/// annotations, additional actions on fields. Images and Forms also carry prepress data: an OPI
/// proxy's link to the original file, and alternate images, which can be the unedited original.
const OWNED: [(Hidden, &[u8]); 6] = [
(Hidden::Metadata, b"Metadata"),
(Hidden::PrivateData, b"PieceInfo"),
(Hidden::PrivateData, b"OPI"),
(Hidden::PrivateData, b"Alternates"),
(Hidden::Attachments, b"AF"),
(Hidden::LinksActionsScripts, b"AA"),
];
/// Keys whose dictionary values map names a producer chooses to objects (resources, glyph
/// procedures, named destinations, role and class maps, private data by application). A name
@@ -542,7 +550,7 @@ pub fn scan(doc: &Document) -> Vec<(Hidden, usize)> {
// A graph nested too deeply to walk counts as holding everything, so the removal runs and
// refuses it.
let held = owners(doc, &owned_keys).map_or_else(|_| vec![1; OWNED.len()], |o| o.0);
let owned = |h: Hidden| OWNED.iter().position(|o| o.0 == h).and_then(|i| held.get(i)).copied().unwrap_or(0);
let owned = |h: Hidden| OWNED.iter().zip(&held).filter(|(o, _)| o.0 == h).map(|(_, n)| n).sum::<usize>();
let mut doc2 = doc.clone();
HIDDEN
.into_iter()
+26
View File
@@ -4580,3 +4580,29 @@ fn a_redacted_form_xobject_drops_the_source_text_of_added_content() {
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");
}
/// Private data takes an image's prepress data too: its OPI link to the original file and its
/// alternate images, which can be the unedited original.
#[test]
fn private_data_takes_opi_links_and_alternate_images() {
use crate::sanitize::{Hidden, remove_hidden, scan};
let mut doc = pdf(vec![
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 4 0 R /Resources << /XObject << /Im 5 0 R /Fm 6 0 R >> >> >>".to_vec(),
stream("", b"q 50 0 0 50 20 20 cm /Im Do Q /Fm Do"),
stream(
"/Type /XObject /Subtype /Image /Width 11 /Height 1 /ColorSpace /DeviceGray /BitsPerComponent 8 /Alternates [<< /Image 7 0 R /DefaultForPrinting true >>]",
b"SHOWNIMAGE!",
),
stream("/Type /XObject /Subtype /Form /BBox [0 0 300 300] /OPI << /1.3 << /Type /OPI /Version 1.3 /F (OPISERVERPATH.tif) >> >>", b""),
stream("/Type /XObject /Subtype /Image /Width 17 /Height 1 /ColorSpace /DeviceGray /BitsPerComponent 8", b"ALTERNATEORIGINAL"),
]);
assert_eq!(scan(&doc).into_iter().find(|c| c.0 == Hidden::PrivateData), Some((Hidden::PrivateData, 2)));
assert_eq!(remove_hidden(&mut doc, &[Hidden::PrivateData]).unwrap(), [(Hidden::PrivateData, 2)]);
let bytes = write_full(&doc, &SaveOptions { object_streams: false, ..SaveOptions::default() }).unwrap();
let has = |needle: &[u8]| bytes.windows(needle.len()).any(|w| w == needle);
assert!(has(b"SHOWNIMAGE!"), "the image the page shows stays");
assert!(!has(b"ALTERNATEORIGINAL"), "the alternate image survived the save");
assert!(!has(b"OPISERVERPATH"), "the OPI link survived the save");
}