Skip to content

Commit e97395e

Browse files
authored
Handle negative zero and exponential numbers in Canonical JSON verification (#424)
1 parent 41497b7 commit e97395e

File tree

2 files changed

+196
-2
lines changed

2 files changed

+196
-2
lines changed

json.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ func verifyEnforcedCanonicalJSON(input []byte) error {
131131
valid = false
132132
return false
133133
}
134+
if value.Num != 0 && strings.ContainsRune(value.Raw, 'e') {
135+
valid = false
136+
return false
137+
}
138+
if value.Num == 0 && value.Raw == "-0" {
139+
valid = false
140+
return false
141+
}
134142
return true
135143
}
136144
res.ForEach(iter)
@@ -265,6 +273,10 @@ func CompactJSON(input, output []byte) []byte {
265273
// Skip over whitespace.
266274
continue
267275
}
276+
if c == '-' && input[i] == '0' {
277+
// Negative 0 is changed to '0', skip the '-'.
278+
continue
279+
}
268280
// Add the non-whitespace character to the output.
269281
output = append(output, c)
270282
if c == '"' {

json_test.go

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package gomatrixserverlib
1717

1818
import (
19+
"bytes"
1920
"encoding/json"
2021
"reflect"
2122
"testing"
@@ -78,9 +79,10 @@ func TestSortJSON(t *testing.T) {
7879
}
7980

8081
func testCompactJSON(t *testing.T, input, want string) {
81-
got := string(CompactJSON([]byte(input), nil))
82+
bytes := CompactJSON([]byte(input), nil)
83+
got := string(bytes)
8284
if got != want {
83-
t.Errorf("CompactJSON(%q):\n want: %q\n got: %q", input, want, got)
85+
t.Errorf("CompactJSON(%q):\n want: %q\n got: %q\n bytes: % X", input, want, got, bytes)
8486
}
8587
}
8688

@@ -106,12 +108,192 @@ func TestCompactJSON(t *testing.T) {
106108
testCompactJSON(t, `["\u0061\u005C\u0042\u0022"]`, `["a\\B\""]`)
107109
testCompactJSON(t, `["\u0120"]`, "[\"\u0120\"]")
108110
testCompactJSON(t, `["\u0FFF"]`, "[\"\u0FFF\"]")
111+
testCompactJSON(t, `["\u0FFf"]`, "[\"\u0FFF\"]")
109112
testCompactJSON(t, `["\u1820"]`, "[\"\u1820\"]")
110113
testCompactJSON(t, `["\uFFFF"]`, "[\"\uFFFF\"]")
111114
testCompactJSON(t, `["\uD842\uDC20"]`, "[\"\U00020820\"]")
112115
testCompactJSON(t, `["\uDBFF\uDFFF"]`, "[\"\U0010FFFF\"]")
113116

117+
// Unpaired UTF-16 surrogate pair
118+
testCompactJSON(t, `["\uDEAD"]`, "[\"\"]")
119+
120+
testCompactJSON(t, `["\\"]`, "[\"\\\\\"]")
121+
testCompactJSON(t, `"`, "\"")
122+
testCompactJSON(t, `["\b"]`, "[\"\\b\"]")
123+
testCompactJSON(t, `["\f"]`, "[\"\\f\"]")
124+
testCompactJSON(t, `["\n"]`, "[\"\\n\"]")
125+
testCompactJSON(t, `["\r"]`, "[\"\\r\"]")
126+
testCompactJSON(t, `["\t"]`, "[\"\\t\"]")
127+
128+
testCompactJSON(t, `"\u000a"`, "\"\\n\"")
129+
testCompactJSON(t, `"\u000A"`, "\"\\n\"")
130+
testCompactJSON(t, `"\u0022"`, "\"\\\"\"")
131+
testCompactJSON(t, `"\u005c"`, "\"\\\\\"")
132+
114133
testCompactJSON(t, `["\"\\\/"]`, `["\"\\/"]`)
134+
testCompactJSON(t, `["\/"]`, `["/"]`)
135+
}
136+
137+
func TestVerifyCanonical(t *testing.T) {
138+
tests := []struct {
139+
name string
140+
input []byte
141+
valid bool
142+
}{
143+
//{
144+
// name: "escaped unicode",
145+
// input: []byte(`{"\u00F1":0}`),
146+
// valid: false,
147+
//},
148+
//{
149+
// name: "long form unicode",
150+
// input: []byte(`{"\u0009":0}`),
151+
// valid: false,
152+
//},
153+
//{
154+
// name: "escaped unicode surrogate pair",
155+
// input: []byte(`{"\ud83d\udc08":0}`),
156+
// valid: false,
157+
//},
158+
{
159+
name: "negative zero",
160+
input: []byte(`{"a":-0}`),
161+
valid: false,
162+
},
163+
{
164+
name: "number out of bounds upper",
165+
input: []byte(`{"a":9007199254740992}`),
166+
valid: false,
167+
},
168+
{
169+
name: "number out of bounds lower",
170+
input: []byte(`{"a":-9007199254740992}`),
171+
valid: false,
172+
},
173+
{
174+
name: "exponential notation number",
175+
input: []byte(`{"a":1e5}`),
176+
valid: false,
177+
},
178+
{
179+
name: "fractional number",
180+
input: []byte(`{"a":1.5}`),
181+
valid: false,
182+
},
183+
//{
184+
// name: "unsorted keys",
185+
// input: []byte(`{"b":0,"a":1}`),
186+
// valid: false,
187+
//},
188+
//{
189+
// name: "unsorted keys in array",
190+
// input: []byte(`{"a":[{"b":0,"a":1},{"b":0,"a":1}]}`),
191+
// valid: false,
192+
//},
193+
//{
194+
// name: "unnecessary whitespace",
195+
// input: []byte(`{"a": 0}`),
196+
// valid: false,
197+
//},
198+
//{
199+
// name: "unpaired UTF-16 surrogate",
200+
// input: []byte(`{"a":"\uDEAD"}`),
201+
// valid: false,
202+
//},
203+
//{
204+
// name: "failure combo",
205+
// input: []byte(`{ "\u00F1": -0, "2": 0, "1": 9007199254740991, "3":1e5, "4": [{"2": 0, "1": 0},{"2": 0, "1": 0}] }`),
206+
// valid: false,
207+
//},
208+
{
209+
name: "canonical JSON",
210+
input: []byte(`{"1":9007199254740991,"2":0,"3":-9007199254740991,"4":[{"1":0,"2":0},{"1":0,"2":0}],"ñ":0}`),
211+
valid: true,
212+
},
213+
//{
214+
// name: "duplicate keys",
215+
// input: []byte(`{"a":0,"a":1}`),
216+
// valid: false,
217+
//},
218+
//{
219+
// name: "nested duplicate keys",
220+
// input: []byte(`{"a":[{"a":0,"a":1}]}`),
221+
// valid: false,
222+
//},
223+
}
224+
for _, tt := range tests {
225+
t.Run(tt.name, func(t *testing.T) {
226+
_, err := EnforcedCanonicalJSON(tt.input, RoomVersionV11)
227+
228+
if !tt.valid && err == nil {
229+
t.Fatalf("JSON passes canonical check when it shouldn't. \n Original: %s (% X)", tt.input, tt.input)
230+
}
231+
if tt.valid && err != nil {
232+
t.Fatalf("JSON doesn't pass canonical check when it should. \n Original: %s (% X)", tt.input, tt.input)
233+
}
234+
})
235+
}
236+
}
237+
238+
func TestCanonicalConversion(t *testing.T) {
239+
tests := []struct {
240+
name string
241+
input []byte
242+
canonical []byte
243+
}{
244+
{
245+
name: "escaped unicode",
246+
input: []byte(`{"\u00F1":0}`),
247+
canonical: []byte(`{"ñ":0}`),
248+
},
249+
{
250+
name: "escaped unicode surrogate pair",
251+
input: []byte(`{"\ud83d\udc08":0}`),
252+
canonical: []byte(`{"🐈":0}`),
253+
},
254+
{
255+
name: "negative zero",
256+
input: []byte(`{"a":-0}`),
257+
canonical: []byte(`{"a":0}`),
258+
},
259+
//{
260+
// name: "exponential notation number",
261+
// input: []byte(`{"a":1e5}`),
262+
// canonical: []byte(`{"a":100000}`),
263+
//},
264+
{
265+
name: "unsorted keys",
266+
input: []byte(`{"b":0,"a":1}`),
267+
canonical: []byte(`{"a":1,"b":0}`),
268+
},
269+
{
270+
name: "unsorted keys in array",
271+
input: []byte(`{"a":[{"b":0,"a":1},{"b":0,"a":1}]}`),
272+
canonical: []byte(`{"a":[{"a":1,"b":0},{"a":1,"b":0}]}`),
273+
},
274+
{
275+
name: "unnecessary whitespace",
276+
input: []byte(`{"a": 0}`),
277+
canonical: []byte(`{"a":0}`),
278+
},
279+
{
280+
name: "conversion combo",
281+
input: []byte(`{ "\u00F1": -0, "2": 0, "1": 9007199254740991, "4": [{"2": 0, "1": 0},{"2": 0, "1": 0}] }`),
282+
canonical: []byte(`{"1":9007199254740991,"2":0,"4":[{"1":0,"2":0},{"1":0,"2":0}],"ñ":0}`),
283+
},
284+
}
285+
for _, tt := range tests {
286+
t.Run(tt.name, func(t *testing.T) {
287+
gmslCanonical, err := CanonicalJSON(tt.input)
288+
if err != nil {
289+
t.Fatalf("Failed parsing json: %s", err.Error())
290+
}
291+
292+
if !bytes.Equal(tt.canonical, gmslCanonical) {
293+
t.Fatalf("GMSL canonical JSON is not canonical. \n Original: %s (% X) \nGMSL Canonical: %s (% X) \n Expected Form: %s (% X)", tt.input, tt.input, gmslCanonical, gmslCanonical, tt.canonical, tt.canonical)
294+
}
295+
})
296+
}
115297
}
116298

117299
func TestCompactUnicodeEscapeWithUTF16Surrogate(t *testing.T) {

0 commit comments

Comments
 (0)