mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 03:44:41 +00:00
fix(engine): require modify for console field-property changes and optimizing, copy for structure bookmarks, refuse read-only image fields
This commit is contained in:
@@ -27,7 +27,6 @@ pub(crate) fn check_permission(edit: &Edit, p: &pdfcraft_cos::Permissions) -> Re
|
||||
| Edit::SetBookmarkPage { .. }
|
||||
| Edit::SetBookmarkColor { .. }
|
||||
| Edit::SetBookmarkStyle { .. }
|
||||
| Edit::BookmarksFromStructure
|
||||
| Edit::NumberPages { .. } => {
|
||||
if p.assemble() {
|
||||
Ok(())
|
||||
@@ -37,7 +36,7 @@ pub(crate) fn check_permission(edit: &Edit, p: &pdfcraft_cos::Permissions) -> Re
|
||||
}
|
||||
// Creates bookmarks (assembly), and their titles are the document's text, copied out of it
|
||||
// (Table 22, bit 5), as an export of it would be.
|
||||
Edit::BookmarksFromTextStyles { .. } => {
|
||||
Edit::BookmarksFromTextStyles { .. } | Edit::BookmarksFromStructure => {
|
||||
if !p.assemble() {
|
||||
Err(EditError::NotPermitted("page changes"))
|
||||
} else if !p.copy() {
|
||||
@@ -594,6 +593,10 @@ pub(crate) fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut E
|
||||
ran.map_err(EditError::Invalid)?;
|
||||
}
|
||||
Edit::SetFieldImage { name, image } => {
|
||||
// As filling a value does: a read-only field keeps its picture.
|
||||
if pdfcraft_forms::fields(doc).iter().any(|f| f.name == *name && f.read_only()) {
|
||||
return Err(pdfcraft_forms::FormError::ReadOnly(name.clone()).into());
|
||||
}
|
||||
let (img, _) = embed_image(doc, name, image)?;
|
||||
let px = match &*doc.get(img) {
|
||||
pdfcraft_cos::Object::Stream(s) => (s.dict.int(b"Width").unwrap_or(1) as u32, s.dict.int(b"Height").unwrap_or(1) as u32),
|
||||
|
||||
@@ -298,6 +298,12 @@ impl Session {
|
||||
};
|
||||
let mut o = pdfcraft_js::run(script, &event, &runner.doc, &states, &runner.doc_scripts, Limits::default());
|
||||
let changes = changes(&states, &o.changed);
|
||||
// Console input is the user's, not the document's: making a field read-only, required,
|
||||
// hidden or shown is a change to the form (Table 22, bit 4), which filling it in is not.
|
||||
let properties = |c: &FieldChange| c.read_only.is_some() || c.required.is_some() || c.display.is_some();
|
||||
if target.is_none() && changes.iter().any(properties) && !doc.allows_modification() {
|
||||
return Err(EditError::NotPermitted("changes to the document"));
|
||||
}
|
||||
let resets: Vec<Vec<String>> = o
|
||||
.requests
|
||||
.iter()
|
||||
|
||||
@@ -64,6 +64,10 @@ impl Session {
|
||||
return Err(EditError::Signed);
|
||||
}
|
||||
let editor = doc.editor.as_ref().ok_or_else(|| EditError::ReadOnly(doc.read_only_reason.clone().unwrap_or_default()))?;
|
||||
// A rewrite that takes data out of the document, as Remove Hidden Information does.
|
||||
if !doc.allows_modification() {
|
||||
return Err(EditError::NotPermitted("changes to the document"));
|
||||
}
|
||||
Ok(OptimizeJob {
|
||||
cos: editor.cos.clone(),
|
||||
settings: settings.clone(),
|
||||
|
||||
@@ -10251,3 +10251,39 @@ fn image_transform_engine_refuses_singular_matrices_without_mutation() {
|
||||
assert_eq!(s.get(id).unwrap().bytes, before);
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 22, bit 4 (modify) withheld while forms may be filled in: the console's script may
|
||||
/// set values, but not make a field read-only, required or hidden, which the property editor
|
||||
/// refuses too; optimizing (a rewrite that removes data) is refused; bookmarks from the
|
||||
/// structure copy the headings' text out, so they need copying (bit 5) as the text-style
|
||||
/// bookmarks do.
|
||||
#[test]
|
||||
fn fill_only_documents_refuse_property_changes_from_the_console_and_optimizing() {
|
||||
let encrypt = |bytes: Vec<u8>, permissions: i32| {
|
||||
let mut doc = pdfcraft_cos::Document::open(Arc::new(bytes)).unwrap();
|
||||
doc.set_encryption(&pdfcraft_cos::NewEncryption {
|
||||
algorithm: pdfcraft_cos::Algorithm::Aes256,
|
||||
user_password: "",
|
||||
owner_password: "owner",
|
||||
permissions,
|
||||
encrypt_metadata: true,
|
||||
seed: [7; 32],
|
||||
})
|
||||
.unwrap();
|
||||
Arc::new(pdfcraft_cos::write_full(&doc, &Default::default()).unwrap())
|
||||
};
|
||||
let refused = Err(EditError::NotPermitted("changes to the document"));
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
let id = s.open("order.pdf", None, encrypt(scripted_form(), -1 ^ 0b1000), None).unwrap();
|
||||
assert!(s.get(id).unwrap().allows_form_filling() && !s.get(id).unwrap().allows_modification());
|
||||
assert_eq!(s.run_javascript(id, "getField('total').readonly = true;", None).map(|_| ()), refused);
|
||||
assert_eq!(s.run_javascript(id, "getField('total').display = display.hidden;", None).map(|_| ()), refused);
|
||||
assert!(!s.get(id).unwrap().form.iter().find(|f| f.name == "total").unwrap().read_only(), "nothing changed");
|
||||
s.run_javascript(id, "getField('price').value = 3;", None).unwrap();
|
||||
assert_eq!(s.get(id).unwrap().form.iter().find(|f| f.name == "price").unwrap().value, ["3"], "values may be filled in");
|
||||
assert_eq!(s.optimize_job(id, &optimize::Settings::default(), &[]).map(|_| ()), refused);
|
||||
|
||||
let id = s.open("tagged.pdf", None, encrypt(tagged_headings_fixture(), -1 ^ 0b1_0000), None).unwrap();
|
||||
assert_eq!(s.apply(id, Edit::BookmarksFromStructure), Err(EditError::NotPermitted("copying text and images")));
|
||||
assert!(s.get(id).unwrap().info.outline.is_empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user