mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-28 10:11:58 +01:00
Reject overflowing slice rectangles (#768)
* fix(engine): reject overflowing slice rectangles (#715) * test(engine): cover slice rectangle overflow (#715) * refactor(engine): keep slice rect validation explicit (#715) * test(engine): exercise overflowing slice commands (#715) * fix(engine): reject invalid slice.set geometry before mutation
This commit is contained in:
@@ -126,15 +126,20 @@ pub(crate) fn refresh(s: &mut Session) {
|
||||
|
||||
pub(crate) fn rect_param(p: &Value) -> Option<Rect> {
|
||||
let f = |v: &Value| v.as_f64().filter(|x| x.is_finite()).map(|x| x.round() as i32);
|
||||
let rect = |x: i32, y: i32, w: i32, h: i32| {
|
||||
if w <= 0 || h <= 0 {
|
||||
return None;
|
||||
}
|
||||
Some(Rect::new(x, y, x.checked_add(w)?, y.checked_add(h)?))
|
||||
};
|
||||
if let Some(a) = p.get("rect").and_then(Value::as_array).filter(|a| a.len() == 4) {
|
||||
let v: Vec<i32> = a.iter().filter_map(f).collect();
|
||||
if v.len() == 4 && v[2] > 0 && v[3] > 0 {
|
||||
return Some(Rect::new(v[0], v[1], v[0] + v[2], v[1] + v[3]));
|
||||
if v.len() == 4 {
|
||||
return rect(v[0], v[1], v[2], v[3]);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let (x, y, w, h) = (f(p.get("x")?)?, f(p.get("y")?)?, f(p.get("width")?)?, f(p.get("height")?)?);
|
||||
(w > 0 && h > 0).then(|| Rect::new(x, y, x + w, y + h))
|
||||
rect(f(p.get("x")?)?, f(p.get("y")?)?, f(p.get("width")?)?, f(p.get("height")?)?)
|
||||
}
|
||||
|
||||
fn hex_color(s: &str) -> Option<[u8; 4]> {
|
||||
@@ -289,6 +294,9 @@ fn from_guides(s: &mut Session) -> Result<Value> {
|
||||
|
||||
fn set_slice(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "slice.set";
|
||||
if ["rect", "x", "y", "width", "height"].iter().any(|key| p.get(key).is_some()) && rect_param(p).is_none() {
|
||||
return Err(bad(cmd, "give a finite rectangle with positive dimensions and representable corners"));
|
||||
}
|
||||
let id = target(s, p, cmd, true)?;
|
||||
let coalesce_into_promote = s.active().is_some_and(|d| d.history.undo_label() == Some("Promote to User Slice")) && p.get("number").is_some();
|
||||
let label = "Slice Options";
|
||||
|
||||
@@ -50,6 +50,19 @@ fn user_slices_and_options() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rect_param_rejects_corner_overflow() {
|
||||
assert_eq!(rect_param(&json!({"rect": [1, 2, 3, 4]})), Some(Rect::new(1, 2, 4, 6)));
|
||||
assert_eq!(rect_param(&json!({"x": 1, "y": 2, "width": 3, "height": 4})), Some(Rect::new(1, 2, 4, 6)));
|
||||
assert_eq!(rect_param(&json!({"rect": [i32::MAX, 0, 1, 1]})), None);
|
||||
assert_eq!(rect_param(&json!({"x": 0, "y": i32::MAX, "width": 1, "height": 1})), None);
|
||||
|
||||
let mut s = session(8);
|
||||
assert!(s.execute("slice.new", json!({"rect": [i32::MAX, 0, 1, 1]})).is_err());
|
||||
let id = s.execute("slice.new", json!({"rect": [1, 1, 8, 8]})).unwrap()["slice"].as_u64().unwrap();
|
||||
assert!(s.execute("slice.set", json!({"slice": id, "rect": [0, i32::MAX, 1, 1]})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promote_auto_slice_and_divide() {
|
||||
let mut s = session(8);
|
||||
|
||||
Reference in New Issue
Block a user