mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
- pdf/src/encrypt.rs encrypts a written PDF with the standard security handler: 128-bit RC4 (PDF 1.4, revision 3; PDF 1.5, revision 4), 128-bit AES (1.6) or 256-bit AES (1.7/2.0, revision 6), every string and stream, plaintext XMP metadata when asked, the permission bits (printing none/low/high, five change levels, copy, screen readers). Crypto from RustCrypto (aes, md-5, sha2: MIT OR Apache-2.0, pure Rust, wasm). - PDF/A and PDF/X refuse passwords; equal passwords and passwords the algorithm can't take are refused. - Opening: the permissions password opens RC4/AES-128 files too; editing data comes back from an encrypted PDF opened with its password. - Save PDF › Security: the passwords, permissions and plaintext metadata, the encryption level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
3.5 KiB
Rust
59 lines
3.5 KiB
Rust
//! Opening password-protected PDFs: the open password or the permissions password unlocks them.
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_testkit::pdf::{PdfPage, pdf};
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn the_permissions_password_opens_a_protected_pdf_too() {
|
|
let mut s = Session::new();
|
|
// The test file's permissions (owner) password is its open password followed by `-owner`.
|
|
let locked = vectorcraft_format::base64_encode(&pdf(&[PdfPage::new(80.0, 40.0, "0 g 0 0 10 10 re f")], Some("pw")));
|
|
let info = |s: &mut Session, pw: &str| s.execute("document.pdfInfo", &json!({"dataBase64": locked, "password": pw})).unwrap();
|
|
assert_eq!(info(&mut s, "pw-owner")["pages"], 1);
|
|
assert_eq!(info(&mut s, "pw-owners")["wrongPassword"], true);
|
|
let open = |s: &mut Session, pw: &str| s.execute("document.open", &json!({"name": "locked.pdf", "dataBase64": locked, "password": pw}));
|
|
let e = open(&mut s, "nope").unwrap_err().to_string();
|
|
assert!(e.contains("password is wrong"), "{e}");
|
|
open(&mut s, "pw-owner").unwrap();
|
|
assert_eq!(s.doc().unwrap().doc.artboards[0].rect.width(), 80.0);
|
|
}
|
|
|
|
#[test]
|
|
fn export_with_passwords_writes_a_pdf_that_only_its_passwords_open() {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", &json!({"width": 100, "height": 80})).unwrap();
|
|
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 40, "height": 30})).unwrap();
|
|
for compatibility in ["1.4", "1.5", "1.6", "1.7", "2.0"] {
|
|
let p = json!({"compatibility": compatibility, "security": {"openPassword": "open", "permissionsPassword": "owner", "printing": "none"}});
|
|
let out = s.execute("document.exportPdf", &p).unwrap_or_else(|e| panic!("{p}: {e}"));
|
|
let data = out["dataBase64"].as_str().unwrap().to_string();
|
|
let bytes = vectorcraft_format::base64_decode(&data).unwrap();
|
|
assert!(String::from_utf8_lossy(&bytes).contains("/Encrypt"), "{compatibility}: not encrypted");
|
|
let info = |s: &mut Session, p: Value| s.execute("document.pdfInfo", &p).unwrap();
|
|
assert_eq!(info(&mut s, json!({"dataBase64": data}))["needsPassword"], true, "{compatibility}");
|
|
assert_eq!(info(&mut s, json!({"dataBase64": data, "password": "nope"}))["wrongPassword"], true, "{compatibility}");
|
|
for pw in ["open", "owner"] {
|
|
assert_eq!(info(&mut s, json!({"dataBase64": data, "password": pw}))["pages"], 1, "{compatibility} {pw}");
|
|
}
|
|
// The open password restores the editable document (Preserve Editing is on by default).
|
|
let mut r = Session::new();
|
|
r.execute("document.open", &json!({"name": "locked.pdf", "dataBase64": data, "password": "open"})).unwrap();
|
|
assert!(r.doc().unwrap().doc.node_count() >= 2, "{compatibility}: the art came back");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn permissions_without_a_password_warn_and_a_password_applies_them() {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", &json!({"width": 100, "height": 80})).unwrap();
|
|
let warns = |v: &Value| v["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("permissions need a password"));
|
|
let open = s.execute("document.exportPdf", &json!({"security": {"printing": "none"}})).unwrap();
|
|
assert!(warns(&open), "{open}");
|
|
let locked = s.execute("document.exportPdf", &json!({"security": {"permissionsPassword": "owner", "printing": "none"}})).unwrap();
|
|
assert!(!warns(&locked), "{locked}");
|
|
let bytes = vectorcraft_format::base64_decode(locked["dataBase64"].as_str().unwrap()).unwrap();
|
|
assert!(String::from_utf8_lossy(&bytes).contains("/Encrypt"));
|
|
}
|