package catalog import ( "bytes" "encoding/json" "fmt" "io" "regexp" "sort" "unicode/utf8" ) var integerJSONNumber = regexp.MustCompile(`^(0|[1-9][0-9]*|-[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) } if err := validateJSONStringSurrogates(data); err != nil { return nil, err } 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 validateJSONStringSurrogates(data []byte) error { for index := 0; index < len(data); index++ { if data[index] != '"' { continue } next, err := scanJSONStringSurrogates(data, index) if err != nil { return err } index = next - 1 } return nil } func scanJSONStringSurrogates(data []byte, start int) (int, error) { for index := start + 1; index < len(data); index++ { switch data[index] { case '"': return index + 1, nil case '\\': if index+1 >= len(data) { return 0, fmt.Errorf("%w: incomplete string escape", ErrInvalidDocument) } if data[index+1] != 'u' { index++ continue } codeUnit, ok := decodeJSONHexCodeUnit(data, index+2) if !ok { return 0, fmt.Errorf("%w: invalid unicode escape", ErrInvalidDocument) } switch { case codeUnit >= 0xd800 && codeUnit <= 0xdbff: if index+7 >= len(data) || data[index+6] != '\\' || data[index+7] != 'u' { return 0, fmt.Errorf("%w: high surrogate is not paired", ErrInvalidDocument) } lowSurrogate, ok := decodeJSONHexCodeUnit(data, index+8) if !ok || lowSurrogate < 0xdc00 || lowSurrogate > 0xdfff { return 0, fmt.Errorf("%w: high surrogate is not followed by a low surrogate", ErrInvalidDocument) } index += 11 case codeUnit >= 0xdc00 && codeUnit <= 0xdfff: return 0, fmt.Errorf("%w: low surrogate has no high surrogate", ErrInvalidDocument) default: index += 5 } } } return 0, fmt.Errorf("%w: unterminated string", ErrInvalidDocument) } func decodeJSONHexCodeUnit(data []byte, start int) (uint16, bool) { if start+4 > len(data) { return 0, false } var value uint16 for _, digit := range data[start : start+4] { value <<= 4 switch { case digit >= '0' && digit <= '9': value |= uint16(digit - '0') case digit >= 'a' && digit <= 'f': value |= uint16(digit-'a') + 10 case digit >= 'A' && digit <= 'F': value |= uint16(digit-'A') + 10 default: return 0, false } } return value, true } 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 }