Skip to content

Commit 80c5153

Browse files
committed
Adds jsonCountDepthAndKeys.
1 parent a348b43 commit 80c5153

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package net.aholbrook.paseto.protocol
2+
3+
internal fun jsonCountDepthAndKeys(json: String): Pair<Int, Int> {
4+
var depth = 0
5+
var maxDepth = 0
6+
var keys = 0
7+
var inString = false
8+
var i = 0
9+
10+
while (i < json.length) {
11+
val c = json[i]
12+
13+
if (inString) {
14+
when (c) {
15+
'\\' -> ++i
16+
'"' -> inString = false
17+
}
18+
} else {
19+
when (c) {
20+
'"' -> inString = true
21+
'{', '[' -> {
22+
if (++depth > maxDepth) {
23+
maxDepth = depth
24+
}
25+
}
26+
'}', ']' -> {
27+
if (--depth < 0) {
28+
maxDepth = -1
29+
keys = -1
30+
break
31+
}
32+
}
33+
':' -> {
34+
++keys
35+
}
36+
}
37+
}
38+
39+
++i
40+
}
41+
42+
return Pair(maxDepth, keys)
43+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package net.aholbrook.paseto.protocol
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
import org.junit.jupiter.params.ParameterizedTest
6+
import org.junit.jupiter.params.provider.CsvSource
7+
import org.junit.jupiter.params.provider.ValueSource
8+
9+
class JsonCountDepthAndKeysTests {
10+
@ParameterizedTest
11+
@CsvSource(
12+
value = [
13+
"{} | 1",
14+
"[] | 1",
15+
"{{{}}{}} | 3",
16+
"{{}{{}}} | 3",
17+
"""{"a":1,"b":[1,2,{"c":true}]} | 3""",
18+
"""{"text":"{[not-structure]}","items":[{"x":"\\\""}]} | 3""",
19+
],
20+
delimiterString = " | ",
21+
)
22+
fun `valid payloads return expected depth`(payload: String, expectedDepth: Int) {
23+
jsonCountDepthAndKeys(payload).first shouldBe expectedDepth
24+
}
25+
26+
@ParameterizedTest
27+
@ValueSource(strings = [
28+
"}",
29+
"[]}",
30+
"{}]",
31+
"}[]",
32+
"][[[[[[]]]]]]",
33+
"[[[[[[]]]]]]]"
34+
])
35+
fun `corrupt json return invalid depth`(json: String) {
36+
jsonCountDepthAndKeys(json).first shouldBe -1
37+
}
38+
39+
@Test
40+
fun `deeply nested objects are handled`() {
41+
val depth = 128
42+
val payload = nestedObject(depth)
43+
44+
jsonCountDepthAndKeys(payload).first shouldBe depth
45+
}
46+
47+
@Test
48+
fun `deeply nested arrays are handled`() {
49+
val depth = 2000
50+
val payload = nestedArray(depth)
51+
52+
jsonCountDepthAndKeys(payload).first shouldBe depth
53+
}
54+
55+
private fun nestedObject(depth: Int): String {
56+
val prefix = "{\"x\":".repeat(depth)
57+
val suffix = "}".repeat(depth)
58+
return "$prefix 0 $suffix"
59+
}
60+
61+
private fun nestedArray(depth: Int): String {
62+
val prefix = "[".repeat(depth)
63+
val suffix = "]".repeat(depth)
64+
return "$prefix 0 $suffix"
65+
}
66+
}

0 commit comments

Comments
 (0)