mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
M11: PDF/A verify reads a Named action's name through a reference (6.5.1)
A Named action whose /N refers to /NextPage (or another page move) is allowed and survives Save as PDF/A; the test checks that conversion keeps such an action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
This commit is contained in:
1 parent
33cd765828
commit
d9abc9b82c
2 files changed
+23
-4
No files matched your search
@@ -770,7 +770,7 @@ fn walk_chain(doc: &Document, a: &Dict, read: &mut usize, seen: &mut std::collec
|
||||
if *read > MAX_ACTION_CHAIN {
|
||||
return Some("too deeply chained".into());
|
||||
}
|
||||
if let Some(s) = action_forbidden(d) {
|
||||
if let Some(s) = action_forbidden(doc, d) {
|
||||
return Some(s);
|
||||
}
|
||||
match d.get(b"Next") {
|
||||
@@ -789,13 +789,15 @@ 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`].
|
||||
fn action_forbidden(d: &Dict) -> Option<String> {
|
||||
/// action for anything but the four of [`NAMED_ACTIONS`], its name given directly or by
|
||||
/// reference.
|
||||
fn action_forbidden(doc: &Document, d: &Dict) -> Option<String> {
|
||||
let s = d.name(b"S")?;
|
||||
if FORBIDDEN_ACTIONS.contains(&s) {
|
||||
return Some(String::from_utf8_lossy(s).into_owned());
|
||||
}
|
||||
match d.name(b"N") {
|
||||
let n = d.get(b"N").map(|n| doc.resolve(n));
|
||||
match n.as_deref().and_then(Object::as_name) {
|
||||
_ if s != b"Named" => None,
|
||||
Some(n) if NAMED_ACTIONS.contains(&n) => None,
|
||||
Some(n) => Some(format!("Named {}", String::from_utf8_lossy(n))),
|
||||
|
||||
@@ -501,6 +501,7 @@ fn set_state_no_op_and_named_actions_are_checked() {
|
||||
"<< /Type /Pages /Kids [] /Count 0 >>",
|
||||
&outline,
|
||||
"<< /Type /Outlines /First 3 0 R /Last 3 0 R /Count 1 >>",
|
||||
"/NextPage",
|
||||
],
|
||||
"",
|
||||
)
|
||||
@@ -517,10 +518,26 @@ fn set_state_no_op_and_named_actions_are_checked() {
|
||||
for allowed in ["NextPage", "PrevPage", "FirstPage", "LastPage"] {
|
||||
assert!(actions(&format!("<< /S /Named /N /{allowed} >>")).is_empty(), "{allowed}");
|
||||
}
|
||||
// The name given by reference (object 5 is /NextPage).
|
||||
assert!(actions("<< /S /Named /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);
|
||||
convert(&mut doc, Level::A2b).unwrap();
|
||||
assert!(verify(&doc, Level::A2b).iter().all(|i| i.clause != "6.5.1"), "{action}");
|
||||
}
|
||||
// A permitted Named action survives it, also one naming its page move by reference.
|
||||
for action in ["<< /S /Named /N /NextPage >>", "<< /S /Named /N 5 0 R >>"] {
|
||||
let mut doc = doc_with(action);
|
||||
convert(&mut doc, Level::A2b).unwrap();
|
||||
let outline = doc.get(ObjRef { num: 3, generation: 0 });
|
||||
let kept = outline
|
||||
.as_dict()
|
||||
.and_then(|d| d.get(b"A"))
|
||||
.and_then(Object::as_dict)
|
||||
.map(|a| (a.name(b"S").map(<[u8]>::to_vec), a.get(b"N").map(|n| doc.resolve(n))));
|
||||
let (s, n) = kept.unwrap_or_else(|| panic!("{action}: the action is gone"));
|
||||
assert_eq!(s.as_deref(), Some(&b"Named"[..]), "{action}");
|
||||
assert_eq!(n.as_deref().and_then(Object::as_name), Some(&b"NextPage"[..]), "{action}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user