From 66e92e36985e34d09c8279997a3556cff66c2a63 Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Thu, 22 Oct 2026 15:32:43 +0100 Subject: [PATCH] #3692: formatting restriction refuses Copy Styles Finding 50. check_formatting blocked Update Styles and Attach Template with update, but not styles.copyFrom, which replaces style definitions (locked flag included) from another file. Under "limit formatting to a selection of styles" an editor could import changed formatting and unlock a locked style without the password. styles.copyFrom is now refused like Update Styles. copy_styles' callers are all blocked under the restriction, so its lock handling is left as is. Test: cmd::protect::tests::formatting_restriction_refuses_attach_template_update (extended). Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/engine/src/cmd/protect.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/protect.rs b/crates/engine/src/cmd/protect.rs index a1824a863a..5beb67eadb 100644 --- a/crates/engine/src/cmd/protect.rs +++ b/crates/engine/src/cmd/protect.rs @@ -406,6 +406,8 @@ fn check_formatting(s: &Session, id: &str, params: &Value) -> Result<(), CmdErro | "table.modifyStyle" | "table.deleteStyle" | "tools.updateStyles" + // Copies style definitions in from another file, locked flags included (#3692). + | "styles.copyFrom" ) // Attach Template updates the styles from it when asked (`linkStyles` is only a setting). || (id == "tools.attachTemplate" && p::bool(params, "update") == Some(true)); @@ -745,7 +747,8 @@ mod tests { } /// Formatting limited to styles: attaching a template refuses to update the styles from it - /// (it would bypass the Update Styles block); attaching without updating still works. + /// (it would bypass the Update Styles block), nor can Copy Styles; attaching without + /// updating still works. #[test] fn formatting_restriction_refuses_attach_template_update() { let mut t = Document::from_text("Template."); @@ -759,6 +762,12 @@ mod tests { let e = s.run("tools.attachTemplate", &json!({"path": "Report.dotx", "update": true, "data": data})).unwrap_err().to_string(); assert!(e.contains("formatting is limited"), "{e}"); assert_eq!(s.doc, before); + // Copying styles in replaces their definitions, locked flag included (#3692). + s.doc.styles.styles.iter_mut().filter(|st| st.id == "Heading1").for_each(|st| st.locked = true); + let before = s.doc.clone(); + let e = s.run("styles.copyFrom", &json!({"path": "Report.dotx", "styles": ["Heading 1"], "data": data})).unwrap_err().to_string(); + assert!(e.contains("formatting is limited"), "{e}"); + assert_eq!(s.doc, before); s.run("tools.attachTemplate", &json!({"path": "Report.dotx"})).unwrap(); assert_eq!(s.doc.settings.attached_template.as_deref(), Some("Report.dotx")); }