From 3ce51a34875325ec68ae0476f6f04f96d792c208 Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Sun, 11 Oct 2026 07:07:22 +0700 Subject: [PATCH] fix(width): validate explicit width-point removal indices Reapply on current upstream main. --- crates/engine/src/cmd/distortcmds.rs | 20 ++++++++++++++++---- crates/engine/src/tests_widthpoints.rs | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/distortcmds.rs b/crates/engine/src/cmd/distortcmds.rs index 34027e70..508c066a 100644 --- a/crates/engine/src/cmd/distortcmds.rs +++ b/crates/engine/src/cmd/distortcmds.rs @@ -323,10 +323,22 @@ fn width_point_copy(s: &mut Session, p: &Value) -> Result { fn width_point_remove(s: &mut Session, p: &Value) -> Result { const C: &str = "stroke.widthPoint.remove"; let id = id_param(p, "id").ok_or_else(|| bad(C, "missing id"))?; - let mut indices: Vec = match (p.get("indices").and_then(Value::as_array), p.get("index").and_then(Value::as_u64)) { - (Some(a), _) => a.iter().filter_map(Value::as_u64).map(|i| i as usize).collect(), - (None, Some(i)) => vec![i as usize], - (None, None) => return Err(bad(C, "missing index")), + let mut indices: Vec = match p.get("indices") { + Some(Value::Array(values)) => values + .iter() + .map(|value| { + let id = value.as_u64().ok_or_else(|| bad(C, format!("invalid width point index {value}: expected an integer")))?; + usize::try_from(id).map_err(|_| bad(C, format!("width point index {id} is out of range"))) + }) + .collect::>()?, + Some(_) => return Err(bad(C, "indices must be an array of non-negative integers")), + None => match p.get("index") { + Some(value) => { + let id = value.as_u64().ok_or_else(|| bad(C, "index must be a non-negative integer"))?; + vec![usize::try_from(id).map_err(|_| bad(C, "index is out of range"))?] + } + None => return Err(bad(C, "missing index")), + }, }; indices.sort_unstable(); indices.dedup(); diff --git a/crates/engine/src/tests_widthpoints.rs b/crates/engine/src/tests_widthpoints.rs index de287868..a2132778 100644 --- a/crates/engine/src/tests_widthpoints.rs +++ b/crates/engine/src/tests_widthpoints.rs @@ -30,6 +30,31 @@ fn dark(s: &Session, x: u32, y: u32) -> bool { vectorcraft_render::Renderer::new().render(&doc, 200, 200, vectorcraft_geom::Affine::IDENTITY, &opts).pixel(x, y)[0] < 128 } +/// Invalid entries in an explicit multi-point delete must not delete the valid entries. +#[test] +fn invalid_width_point_indices_leave_the_entire_profile_unchanged() { + let (mut s, id) = line_session(); + set(&mut s, json!({"id": id.0, "t": 0.5, "left": 10, "right": 10})); + let before = points(&s, id); + let history = s.doc().unwrap().history.undo.len(); + for bad in [ + json!({"indices": [1, "bad"]}), + json!({"indices": [-1, 1]}), + json!({"indices": [1.5, 1]}), + json!({"indices": {"point": 1}}), + json!({"index": "bad"}), + ] { + let mut params = bad.as_object().unwrap().clone(); + params.insert("id".into(), json!(id.0)); + let err = s.execute("stroke.widthPoint.remove", &serde_json::Value::Object(params)); + assert!(err.is_err(), "accepted malformed width point indices"); + assert_eq!(points(&s, id), before); + assert_eq!(s.doc().unwrap().history.undo.len(), history); + } + s.execute("stroke.widthPoint.remove", &json!({"id": id.0, "indices": [1]})).unwrap(); + assert_eq!(points(&s, id).len(), before.len() - 1); +} + #[test] fn a_point_moved_onto_another_makes_a_discontinuous_point_that_renders_a_step() { let (mut s, id) = line_session();