package catalog import ( "bytes" "encoding/json" "fmt" "io" "regexp" "sort" "unicode/utf8" ) var integerJSONNumber = regexp.MustCompile(`^-?(0|[1-9][0-9]*)$`) func parseRestrictedJSON(data []byte) (any, error) { if !utf8.Valid(data) { return nil, fmt.Errorf("%w: input is not valid UTF-8", ErrInvalidDocument) } decoder := json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() value, err := decodeJSONValue(decoder) if err != nil { return nil, err } if _, err := decoder.Token(); err != io.EOF { if err == nil { return nil, fmt.Errorf("%w: trailing JSON value", ErrInvalidDocument) } return nil, fmt.Errorf("%w: trailing data: %v", ErrInvalidDocument, err) } return value, nil } func decodeJSONValue(decoder *json.Decoder) (any, error) { token, err := decoder.Token() if err != nil { return nil, fmt.Errorf("%w: %v", ErrInvalidDocument, err) } switch value := token.(type) { case json.Delim: switch value { case '{': object := make(map[string]any) for decoder.More() { keyToken, err := decoder.Token() if err != nil { return nil, fmt.Errorf("%w: object key: %v", ErrInvalidDocument, err) } key, ok := keyToken.(string) if !ok { return nil, fmt.Errorf("%w: object key is not a string", ErrInvalidDocument) } if _, exists := object[key]; exists { return nil, fmt.Errorf("%w: %q", ErrDuplicateField, key) } child, err := decodeJSONValue(decoder) if err != nil { return nil, err } object[key] = child } end, err := decoder.Token() if err != nil || end != json.Delim('}') { return nil, fmt.Errorf("%w: unterminated object", ErrInvalidDocument) } return object, nil case '[': var array []any for decoder.More() { child, err := decodeJSONValue(decoder) if err != nil { return nil, err } array = append(array, child) } end, err := decoder.Token() if err != nil || end != json.Delim(']') { return nil, fmt.Errorf("%w: unterminated array", ErrInvalidDocument) } return array, nil default: return nil, fmt.Errorf("%w: unexpected delimiter %q", ErrInvalidDocument, value) } case json.Number: if !integerJSONNumber.MatchString(string(value)) { return nil, fmt.Errorf("%w: %q", ErrUnsupportedNumber, value) } return value, nil case string, bool, nil: return value, nil default: return nil, fmt.Errorf("%w: unsupported token %T", ErrInvalidDocument, token) } } func canonicalJSON(value any) ([]byte, error) { var buffer bytes.Buffer if err := appendCanonicalJSON(&buffer, value); err != nil { return nil, err } return buffer.Bytes(), nil } func appendCanonicalJSON(buffer *bytes.Buffer, value any) error { switch value := value.(type) { case nil: buffer.WriteString("null") case bool: if value { buffer.WriteString("true") } else { buffer.WriteString("false") } case string: encoded, err := json.Marshal(value) if err != nil { return fmt.Errorf("%w: encode string: %v", ErrInvalidDocument, err) } buffer.Write(encoded) case json.Number: if !integerJSONNumber.MatchString(string(value)) { return fmt.Errorf("%w: %q", ErrUnsupportedNumber, value) } buffer.WriteString(string(value)) case []any: buffer.WriteByte('[') for index, child := range value { if index > 0 { buffer.WriteByte(',') } if err := appendCanonicalJSON(buffer, child); err != nil { return err } } buffer.WriteByte(']') case map[string]any: keys := make([]string, 0, len(value)) for key := range value { keys = append(keys, key) } sort.Strings(keys) buffer.WriteByte('{') for index, key := range keys { if index > 0 { buffer.WriteByte(',') } encodedKey, err := json.Marshal(key) if err != nil { return fmt.Errorf("%w: encode key: %v", ErrInvalidDocument, err) } buffer.Write(encodedKey) buffer.WriteByte(':') if err := appendCanonicalJSON(buffer, value[key]); err != nil { return err } } buffer.WriteByte('}') default: return fmt.Errorf("%w: unsupported value %T", ErrInvalidDocument, value) } return nil }