Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 42 additions & 19 deletions json.carp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ where the error was detected.")
(ExpectedColon [])
(ExpectedCommaOrEnd [])
(ExpectedStringKey [])
(TrailingContent []))
(TrailingContent [])
(DepthLimitExceeded []))

(doc ParseError "wraps a `ParseErrorKind` together with the byte position
in the input where the error was detected.")
Expand All @@ -93,6 +94,8 @@ neither of which is representable in JSON.")

(def json-idx 0)
(def json-len 0)
(def json-depth 0)
(def json-max-depth 128)
(def *parse-error* (the (Maybe JSON.ParseError) (Maybe.Nothing)))
(def *serialize-error* (the (Maybe JSON.SerializeError) (Maybe.Nothing)))
(def *json-indent-width* 0)
Expand Down Expand Up @@ -194,9 +197,9 @@ neither of which is representable in JSON.")
(do
(set! json-idx (Int.inc json-idx))
(let [cp (json-read-hex4 s)]
(if (json-failed?)
(break)
(if (and (>= cp 55296) (<= cp 56319))
(cond
(json-failed?) (break)
(and (>= cp 55296) (<= cp 56319))
(if (and (< (Int.inc json-idx) json-len)
(and (= (String.char-at s json-idx)
\\)
Expand All @@ -206,27 +209,27 @@ neither of which is representable in JSON.")
(do
(set! json-idx (+ json-idx 2))
(let [low (json-read-hex4 s)]
(if (json-failed?)
(break)
(if (and (>= low 56320) (<= low 57343))
(cond
(json-failed?) (break)
(and (>= low 56320) (<= low 57343))
(let [full-cp (+ (+ (* (- cp 55296)
1024)
(- low 56320))
65536)]
(json-push-codepoint! &bytes
full-cp))
(do
(json-set-err!
(JSON.ParseErrorKind.InvalidLowSurrogate))
(break))))))
(do
(json-set-err!
(JSON.ParseErrorKind.InvalidLowSurrogate))
(break)))))
(do
(json-set-err! (JSON.ParseErrorKind.LoneHighSurrogate))
(break)))
(if (and (>= cp 56320) (<= cp 57343))
(do
(json-set-err! (JSON.ParseErrorKind.LoneLowSurrogate))
(break))
(json-push-codepoint! &bytes cp))))))
(and (>= cp 56320) (<= cp 57343))
(do
(json-set-err! (JSON.ParseErrorKind.LoneLowSurrogate))
(break))
(json-push-codepoint! &bytes cp))))
(do
(json-set-err! (JSON.ParseErrorKind.InvalidEscape e))
(break))))))
Expand Down Expand Up @@ -389,8 +392,26 @@ neither of which is representable in JSON.")
(= c \t) (json-parse-true s)
(= c \f) (json-parse-false s)
(= c \n) (json-parse-null s)
(= c \[) (json-parse-array s)
(= c \{) (json-parse-object s)
(= c \[)
(do
(set! json-depth (Int.inc json-depth))
(let-do [result (if (> json-depth json-max-depth)
(do
(json-set-err! (JSON.ParseErrorKind.DepthLimitExceeded))
(JSON.Null))
(json-parse-array s))]
(set! json-depth (Int.dec json-depth))
result))
(= c \{)
(do
(set! json-depth (Int.inc json-depth))
(let-do [result (if (> json-depth json-max-depth)
(do
(json-set-err! (JSON.ParseErrorKind.DepthLimitExceeded))
(JSON.Null))
(json-parse-object s))]
(set! json-depth (Int.dec json-depth))
result))
(or (= c \-) (Char.num? c)) (json-parse-number s)
(do (json-set-err! (JSON.ParseErrorKind.UnexpectedChar c)) (JSON.Null)))))))

Expand All @@ -408,6 +429,7 @@ was detected. Use `JSON.parse-error-str` to format the error for display.")
(do
(set! json-idx 0)
(set! json-len (String.length s))
(set! json-depth 0)
(set! *parse-error* (the (Maybe JSON.ParseError) (Maybe.Nothing)))
(let-do [j (json-parse-value s)]
(unless-do (json-failed?)
Expand Down Expand Up @@ -446,7 +468,8 @@ message, without position information.")
(JSON.ParseErrorKind.ExpectedCommaOrEnd)
@"expected ',' or closing bracket"
(JSON.ParseErrorKind.ExpectedStringKey) @"expected string key"
(JSON.ParseErrorKind.TrailingContent) @"unexpected trailing content"))
(JSON.ParseErrorKind.TrailingContent) @"unexpected trailing content"
(JSON.ParseErrorKind.DepthLimitExceeded) @"nesting depth limit exceeded"))

(doc parse-error-str "formats a `ParseError` as a human-readable message
including the byte position where the error was detected.")
Expand Down
47 changes: 47 additions & 0 deletions test/json.carp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,53 @@

(assert-true test (parse-err? "'single'") "reject single-quoted string")

; =========================================================================
; Depth limit
; =========================================================================

; 128 nested arrays (at the limit) should succeed
(assert-true test
(let-do [s @""]
(for [i 0 128] (set! s (String.append &s "[")))
(set! s (String.append &s "1"))
(for [i 0 128] (set! s (String.append &s "]")))
(parse-ok? &s))
"parse arrays nested to exactly the depth limit")

; 129 nested arrays (one over) should fail
(assert-true test
(let-do [s @""]
(for [i 0 129] (set! s (String.append &s "[")))
(set! s (String.append &s "1"))
(for [i 0 129] (set! s (String.append &s "]")))
(parse-err? &s))
"reject arrays nested beyond the depth limit")

; Verify the error kind is DepthLimitExceeded
(assert-true test
(let-do [s @""]
(for [i 0 129] (set! s (String.append &s "[")))
(set! s (String.append &s "1"))
(for [i 0 129] (set! s (String.append &s "]")))
(match (err-kind &s)
(Maybe.Just (JSON.ParseErrorKind.DepthLimitExceeded)) true
_ false))
"excess nesting gives DepthLimitExceeded")

; Deeply nested objects should also be rejected
(assert-true test
(let-do [s @""]
(for [i 0 129] (set! s (String.append &s "{\"k\":")))
(set! s (String.append &s "1"))
(for [i 0 129] (set! s (String.append &s "}")))
(parse-err? &s))
"reject objects nested beyond the depth limit")

; Flat input is unaffected
(assert-true test
(parse-ok? "[1,2,3,4,5,6,7,8,9,10]")
"flat array is unaffected by depth limit")

; =========================================================================
; Serializer (str)
; =========================================================================
Expand Down