fix(engine): fail an action whose information-removal step fails

A step that applies an edit logged any error and carried on, which is right
for a flatten with nothing to flatten but not for removing hidden
information or sanitizing: on a signed file the removal was refused, and the
unchanged file was written out as the action's successful result. Those
steps now fail the action unless there was nothing to remove.
This commit is contained in:
aiongg
2026-10-22 13:18:26 -04:00
committed by Dylan M. Taylor
parent 28ab616eb6
commit 73f11b2a29
2 changed files with 23 additions and 1 deletions
+5 -1
View File
@@ -191,9 +191,13 @@ fn apply_step(s: &mut Session, id: DocId, step: &Step, log: &mut Vec<String>) ->
return Ok(None);
}
};
// Steps that find nothing to do (no comments to flatten, …) aren't failures.
// Steps that find nothing to do (no comments to flatten, …) aren't failures. A removal step
// that didn't remove is: the file would be passed on as cleaned with the data still in it.
let removes = matches!(step, Step::RemoveHiddenInformation | Step::Sanitize);
match s.apply(id, edit) {
Ok(()) => log.push(step.label().to_string()),
Err(EditError::Redact(pdfcraft_redact::RedactError::NothingToApply)) if removes => log.push(format!("{}: nothing to remove", step.label())),
Err(e) if removes => return Err(e),
Err(e) => log.push(format!("{}: {e}", step.label())),
}
Ok(None)
+18
View File
@@ -4274,6 +4274,24 @@ fn actions_run_their_steps_on_files() {
assert!(actions::run_on(&custom, "bad.pdf", Arc::new(b"nope".to_vec()), |_, _| {}).is_err());
}
/// A removal step that fails (here: a signed file can't be rewritten without its hidden data)
/// fails the action, so the file is not passed on as cleaned.
#[test]
fn actions_fail_when_hidden_information_could_not_be_removed() {
let p12 = std::fs::read(concat!(env!("CARGO_MANIFEST_DIR"), "/../sign/tests/data/ec-p256.p12")).unwrap();
let digital_id = sign::pkcs12::open(&p12, "test").unwrap();
let mut s = Session::new().with_clock(|| 1_800_000_000);
let id = s.open("fixture.pdf", None, Arc::new(fixture(2)), None).unwrap();
s.apply(id, Edit::SetInfo { key: "Author".into(), value: "Private Name".into() }).unwrap();
let saved = s.save_bytes(id).unwrap();
s.mark_saved(id, saved, None).unwrap();
let signed = s.sign(id, &digital_id, SignOptions { page: 1, ..SignOptions::default() }).unwrap();
let built = actions::builtin();
let sanitize = built.iter().find(|a| a.name == "Sanitize Documents").unwrap();
let e = actions::run_on(sanitize, "signed.pdf", signed, |_, _| {}).map(|r| r.log).unwrap_err();
assert!(e.starts_with("Sanitize document:"), "{e}");
}
#[test]
fn exporting_office_files_keeps_images() {
let mut s = Session::new();