Files
orbit/sdk/go/worker_codec.go

139 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package orbit
import (
"bytes"
"encoding/json"
"io"
"strconv"
"unicode/utf8"
"github.com/coder/websocket"
)
func parseWorkerFrame(kind websocket.MessageType, raw []byte) (workerFrame, error) {
var f workerFrame
if kind != websocket.MessageText || len(raw) > 512<<10 || validateWorkerJSON(raw) != nil {
return f, ErrWorkerProtocol
}
// Envelope 字段严格区分大小写,防止 encoding/json 的大小写别名覆盖控制字段。
var fields map[string]json.RawMessage
if json.Unmarshal(raw, &fields) != nil || fields == nil {
return f, ErrWorkerProtocol
}
for k := range fields {
switch k {
case "protocol_version", "type", "message_id", "reply_to", "session_id", "body", "error":
default:
return f, ErrWorkerProtocol
}
}
if json.Unmarshal(raw, &f) != nil || f.Version != "1" || !validID(f.MessageID) || f.ReplyTo != "" && !validID(f.ReplyTo) || f.SessionID != "" && !validID(f.SessionID) {
return f, ErrWorkerProtocol
}
if f.Type == "error" {
if f.Error == nil || !workerName(f.Error.Code, 64) || len(f.Error.Message) > 1024 || len(f.Body) > 0 {
return f, ErrWorkerProtocol
}
} else if f.Error != nil || len(f.Body) == 0 || bytes.Equal(f.Body, []byte("null")) {
return f, ErrWorkerProtocol
}
return f, nil
}
// validateWorkerJSON 拒绝重复键、过深嵌套及非法 Unicode,保留数字原文。
// 数值的业务范围仍由服务端规范化器最终验证;SDK 不经由 float64 重编码。
func validateWorkerJSON(raw []byte) error {
if len(raw) == 0 || !utf8.Valid(raw) || !json.Valid(raw) || !workerUnicode(raw) {
return ErrWorkerProtocol
}
d := json.NewDecoder(bytes.NewReader(raw))
d.UseNumber()
var value func(int) error
value = func(depth int) error {
if depth > 32 {
return ErrWorkerProtocol
}
t, err := d.Token()
if err != nil {
return err
}
switch t {
case json.Delim('{'):
keys := map[string]bool{}
for d.More() {
k, err := d.Token()
if err != nil {
return err
}
s, ok := k.(string)
if !ok || keys[s] {
return ErrWorkerProtocol
}
keys[s] = true
if err := value(depth + 1); err != nil {
return err
}
}
_, err = d.Token()
return err
case json.Delim('['):
for d.More() {
if err := value(depth + 1); err != nil {
return err
}
}
_, err = d.Token()
return err
}
return nil
}
if err := value(0); err != nil {
return err
}
if _, err := d.Token(); err != io.EOF {
return ErrWorkerProtocol
}
return nil
}
// JSON 的未配对 UTF-16 转义不能被静默替换为 U+FFFD。
func workerUnicode(raw []byte) bool {
inside := false
for i := 0; i < len(raw); i++ {
if raw[i] == '"' {
inside = !inside
continue
}
if !inside || raw[i] != '\\' {
continue
}
i++
if i >= len(raw) {
return false
}
if raw[i] != 'u' {
continue
}
if i+4 >= len(raw) {
return false
}
n, err := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16)
if err != nil {
return false
}
i += 4
if n < 0xd800 || n > 0xdfff {
continue
}
if n > 0xdbff || i+6 >= len(raw) || raw[i+1] != '\\' || raw[i+2] != 'u' {
return false
}
low, err := strconv.ParseUint(string(raw[i+3:i+7]), 16, 16)
if err != nil || low < 0xdc00 || low > 0xdfff {
return false
}
i += 6
}
return !inside
}