From 6523a49fb7cf75e1ce402228ceaf7fe4d026c243 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:16:09 +0000 Subject: [PATCH] Reject invalid layout index in master.deleteLayout and renameLayout Found while testing crates/engine/src/cmd/design.rs:1353 (layout_target), used by delete_layout at :1368 and rename_layout at :1356 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/design.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/design.rs b/crates/engine/src/cmd/design.rs index 08c5398c..f104526c 100644 --- a/crates/engine/src/cmd/design.rs +++ b/crates/engine/src/cmd/design.rs @@ -1344,17 +1344,17 @@ fn insert_layout(s: &mut Session, p: &Value) -> Result { }) } -fn layout_target(s: &Session, p: &Value) -> Result<(usize, usize)> { +fn layout_target(s: &Session, p: &Value, cmd: &str) -> Result<(usize, usize)> { let st = s.doc()?; let (m, l) = match st.selection.target { Target::Layout { master, layout } => (master, layout), _ => (master_index(&st.doc, &st.selection), 0), }; - Ok((usize_param(p, "master").unwrap_or(m), usize_param(p, "layout").unwrap_or(l))) + Ok((opt_usize_param(p, "master", cmd)?.unwrap_or(m), opt_usize_param(p, "layout", cmd)?.unwrap_or(l))) } fn rename_layout(s: &mut Session, p: &Value) -> Result { - let (mi, li) = layout_target(s, p)?; + let (mi, li) = layout_target(s, p, "master.renameLayout")?; let name = str_param(p, "name").ok_or_else(|| bad("master.renameLayout", "missing `name`"))?.to_string(); s.edit(|doc, _| { let l = @@ -1366,7 +1366,7 @@ fn rename_layout(s: &mut Session, p: &Value) -> Result { } fn delete_layout(s: &mut Session, p: &Value) -> Result { - let (mi, li) = layout_target(s, p)?; + let (mi, li) = layout_target(s, p, "master.deleteLayout")?; s.edit(|doc, sel| { let id = doc.masters.get(mi).and_then(|m| m.layouts.get(li)).map(|l| l.id).ok_or_else(|| bad("master.deleteLayout", "no such layout"))?; if doc.slides.iter().any(|x| x.layout == id) { @@ -1619,6 +1619,25 @@ mod tests { } } + #[test] + fn layout_commands_reject_invalid_index() { + let mut s = Session::with_new(); + s.execute("master.insertLayout", &json!({})).unwrap(); + let count = |s: &Session| s.doc().unwrap().doc.masters.iter().map(|m| m.layouts.len()).sum::(); + let names = |s: &Session| s.doc().unwrap().doc.masters.iter().flat_map(|m| m.layouts.iter().map(|l| l.name.clone())).collect::>(); + let n = count(&s); + let before = names(&s); + for bad in [json!(-1), json!(1.5)] { + assert!(s.execute("master.deleteLayout", &json!({"layout": bad})).is_err()); + assert!(s.execute("master.deleteLayout", &json!({"master": bad})).is_err()); + assert!(s.execute("master.renameLayout", &json!({"layout": bad, "name": "x"})).is_err()); + assert_eq!(count(&s), n); + assert_eq!(names(&s), before); + } + s.execute("master.deleteLayout", &json!({})).unwrap(); + assert_eq!(count(&s), n - 1); + } + #[test] fn civil_dates() { assert_eq!(super::civil(0), (1970, 1, 1));