feat: deliver authenticated task APIs, SDK and P1 regression
This commit is contained in:
194
internal/jsonvalue/canonical.go
Normal file
194
internal/jsonvalue/canonical.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// Package jsonvalue 比较 JSON 的实际内容,不将 JSON 数值转换为浮点数。
|
||||
package jsonvalue
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Canonical 生成内部 orbit-json-v1 摘要输入:对象键排序、重复键拒绝、
|
||||
// 精确十进制数规范化。该格式不是对外 JSON 编码承诺,也不宣称实现 RFC 8785。
|
||||
func Canonical(raw []byte) ([]byte, error) {
|
||||
if !utf8.Valid(raw) {
|
||||
return nil, errors.New("JSON must be valid UTF-8")
|
||||
}
|
||||
if err := validEscapes(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := json.NewDecoder(bytes.NewReader(raw))
|
||||
d.UseNumber()
|
||||
b, err := value(d, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := d.Token(); err != io.EOF {
|
||||
return nil, errors.New("exactly one JSON value required")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
func value(d *json.Decoder, depth int) ([]byte, error) {
|
||||
if depth > 32 {
|
||||
return nil, errors.New("JSON nesting exceeds 32 levels")
|
||||
}
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch v := t.(type) {
|
||||
case json.Delim:
|
||||
switch v {
|
||||
case '{':
|
||||
fields := map[string][]byte{}
|
||||
keys := []string{}
|
||||
for d.More() {
|
||||
token, err := d.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, ok := token.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid object key")
|
||||
}
|
||||
if _, ok := fields[key]; ok {
|
||||
return nil, errors.New("duplicate JSON object key")
|
||||
}
|
||||
b, err := value(d, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields[key] = b
|
||||
keys = append(keys, key)
|
||||
}
|
||||
if close, err := d.Token(); err != nil || close != json.Delim('}') {
|
||||
return nil, errors.New("unterminated JSON object")
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
key, _ := json.Marshal(k)
|
||||
b.Write(key)
|
||||
b.WriteByte(':')
|
||||
b.Write(fields[k])
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
case '[':
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('[')
|
||||
n := 0
|
||||
for d.More() {
|
||||
v, err := value(d, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
b.Write(v)
|
||||
n++
|
||||
}
|
||||
if close, err := d.Token(); err != nil || close != json.Delim(']') {
|
||||
return nil, errors.New("unterminated JSON array")
|
||||
}
|
||||
b.WriteByte(']')
|
||||
return b.Bytes(), nil
|
||||
default:
|
||||
return nil, errors.New("unexpected JSON delimiter")
|
||||
}
|
||||
case json.Number:
|
||||
return number(string(v))
|
||||
default:
|
||||
return json.Marshal(v)
|
||||
}
|
||||
}
|
||||
func number(s string) ([]byte, error) {
|
||||
if len(s) > 256 {
|
||||
return nil, errors.New("JSON number exceeds 256 characters")
|
||||
}
|
||||
// 仅用作有限范围检查;摘要始终由下面的十进制文本运算生成。
|
||||
if _, err := strconv.ParseFloat(s, 64); err != nil {
|
||||
return nil, errors.New("JSON number outside supported range")
|
||||
}
|
||||
negative := strings.HasPrefix(s, "-")
|
||||
s = strings.TrimPrefix(s, "-")
|
||||
exponent := 0
|
||||
if i := strings.IndexAny(s, "eE"); i >= 0 {
|
||||
var err error
|
||||
exponent, err = strconv.Atoi(s[i+1:])
|
||||
if err != nil || exponent < -308 || exponent > 308 {
|
||||
return nil, errors.New("JSON exponent outside supported range")
|
||||
}
|
||||
s = s[:i]
|
||||
}
|
||||
if i := strings.IndexByte(s, '.'); i >= 0 {
|
||||
exponent -= len(s) - i - 1
|
||||
s = s[:i] + s[i+1:]
|
||||
}
|
||||
s = strings.TrimLeft(s, "0")
|
||||
if s == "" {
|
||||
return []byte("0"), nil
|
||||
}
|
||||
for strings.HasSuffix(s, "0") {
|
||||
s = s[:len(s)-1]
|
||||
exponent++
|
||||
}
|
||||
if exponent < -308 || exponent > 308 {
|
||||
return nil, errors.New("normalized JSON exponent outside supported range")
|
||||
}
|
||||
if exponent+len(s)-1 > 308 {
|
||||
return nil, errors.New("JSON number outside supported range")
|
||||
}
|
||||
if negative {
|
||||
s = "-" + s
|
||||
}
|
||||
if exponent != 0 {
|
||||
s += "e" + strconv.Itoa(exponent)
|
||||
}
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// encoding/json 会替换孤立 UTF-16 代理项;拒绝它们,避免两个不同输入被悄悄合并。
|
||||
func validEscapes(raw []byte) error {
|
||||
for i := 0; i < len(raw); i++ {
|
||||
if raw[i] != '\\' {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
if i >= len(raw) || raw[i] != 'u' {
|
||||
continue
|
||||
}
|
||||
if i+4 >= len(raw) {
|
||||
return errors.New("invalid unicode escape")
|
||||
}
|
||||
v, err := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i += 4
|
||||
if v >= 0xdc00 && v <= 0xdfff {
|
||||
return errors.New("unpaired unicode surrogate")
|
||||
}
|
||||
if v < 0xd800 || v > 0xdbff {
|
||||
continue
|
||||
}
|
||||
if i+6 >= len(raw) || raw[i+1] != '\\' || raw[i+2] != 'u' {
|
||||
return errors.New("unpaired unicode surrogate")
|
||||
}
|
||||
v, err = strconv.ParseUint(string(raw[i+3:i+7]), 16, 16)
|
||||
if err != nil || v < 0xdc00 || v > 0xdfff {
|
||||
return errors.New("unpaired unicode surrogate")
|
||||
}
|
||||
i += 6
|
||||
}
|
||||
return nil
|
||||
}
|
||||
38
internal/jsonvalue/canonical_test.go
Normal file
38
internal/jsonvalue/canonical_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package jsonvalue
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCanonicalPreservesExactNumbers(t *testing.T) {
|
||||
for _, pair := range [][2]string{
|
||||
{`{"z":1.0,"a":9007199254740993}`, `{"a":9007199254740993,"z":1}`},
|
||||
{`[0,-0,100,0.0001000]`, `[0,0,1e2,1e-4]`},
|
||||
{`{"x":12345678901234567890.1234567890123456789}`, `{"x":123456789012345678901234567890123456789e-19}`},
|
||||
{`"\ud83d\ude00"`, `"😀"`},
|
||||
} {
|
||||
got, err := Canonical([]byte(pair[0]))
|
||||
if err != nil || string(got) != pair[1] {
|
||||
t.Fatalf("%s => %s, %v", pair[0], got, err)
|
||||
}
|
||||
}
|
||||
for _, raw := range []string{`{"a":1,"\u0061":2}`, `{"nested":{"x":1,"x":2}}`, `1 2`, `9e308`, `1e309`, `"\ud800"`, `"\udc00"`, `"\ud800\u1234"`, string([]byte{'"', 0xff, '"'})} {
|
||||
if _, err := Canonical([]byte(raw)); err == nil {
|
||||
t.Fatalf("accepted %q", raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzCanonical(f *testing.F) {
|
||||
for _, s := range []string{`{"a":1.0}`, `[1,true,null]`, `"\ud83d\ude00"`, `0.0000000000000000001`} {
|
||||
f.Add(s)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
b, err := Canonical([]byte(s))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
again, err := Canonical(b)
|
||||
if err != nil || string(again) != string(b) {
|
||||
t.Fatalf("unstable canonical form %s %v", b, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user