92 lines
1.9 KiB
Go
92 lines
1.9 KiB
Go
package control
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
func decodeStrictControlJSONObject(data []byte, target any) error {
|
|
trimmed := bytes.TrimSpace(data)
|
|
if len(trimmed) == 0 || trimmed[0] != '{' {
|
|
return fmt.Errorf("request body must be a JSON object")
|
|
}
|
|
if err := rejectDuplicateJSONFields(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.UseNumber()
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(target); err != nil {
|
|
return err
|
|
}
|
|
var extra json.RawMessage
|
|
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
|
|
if err == nil {
|
|
return fmt.Errorf("decode JSON request: multiple values")
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rejectDuplicateJSONFields(data []byte) error {
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.UseNumber()
|
|
var walk func() error
|
|
walk = func() error {
|
|
token, err := decoder.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
delimiter, isDelimiter := token.(json.Delim)
|
|
if !isDelimiter {
|
|
return nil
|
|
}
|
|
switch delimiter {
|
|
case '{':
|
|
seen := make(map[string]struct{})
|
|
for decoder.More() {
|
|
keyToken, err := decoder.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key, ok := keyToken.(string)
|
|
if !ok {
|
|
return fmt.Errorf("object key must be a string")
|
|
}
|
|
if _, duplicate := seen[key]; duplicate {
|
|
return fmt.Errorf("duplicate field %q", key)
|
|
}
|
|
seen[key] = struct{}{}
|
|
if err := walk(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = decoder.Token()
|
|
return err
|
|
case '[':
|
|
for decoder.More() {
|
|
if err := walk(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = decoder.Token()
|
|
return err
|
|
default:
|
|
return fmt.Errorf("unexpected delimiter %q", delimiter)
|
|
}
|
|
}
|
|
if err := walk(); err != nil {
|
|
return err
|
|
}
|
|
var extra json.RawMessage
|
|
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
|
|
return fmt.Errorf("multiple JSON values")
|
|
}
|
|
return nil
|
|
}
|