diff --git a/crates/engine/src/cmd/fileio/screens.rs b/crates/engine/src/cmd/fileio/screens.rs index 19381709a..b6fe4d85a 100644 --- a/crates/engine/src/cmd/fileio/screens.rs +++ b/crates/engine/src/cmd/fileio/screens.rs @@ -698,6 +698,8 @@ fn preset_settings(s: &Session, cmd: &str, given: &Map) -> Result // Refuse what an export would refuse, now rather than when the preset is used. check_settings(s, &Value::Object(settings.clone())).map_err(|e| bad(cmd, e.to_string()))?; Folders::of(&ScreenParams::of(&Value::Object(settings.clone()))?).map_err(|e| bad(cmd, e.to_string()))?; + let mut settings = settings; + remove_passwords(&mut settings); Ok(settings) } @@ -1275,9 +1277,33 @@ fn remember(s: &mut Session, p: &Value) { let _ = store_settings(s, settings); } +/// The PDF passwords an export may be given (`security` of a PDF row or of `settings.pdf`). +const PASSWORD_KEYS: [&str; 2] = ["openPassword", "permissionsPassword"]; + +/// `settings` without the PDF passwords anywhere in them: settings kept in the document (which +/// native saves write) or in the preferences hold the export's choices, never its secrets, as +/// the PDF settings themselves are saved without them. +fn remove_passwords(settings: &mut Map) { + for key in PASSWORD_KEYS { + settings.remove(key); + } + for v in settings.values_mut() { + remove_passwords_in(v); + } +} + +fn remove_passwords_in(v: &mut Value) { + match v { + Value::Object(m) => remove_passwords(m), + Value::Array(items) => items.iter_mut().for_each(remove_passwords_in), + _ => {} + } +} + /// Make `settings` the document's export settings. Not an undo step (like the settings of any /// export); the document has changed only if they differ. -pub(crate) fn store_settings(s: &mut Session, settings: Map) -> Result<()> { +pub(crate) fn store_settings(s: &mut Session, mut settings: Map) -> Result<()> { + remove_passwords(&mut settings); let st = s.doc_mut()?; if st.doc.export_settings != settings { Arc::make_mut(&mut st.doc).export_settings = settings; @@ -1314,6 +1340,7 @@ mod tests { assert_eq!(out.claim("same.png", "last").as_deref(), Some("same-20001.png")); } + #[test] fn size_parse_refuses_signs_and_exponents() { for s in ["+2x", "+2", "+100w", "+100h", "+72ppi", "-2x", "1e2", "2e0w"] { @@ -1334,4 +1361,21 @@ mod tests { assert_eq!(safe_file_name("lpt9 .png"), "lpt9-.png"); assert_eq!(safe_file_name("My CON .png"), "My CON .png"); } + + /// The settings an export leaves in the document keep its PDF security choices but not its + /// passwords, in a row or in `settings.pdf`. + #[test] + fn stored_settings_leave_out_pdf_passwords() { + let mut s = crate::Session::new(); + s.execute("file.new", &serde_json::json!({"width": 100, "height": 100})).unwrap(); + let given = serde_json::json!({ + "formats": [{"format": "pdf", "security": {"openPassword": "open sesame", "permissionsPassword": "owner", "printing": "low"}}], + "settings": {"pdf": {"security": {"openPassword": "open sesame", "copy": false}}}, + }); + super::store_settings(&mut s, given.as_object().unwrap().clone()).unwrap(); + let kept = serde_json::Value::Object(s.doc().unwrap().doc.export_settings.clone()); + assert!(!kept.to_string().contains("Password"), "{kept}"); + assert_eq!(kept["formats"][0]["security"]["printing"], "low"); + assert_eq!(kept["settings"]["pdf"]["security"]["copy"], false); + } }