diff --git a/crates/automation/src/signing.rs b/crates/automation/src/signing.rs index 1a6440fd54..448a2d36d0 100644 --- a/crates/automation/src/signing.rs +++ b/crates/automation/src/signing.rs @@ -253,6 +253,11 @@ impl Automation { // A dynamic form's signing scripts run only when asked: what preSign changes is signed, // and the caller hasn't seen it. What they changed is listed. let run_scripts = a.opt_bool("run_form_scripts")?.unwrap_or(false); + // Every argument is read before the preSign scripts change anything, so a bad one is + // refused with the document as it was. + let reason = a.opt_str("reason")?.map(str::to_string); + let location = a.opt_str("location")?.map(str::to_string); + let contact = a.opt_str("contact")?.map(str::to_string); let signing = field.clone(); let scripted = signing.as_deref().is_some_and(|f| { self.session.sign_event_scripted(doc_id, f, SignEvent::Before) || self.session.sign_event_scripted(doc_id, f, SignEvent::After) @@ -278,19 +283,8 @@ impl Automation { } else { Vec::new() }; - let opts = SignOptions { - field, - page, - rect, - reason: a.opt_str("reason")?.map(str::to_string), - location: a.opt_str("location")?.map(str::to_string), - contact: a.opt_str("contact")?.map(str::to_string), - certify, - appearance, - appearance_labels, - graphic, - ..SignOptions::default() - }; + let opts = + SignOptions { field, page, rect, reason, location, contact, certify, appearance, appearance_labels, graphic, ..SignOptions::default() }; let signed = self.session.sign(doc_id, &id, opts).map_err(|e| match e { // A graphic the signer refuses is the caller's to fix. pdfcraft_engine::EditError::Sign(m) if m.starts_with("the signature graphic") => bad(m), diff --git a/crates/automation/tests/it/automation.rs b/crates/automation/tests/it/automation.rs index c50d3d53c2..4e75f6b588 100644 --- a/crates/automation/tests/it/automation.rs +++ b/crates/automation/tests/it/automation.rs @@ -5804,6 +5804,21 @@ fn signing_an_xfa_box_runs_its_signing_scripts_through_tools() { "{unasked}" ); let doc = ok(&mut a, "doc_open", json!({ "path": "form.pdf" }))["doc"].as_u64().unwrap(); + // A bad argument is refused before the preSign scripts run. + let bad = a.call( + "sign_document", + &json!({ "doc": doc, "id": "ada.p12", "password": "secret12", "field": name("sign"), "out": "bad.pdf", "run_form_scripts": true, "reason": 5 }), + ); + assert!(matches!(bad, Err(ToolError::InvalidArgs(_))), "{bad:?}"); + let now = ok(&mut a, "form_fields", json!({ "doc": doc })); + assert!( + now["fields"] + .as_array() + .unwrap() + .iter() + .any(|f| f["name"].as_str().is_some_and(|n| n == "when" || n.ends_with(".when[0]")) && f["value"].is_null()), + "{now}" + ); // A signed file that can't be written (`out` is a folder): not signed, and what preSign did // is taken back. std::fs::create_dir_all(dir.join("taken")).unwrap();