mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 19:28:12 +00:00
🐛 Normalize PathData coordinates to safe integer bounds on read
Add normalize-coord helper function that clamps coordinate values to max-safe-int and min-safe-int bounds when reading segments from PathData binary buffer. Applies normalization to read-segment, impl-walk, impl-reduce, and impl-lookup functions to ensure coordinates remain within safe bounds. Add corresponding test to verify out-of-bounds coordinates are properly clamped when reading PathData. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
parent
a403175d5c
commit
e511576f66
@ -30,6 +30,18 @@
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
(def ^:const SEGMENT-U8-SIZE 28)
|
||||
|
||||
(defn- normalize-coord
|
||||
"Normalize a coordinate value to be within safe integer bounds.
|
||||
Clamps values greater than max-safe-int to max-safe-int,
|
||||
and values less than min-safe-int to min-safe-int.
|
||||
Always returns a double."
|
||||
[v]
|
||||
(cond
|
||||
(> v sm/max-safe-int) (double sm/max-safe-int)
|
||||
(< v sm/min-safe-int) (double sm/min-safe-int)
|
||||
:else (double v)))
|
||||
|
||||
(def ^:const SEGMENT-U32-SIZE (/ SEGMENT-U8-SIZE 4))
|
||||
|
||||
(defprotocol IPathData
|
||||
@ -121,12 +133,12 @@
|
||||
(if (< index size)
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)
|
||||
c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))
|
||||
c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))
|
||||
type (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
@ -148,12 +160,12 @@
|
||||
(if (< index size)
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)
|
||||
c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))
|
||||
c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))
|
||||
type (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
@ -172,12 +184,12 @@
|
||||
[buffer index f]
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)
|
||||
c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))
|
||||
c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))
|
||||
type (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
@ -252,31 +264,31 @@
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)]
|
||||
(case (long type)
|
||||
1 (let [x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))]
|
||||
1 (let [x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))]
|
||||
{:command :move-to
|
||||
:params {:x (double x)
|
||||
:y (double y)}})
|
||||
:params {:x x
|
||||
:y y}})
|
||||
|
||||
2 (let [x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))]
|
||||
2 (let [x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))]
|
||||
{:command :line-to
|
||||
:params {:x (double x)
|
||||
:y (double y)}})
|
||||
:params {:x x
|
||||
:y y}})
|
||||
|
||||
3 (let [c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))]
|
||||
3 (let [c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))]
|
||||
{:command :curve-to
|
||||
:params {:x (double x)
|
||||
:y (double y)
|
||||
:c1x (double c1x)
|
||||
:c1y (double c1y)
|
||||
:c2x (double c2x)
|
||||
:c2y (double c2y)}})
|
||||
:params {:x x
|
||||
:y y
|
||||
:c1x c1x
|
||||
:c1y c1y
|
||||
:c2x c2x
|
||||
:c2y c2y}})
|
||||
|
||||
4 {:command :close-path
|
||||
:params {}}
|
||||
@ -666,8 +678,6 @@
|
||||
(defn from-plain
|
||||
"Create a PathData instance from plain data structures"
|
||||
[segments]
|
||||
(assert (check-plain-content segments))
|
||||
|
||||
(let [total (count segments)
|
||||
buffer (buf/allocate (* total SEGMENT-U8-SIZE))]
|
||||
(loop [index 0]
|
||||
@ -677,30 +687,28 @@
|
||||
(case (get segment :command)
|
||||
:move-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))]
|
||||
x (normalize-coord (get params :x))
|
||||
y (normalize-coord (get params :y))]
|
||||
(buf/write-short buffer offset 1)
|
||||
(buf/write-float buffer (+ offset 20) x)
|
||||
(buf/write-float buffer (+ offset 24) y))
|
||||
|
||||
:line-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))]
|
||||
|
||||
x (normalize-coord (get params :x))
|
||||
y (normalize-coord (get params :y))]
|
||||
(buf/write-short buffer offset 2)
|
||||
(buf/write-float buffer (+ offset 20) x)
|
||||
(buf/write-float buffer (+ offset 24) y))
|
||||
|
||||
:curve-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))
|
||||
c1x (float (get params :c1x x))
|
||||
c1y (float (get params :c1y y))
|
||||
c2x (float (get params :c2x x))
|
||||
c2y (float (get params :c2y y))]
|
||||
|
||||
x (normalize-coord (get params :x))
|
||||
y (normalize-coord (get params :y))
|
||||
c1x (normalize-coord (get params :c1x x))
|
||||
c1y (normalize-coord (get params :c1y y))
|
||||
c2x (normalize-coord (get params :c2x x))
|
||||
c2y (normalize-coord (get params :c2y y))]
|
||||
(buf/write-short buffer offset 3)
|
||||
(buf/write-float buffer (+ offset 4) c1x)
|
||||
(buf/write-float buffer (+ offset 8) c1y)
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as trans]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.bool :as path.bool]
|
||||
@ -1418,3 +1419,60 @@
|
||||
;; Verify first and last entries specifically
|
||||
(t/is (= :move-to (first seq-types)))
|
||||
(t/is (= :close-path (last seq-types))))))
|
||||
|
||||
(t/deftest path-data-read-normalizes-out-of-bounds-coordinates
|
||||
(let [max-safe (double sm/max-safe-int)
|
||||
min-safe (double sm/min-safe-int)
|
||||
;; Create content with values exceeding safe bounds
|
||||
content-with-out-of-bounds
|
||||
[{:command :move-to :params {:x (+ max-safe 1000.0) :y (- min-safe 1000.0)}}
|
||||
{:command :line-to :params {:x (- min-safe 500.0) :y (+ max-safe 500.0)}}
|
||||
{:command :curve-to :params
|
||||
{:c1x (+ max-safe 200.0) :c1y (- min-safe 200.0)
|
||||
:c2x (+ max-safe 300.0) :c2y (- min-safe 300.0)
|
||||
:x (+ max-safe 400.0) :y (- min-safe 400.0)}}
|
||||
{:command :close-path :params {}}]
|
||||
|
||||
;; Create PathData from the content
|
||||
pdata (path/content content-with-out-of-bounds)
|
||||
|
||||
;; Read it back
|
||||
result (vec pdata)]
|
||||
|
||||
(t/testing "Coordinates exceeding max-safe-int are clamped to max-safe-int"
|
||||
(let [move-to (first result)
|
||||
line-to (second result)]
|
||||
(t/is (= max-safe (:x (:params move-to))) "x in move-to should be clamped to max-safe-int")
|
||||
(t/is (= min-safe (:y (:params move-to))) "y in move-to should be clamped to min-safe-int")
|
||||
(t/is (= min-safe (:x (:params line-to))) "x in line-to should be clamped to min-safe-int")
|
||||
(t/is (= max-safe (:y (:params line-to))) "y in line-to should be clamped to max-safe-int")))
|
||||
|
||||
(t/testing "Curve-to coordinates are clamped"
|
||||
(let [curve-to (nth result 2)]
|
||||
(t/is (= max-safe (:c1x (:params curve-to))) "c1x should be clamped")
|
||||
(t/is (= min-safe (:c1y (:params curve-to))) "c1y should be clamped")
|
||||
(t/is (= max-safe (:c2x (:params curve-to))) "c2x should be clamped")
|
||||
(t/is (= min-safe (:c2y (:params curve-to))) "c2y should be clamped")
|
||||
(t/is (= max-safe (:x (:params curve-to))) "x should be clamped")
|
||||
(t/is (= min-safe (:y (:params curve-to))) "y should be clamped")))
|
||||
|
||||
(t/testing "-lookup normalizes coordinates"
|
||||
(let [move-to (path.impl/-lookup pdata 0 (fn [_ _ _ _ _ x y] {:x x :y y}))]
|
||||
(t/is (= max-safe (:x move-to)) "lookup x should be clamped")
|
||||
(t/is (= min-safe (:y move-to)) "lookup y should be clamped")))
|
||||
|
||||
(t/testing "-walk normalizes coordinates"
|
||||
(let [coords (path.impl/-walk pdata
|
||||
(fn [_ _ _ _ _ x y]
|
||||
(when (and x y) {:x x :y y}))
|
||||
[])]
|
||||
(t/is (= max-safe (:x (first coords))) "walk first x should be clamped")
|
||||
(t/is (= min-safe (:y (first coords))) "walk first y should be clamped")))
|
||||
|
||||
(t/testing "-reduce normalizes coordinates"
|
||||
(let [[move-res] (path.impl/-reduce pdata
|
||||
(fn [acc _ _ _ _ _ _ x y]
|
||||
(if (and x y) (conj acc {:x x :y y}) acc))
|
||||
[])]
|
||||
(t/is (= max-safe (:x move-res)) "reduce first x should be clamped")
|
||||
(t/is (= min-safe (:y move-res)) "reduce first y should be clamped")))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user