oficina / suite Public

Clone
git clone https://amadan.net/oficina/suite

Plain git — no account needed to clone.

Download

Download this file

1package suite
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "strings"
8)
9
10// rejectAmbiguousJSON refuses a body whose meaning depends on which duplicate
11// wins.
12//
13// encoding/json resolves a duplicate object key by keeping the last one, and
14// matches field names case-insensitively, so a sibling can send
15// `{"result":"NOT_VISIBLE","result":"VISIBLE"}` — or spell the second one
16// `RESULT` — and the value that survives is not the one a reader of the bytes
17// would expect. That defeats every check made *after* decoding, including S2's
18// duplicate-row rule: the duplicate is gone before the rule can see it.
19//
20// Keys are folded to lower case before comparison because that is how
21// encoding/json matches them. Two keys differing only in case are therefore
22// treated as the same key, which is the safe reading and costs nothing: no
23// call in this contract has two fields whose names differ only by case.
24func rejectAmbiguousJSON(data []byte) error {
25 dec := json.NewDecoder(bytes.NewReader(data))
26 if err := scanUnique(dec); err != nil {
27 return err
28 }
29 // Anything after the first complete value means the body is two documents
30 // wearing one Content-Length, and which one is "the" answer is a guess.
31 if dec.More() {
32 return fmt.Errorf("suite: trailing content after the JSON document")
33 }
34 return nil
35}
36
37func scanUnique(dec *json.Decoder) error {
38 tok, err := dec.Token()
39 if err != nil {
40 return err
41 }
42
43 delim, isDelim := tok.(json.Delim)
44 if !isDelim {
45 return nil // a scalar; nothing nested to check
46 }
47
48 switch delim {
49 case '{':
50 seen := make(map[string]bool)
51 for dec.More() {
52 keyTok, err := dec.Token()
53 if err != nil {
54 return err
55 }
56 key, ok := keyTok.(string)
57 if !ok {
58 return fmt.Errorf("suite: object key is not a string")
59 }
60 folded := strings.ToLower(key)
61 if seen[folded] {
62 return fmt.Errorf("suite: duplicate object key %q", key)
63 }
64 seen[folded] = true
65
66 if err := scanUnique(dec); err != nil {
67 return err
68 }
69 }
70 case '[':
71 for dec.More() {
72 if err := scanUnique(dec); err != nil {
73 return err
74 }
75 }
76 }
77
78 _, err = dec.Token() // the matching close
79 return err
80}
81