package suite import ( "bytes" "encoding/json" "fmt" "strings" ) // rejectAmbiguousJSON refuses a body whose meaning depends on which duplicate // wins. // // encoding/json resolves a duplicate object key by keeping the last one, and // matches field names case-insensitively, so a sibling can send // `{"result":"NOT_VISIBLE","result":"VISIBLE"}` — or spell the second one // `RESULT` — and the value that survives is not the one a reader of the bytes // would expect. That defeats every check made *after* decoding, including S2's // duplicate-row rule: the duplicate is gone before the rule can see it. // // Keys are folded to lower case before comparison because that is how // encoding/json matches them. Two keys differing only in case are therefore // treated as the same key, which is the safe reading and costs nothing: no // call in this contract has two fields whose names differ only by case. func rejectAmbiguousJSON(data []byte) error { dec := json.NewDecoder(bytes.NewReader(data)) if err := scanUnique(dec); err != nil { return err } // Anything after the first complete value means the body is two documents // wearing one Content-Length, and which one is "the" answer is a guess. if dec.More() { return fmt.Errorf("suite: trailing content after the JSON document") } return nil } func scanUnique(dec *json.Decoder) error { tok, err := dec.Token() if err != nil { return err } delim, isDelim := tok.(json.Delim) if !isDelim { return nil // a scalar; nothing nested to check } switch delim { case '{': seen := make(map[string]bool) for dec.More() { keyTok, err := dec.Token() if err != nil { return err } key, ok := keyTok.(string) if !ok { return fmt.Errorf("suite: object key is not a string") } folded := strings.ToLower(key) if seen[folded] { return fmt.Errorf("suite: duplicate object key %q", key) } seen[folded] = true if err := scanUnique(dec); err != nil { return err } } case '[': for dec.More() { if err := scanUnique(dec); err != nil { return err } } } _, err = dec.Token() // the matching close return err }