M11: PDF/A verify reads an action's type through a reference (6.5.1)

An action whose /S refers to /Named or /NOP is checked like one that
names it directly, so verification reports it and conversion removes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-11-05 10:14:44 +08:00
1 parent d9abc9b82c
commit 75b82de0d5
2 files changed
+10 -3

No files matched your search

+4 -3
View File
@@ -789,10 +789,11 @@ fn walk_chain(doc: &Document, a: &Dict, read: &mut usize, seen: &mut std::collec
}
/// The forbidden kind of the action `d` itself, if it is one: a forbidden type, or a Named
/// action for anything but the four of [`NAMED_ACTIONS`], its name given directly or by
/// reference.
/// action for anything but the four of [`NAMED_ACTIONS`]. The type and the name may each be given
/// directly or by reference.
fn action_forbidden(doc: &Document, d: &Dict) -> Option<String> {
let s = d.name(b"S")?;
let s = doc.resolve(d.get(b"S")?);
let s = s.as_name()?;
if FORBIDDEN_ACTIONS.contains(&s) {
return Some(String::from_utf8_lossy(s).into_owned());
}
+6
View File
@@ -502,6 +502,8 @@ fn set_state_no_op_and_named_actions_are_checked() {
&outline,
"<< /Type /Outlines /First 3 0 R /Last 3 0 R /Count 1 >>",
"/NextPage",
"/Named",
"/NOP",
],
"",
)
@@ -520,6 +522,10 @@ fn set_state_no_op_and_named_actions_are_checked() {
}
// The name given by reference (object 5 is /NextPage).
assert!(actions("<< /S /Named /N 5 0 R >>").is_empty());
// The type given by reference (object 6 is /Named, 7 is /NOP).
assert_eq!(actions("<< /S 6 0 R /N /Print >>"), ["A Named Print action isn't allowed"]);
assert_eq!(actions("<< /S 7 0 R >>"), ["A NOP action isn't allowed"]);
assert!(actions("<< /S 6 0 R /N 5 0 R >>").is_empty());
// Save as PDF/A removes them.
for action in ["<< /S /SetState /T [] >>", "<< /S /NOP >>", "<< /S /Named /N /Print >>"] {
let mut doc = doc_with(action);