139 lines
3.2 KiB
Go
139 lines
3.2 KiB
Go
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
|
||
}
|