mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 01:28:22 +00:00
Export for Screens: settings saved without their PDF passwords don't export unprotected
Follow-up to #5305. Stripping passwords from remembered settings made the next export of a password-protected PDF row write an unencrypted file without a word. The stripped settings now keep a non-secret passwordRequired flag, and an export refuses them until a password is given again (or security without the flag is given).
This commit is contained in:
committed by
bflatastic
parent
ac391b0f85
commit
1ce015de86
@@ -927,6 +927,7 @@ pub(crate) fn with_export_preset(s: &Session, cmd: &str, settings: &mut Map<Stri
|
||||
/// so do different files that would share a name, with or without the `name` pattern. The reply's
|
||||
/// `warnings` lists every warning the encoders give for the files, each once.
|
||||
pub(crate) fn export(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
check_passwords(p)?;
|
||||
let o = ScreenParams::of(p)?;
|
||||
let rows = rows_of(s, p, o.preset.as_deref())?;
|
||||
o.one_source()?;
|
||||
@@ -1280,18 +1281,46 @@ fn remember(s: &mut Session, p: &Value) {
|
||||
/// The PDF passwords an export may be given (`security` of a PDF row or of `settings.pdf`).
|
||||
const PASSWORD_KEYS: [&str; 2] = ["openPassword", "permissionsPassword"];
|
||||
|
||||
/// Left where [`remove_passwords`] took a password out: an export of these settings is refused
|
||||
/// until a password is given again ([`check_passwords`]), rather than written unprotected.
|
||||
const PASSWORD_REQUIRED: &str = "passwordRequired";
|
||||
|
||||
/// `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.
|
||||
/// the PDF settings themselves are saved without them. Where one was given, [`PASSWORD_REQUIRED`]
|
||||
/// says so.
|
||||
fn remove_passwords(settings: &mut Map<String, Value>) {
|
||||
let mut removed = false;
|
||||
for key in PASSWORD_KEYS {
|
||||
settings.remove(key);
|
||||
removed |= settings.remove(key).is_some_and(|v| v.as_str().is_none_or(|p| !p.is_empty()));
|
||||
}
|
||||
if removed {
|
||||
settings.insert(PASSWORD_REQUIRED.into(), Value::Bool(true));
|
||||
}
|
||||
for v in settings.values_mut() {
|
||||
remove_passwords_in(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// Refuse export params `v` that hold settings whose PDF passwords were left out
|
||||
/// ([`PASSWORD_REQUIRED`]) without a password given again.
|
||||
fn check_passwords(v: &Value) -> Result<()> {
|
||||
match v {
|
||||
Value::Object(m) => {
|
||||
let given = PASSWORD_KEYS.iter().any(|k| m.get(*k).and_then(Value::as_str).is_some_and(|p| !p.is_empty()));
|
||||
if m.get(PASSWORD_REQUIRED) == Some(&Value::Bool(true)) && !given {
|
||||
return Err(bad(
|
||||
C,
|
||||
"the PDF security settings were saved without their passwords: give openPassword or permissionsPassword again, or security without passwordRequired to export unprotected",
|
||||
));
|
||||
}
|
||||
m.values().try_for_each(check_passwords)
|
||||
}
|
||||
Value::Array(items) => items.iter().try_for_each(check_passwords),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_passwords_in(v: &mut Value) {
|
||||
match v {
|
||||
Value::Object(m) => remove_passwords(m),
|
||||
@@ -1376,5 +1405,6 @@ mod tests {
|
||||
assert!(!kept.to_string().contains("Password"), "{kept}");
|
||||
assert_eq!(kept["formats"][0]["security"]["printing"], "low");
|
||||
assert_eq!(kept["settings"]["pdf"]["security"]["copy"], false);
|
||||
assert_eq!(kept["formats"][0]["security"]["passwordRequired"], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,3 +454,21 @@ fn collecting_explicit_assets_rejects_invalid_ids_without_adding_anything() {
|
||||
assert_eq!(run(&mut s, "assets.add", json!({"ids": [b, a]}))["added"], 2);
|
||||
assert_eq!(assets(&mut s).len(), 2);
|
||||
}
|
||||
|
||||
/// Settings saved without the PDF passwords they were given export only once a password is given
|
||||
/// again, never as an unprotected PDF; security given without `passwordRequired` exports as given.
|
||||
#[test]
|
||||
fn settings_saved_without_their_pdf_passwords_refuse_to_export_until_given_again() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 10.0, 10.0, 20.0, 20.0);
|
||||
run(&mut s, "assets.add", json!({"ids": [a]}));
|
||||
let row = |security: Value| json!({"formats": [{"format": "pdf", "security": security}]});
|
||||
run(&mut s, "assets.settings.set", row(json!({"openPassword": "open sesame"})));
|
||||
let err = s.execute("assets.export", &json!({})).unwrap_err().to_string();
|
||||
assert!(err.contains("without their passwords"), "{err}");
|
||||
let encrypted = |r: &Value| files(r)[0].1.windows(8).any(|w| w == b"/Encrypt");
|
||||
let r = run(&mut s, "assets.export", row(json!({"openPassword": "open sesame", "passwordRequired": true})));
|
||||
assert!(encrypted(&r), "given again: protected");
|
||||
let r = run(&mut s, "assets.export", row(json!({})));
|
||||
assert!(!encrypted(&r), "left out on purpose: unprotected");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user