* feat(gateway): 增加分组参数覆盖规则 * fix(gateway): 收紧参数覆盖安全边界 * refactor(web): 优化参数覆盖编辑器样式 * fix(web): 修正参数覆盖编辑器实现 * fix: 修正参数覆盖空键与输入推断 * fix: 拒绝参数覆盖中的负零 * fix(gateway): 纳入覆盖后兼容的候选 * fix(gateway): 固定参数覆盖前的路由上下文 * fix(control): 保留参数覆盖数字字面量 * fix(gateway): 统一参数覆盖请求体限制 * fix(gateway): 避免参数覆盖的 HTML 转义膨胀 * fix(gateway): 限制请求内参数覆盖缓存 * fix(parameteroverride): 避免请求整树解码膨胀 * fix(parameteroverride): 避免深层请求重复扫描
240 lines
6.2 KiB
Go
240 lines
6.2 KiB
Go
package parameteroverride
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"unicode/utf8"
|
|
|
|
"github.com/buger/jsonparser"
|
|
)
|
|
|
|
// 只为规则涉及的路径建立节点;其他值始终引用原始 JSON,不展开数组或对象。
|
|
// raw 为 nil 表示字段不存在,JSON null 则保留为四个字节。
|
|
type requestValue struct {
|
|
raw []byte
|
|
fields map[string]*requestValue
|
|
loaded bool
|
|
dirty bool
|
|
// 当前字段在父对象原始 JSON 中最后一次出现的位置,不随覆盖值改变。
|
|
start int
|
|
end int
|
|
// 重复对象通过代次使旧子字段失效,避免每次遍历全部规则字段清空。
|
|
generation uint
|
|
parentGeneration uint
|
|
}
|
|
|
|
func (value *requestValue) field(key string) *requestValue {
|
|
if value.fields == nil {
|
|
value.fields = make(map[string]*requestValue)
|
|
}
|
|
if value.fields[key] == nil {
|
|
value.fields[key] = &requestValue{}
|
|
}
|
|
return value.fields[key]
|
|
}
|
|
|
|
func (value *requestValue) planSet(source map[string]any) {
|
|
for key, item := range source {
|
|
child := value.field(key)
|
|
if object, ok := item.(map[string]any); ok {
|
|
child.planSet(object)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (value *requestValue) planPath(path []string) {
|
|
for _, key := range path {
|
|
value = value.field(key)
|
|
}
|
|
}
|
|
|
|
func (value *requestValue) reset(raw []byte) {
|
|
value.raw, value.loaded, value.dirty = raw, false, false
|
|
}
|
|
|
|
func (value *requestValue) isObject() bool {
|
|
return len(value.raw) > 0 && value.raw[0] == '{'
|
|
}
|
|
|
|
func (value *requestValue) load() error {
|
|
if value.loaded {
|
|
return nil
|
|
}
|
|
_, err := value.scan(value.raw)
|
|
return err
|
|
}
|
|
|
|
// 按规则路径直接下行,返回对象终点;父层不先扫描整个子树再递归。
|
|
func (value *requestValue) scan(body []byte) (int, error) {
|
|
value.generation++
|
|
end, err := value.eachField(body, true, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
value.raw, value.loaded = body[:end], true
|
|
return end, nil
|
|
}
|
|
|
|
func (value *requestValue) currentField(key string) *requestValue {
|
|
child := value.fields[key]
|
|
if child.parentGeneration != value.generation {
|
|
child.reset(nil)
|
|
child.start, child.end = 0, 0
|
|
child.parentGeneration = value.generation
|
|
}
|
|
return child
|
|
}
|
|
|
|
func (value *requestValue) remove(path []string) error {
|
|
if !value.isObject() {
|
|
return nil
|
|
}
|
|
if err := value.load(); err != nil {
|
|
return err
|
|
}
|
|
child := value.currentField(path[0])
|
|
if len(path) == 1 {
|
|
child.reset(nil)
|
|
child.dirty = true
|
|
} else if err := child.remove(path[1:]); err != nil {
|
|
return err
|
|
}
|
|
value.dirty = value.dirty || child.dirty
|
|
return nil
|
|
}
|
|
|
|
func (value *requestValue) merge(source any) error {
|
|
if object, ok := source.(map[string]any); ok && value.isObject() {
|
|
if len(object) == 0 {
|
|
return nil
|
|
}
|
|
if err := value.load(); err != nil {
|
|
return err
|
|
}
|
|
for key, item := range object {
|
|
if err := value.currentField(key).merge(item); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
value.dirty = true
|
|
return nil
|
|
}
|
|
// 仅序列化受配置大小限制的替换值,客户端的大值不会进入通用 JSON 树。
|
|
var encoded bytes.Buffer
|
|
encoder := json.NewEncoder(&encoded)
|
|
encoder.SetEscapeHTML(false)
|
|
if err := encoder.Encode(source); err != nil {
|
|
return err
|
|
}
|
|
value.reset(bytes.TrimSuffix(encoded.Bytes(), []byte("\n")))
|
|
value.dirty = true
|
|
return nil
|
|
}
|
|
|
|
type requestOutput struct {
|
|
body []byte
|
|
size int
|
|
}
|
|
|
|
func (output *requestOutput) append(raw []byte) {
|
|
output.size += len(raw)
|
|
if output.body != nil {
|
|
output.body = append(output.body, raw...)
|
|
}
|
|
}
|
|
|
|
func (value *requestValue) write(output *requestOutput) error {
|
|
if !value.dirty || !value.loaded {
|
|
output.append(value.raw)
|
|
return nil
|
|
}
|
|
output.append([]byte{'{'})
|
|
first := true
|
|
separator := func() {
|
|
if !first {
|
|
output.append([]byte{','})
|
|
}
|
|
first = false
|
|
}
|
|
if _, err := value.eachField(value.raw, false, func(key, member []byte) {
|
|
if child := value.fields[string(key)]; child != nil && child.parentGeneration == value.generation && child.dirty {
|
|
return
|
|
}
|
|
separator()
|
|
output.append(member)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
for key, child := range value.fields {
|
|
if child.parentGeneration != value.generation || !child.dirty || child.raw == nil {
|
|
continue
|
|
}
|
|
separator()
|
|
encodedKey, err := json.Marshal(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
output.append(encodedKey)
|
|
output.append([]byte{':'})
|
|
if err := child.write(output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
output.append([]byte{'}'})
|
|
return nil
|
|
}
|
|
|
|
// 调用方已用 json.Valid 校验完整请求。这里只扫描字段边界,值引用原始字节;
|
|
// 仅含转义或非法 UTF-8 的键需要解码,沿用 encoding/json 的键名语义。
|
|
func (value *requestValue) eachField(body []byte, discover bool, visit func(key, member []byte)) (int, error) {
|
|
remaining := bytes.TrimLeft(body[1:], " \t\r\n")
|
|
for remaining[0] != '}' {
|
|
member := remaining
|
|
key, _, end, err := jsonparser.Get(remaining)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if bytes.ContainsRune(key, '\\') || !utf8.Valid(key) {
|
|
var decoded string
|
|
if err := json.Unmarshal(remaining[:end], &decoded); err != nil {
|
|
return 0, err
|
|
}
|
|
key = []byte(decoded)
|
|
}
|
|
remaining = bytes.TrimLeft(remaining[end:], " \t\r\n")
|
|
remaining = bytes.TrimLeft(remaining[1:], " \t\r\n") // 跳过冒号。
|
|
start := len(body) - len(remaining)
|
|
child := value.fields[string(key)]
|
|
switch {
|
|
case discover && child != nil && remaining[0] == '{' && len(child.fields) > 0:
|
|
child.reset(nil)
|
|
end, err = child.scan(remaining)
|
|
case !discover && child != nil && child.parentGeneration == value.generation && child.start == start && child.end > start:
|
|
// 测量和输出直接使用已发现的终点,不重复扫描深层的大值。
|
|
end = child.end - start
|
|
default:
|
|
_, _, end, err = jsonparser.Get(remaining)
|
|
if discover && child != nil {
|
|
child.reset(nil)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if discover && child != nil {
|
|
// 重复键只保留最后一次的位置,索引大小不随客户端字段数量增长。
|
|
child.raw = remaining[:end]
|
|
child.start, child.end = start, start+end
|
|
child.parentGeneration = value.generation
|
|
}
|
|
if visit != nil {
|
|
visit(key, member[:len(member)-len(remaining)+end])
|
|
}
|
|
remaining = bytes.TrimLeft(remaining[end:], " \t\r\n")
|
|
if remaining[0] == ',' {
|
|
remaining = bytes.TrimLeft(remaining[1:], " \t\r\n")
|
|
}
|
|
}
|
|
return len(body) - len(remaining) + 1, nil
|
|
}
|