242 lines
6.0 KiB
Go
242 lines
6.0 KiB
Go
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
|
|
}
|