Files
license/sdk/licence/strictjson.go

244 lines
6.0 KiB
Go
Raw Normal View History

2026-07-31 16:59:55 +08:00
package licence
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"unicode/utf8"
)
type jsonValueKind uint8
const (
jsonValueUnknown jsonValueKind = iota
jsonValueObject
jsonValueString
jsonValueNumber
)
func strictDecodeJSON(content []byte, destination any) error {
if !utf8.Valid(content) {
return errors.New("JSON 不是有效的 UTF-8")
}
structureDecoder := json.NewDecoder(bytes.NewReader(content))
structureDecoder.UseNumber()
if err := validateJSONValue(structureDecoder, nil); err != nil {
return err
}
if _, err := structureDecoder.Token(); !errors.Is(err, io.EOF) {
if err == nil {
return errors.New("JSON 包含多个顶层值")
}
return errors.New("JSON 尾部内容无效")
}
decoder := json.NewDecoder(bytes.NewReader(content))
decoder.DisallowUnknownFields()
if err := decoder.Decode(destination); err != nil {
return errors.New("JSON 包含未知字段或无效字段值")
}
var trailing any
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
if err == nil {
return errors.New("JSON 包含多个顶层值")
}
return errors.New("JSON 尾部内容无效")
}
return nil
}
func validateJSONValue(decoder *json.Decoder, path []string) error {
token, err := decoder.Token()
if err != nil {
return errors.New("JSON 结构不完整")
}
expectedKind := expectedJSONKind(path)
delimiter, isDelimiter := token.(json.Delim)
if isDelimiter {
switch delimiter {
case '{':
if expectedKind != jsonValueUnknown && expectedKind != jsonValueObject {
return fmt.Errorf("字段 %s 类型错误", displayJSONPath(path))
}
return validateJSONObject(decoder, path)
case '[':
if expectedKind != jsonValueUnknown {
return fmt.Errorf("字段 %s 类型错误", displayJSONPath(path))
}
for decoder.More() {
if err := validateJSONValue(decoder, path); err != nil {
return err
}
}
closing, err := decoder.Token()
if err != nil || closing != json.Delim(']') {
return errors.New("JSON 数组结构不完整")
}
return nil
default:
return errors.New("JSON 分隔符无效")
}
}
if expectedKind == jsonValueObject {
return fmt.Errorf("字段 %s 必须是对象", displayJSONPath(path))
}
switch expectedKind {
case jsonValueString:
if _, ok := token.(string); !ok {
return fmt.Errorf("字段 %s 必须是字符串", displayJSONPath(path))
}
case jsonValueNumber:
number, ok := token.(json.Number)
if !ok {
return fmt.Errorf("字段 %s 必须是整数", displayJSONPath(path))
}
if _, err := strconv.ParseInt(number.String(), 10, 64); err != nil {
return fmt.Errorf("字段 %s 必须是 int64 整数", displayJSONPath(path))
}
}
return nil
}
func validateJSONObject(decoder *json.Decoder, path []string) error {
seen := make(map[string]struct{})
allowedFields := requiredJSONFields(path)
for decoder.More() {
nameToken, err := decoder.Token()
if err != nil {
return errors.New("JSON 对象结构不完整")
}
name, ok := nameToken.(string)
if !ok {
return errors.New("JSON 属性名必须是字符串")
}
if allowedFields != nil && !containsJSONField(allowedFields, name) {
return fmt.Errorf("字段 %s 未知", displayJSONPath(appendJSONPath(path, name)))
}
if _, exists := seen[name]; exists {
return fmt.Errorf("字段 %s 重复", displayJSONPath(appendJSONPath(path, name)))
}
seen[name] = struct{}{}
if err := validateJSONValue(decoder, appendJSONPath(path, name)); err != nil {
return err
}
}
closing, err := decoder.Token()
if err != nil || closing != json.Delim('}') {
return errors.New("JSON 对象结构不完整")
}
for _, required := range allowedFields {
if _, exists := seen[required]; !exists {
return fmt.Errorf("缺少字段 %s", displayJSONPath(appendJSONPath(path, required)))
}
}
return nil
}
func containsJSONField(fields []string, candidate string) bool {
for _, field := range fields {
if field == candidate {
return true
}
}
return false
}
func expectedJSONKind(path []string) jsonValueKind {
switch len(path) {
case 0:
return jsonValueObject
case 1:
switch path[0] {
case "format_version":
return jsonValueNumber
case "licence", "signing_key_certificate":
return jsonValueObject
case "signature":
return jsonValueString
}
case 2:
if path[0] == "licence" {
switch path[1] {
case "id", "platform_name", "workspace", "issued_on", "valid_from", "expires_on", "signing_key_id":
return jsonValueString
case "quotas":
return jsonValueObject
}
}
if path[0] == "signing_key_certificate" {
switch path[1] {
case "format_version":
return jsonValueNumber
case "key_id", "public_key", "root_signature":
return jsonValueString
}
}
case 3:
if path[0] == "licence" && path[1] == "quotas" {
for _, name := range quotaJSONFields() {
if path[2] == name {
return jsonValueNumber
}
}
}
}
return jsonValueUnknown
}
func requiredJSONFields(path []string) []string {
switch {
case len(path) == 0:
return []string{"format_version", "licence", "signing_key_certificate", "signature"}
case len(path) == 1 && path[0] == "licence":
return []string{"id", "platform_name", "workspace", "issued_on", "valid_from", "expires_on", "quotas", "signing_key_id"}
case len(path) == 2 && path[0] == "licence" && path[1] == "quotas":
return quotaJSONFields()
case len(path) == 1 && path[0] == "signing_key_certificate":
return []string{"format_version", "key_id", "public_key", "root_signature"}
default:
return nil
}
}
func quotaJSONFields() []string {
return []string{
"max_database",
"max_middleware",
"max_network_device",
"max_security",
"max_storage",
"max_pc",
"max_server",
"max_user",
"max_role",
"max_permission",
"max_menu",
}
}
func appendJSONPath(path []string, name string) []string {
result := make([]string, len(path)+1)
copy(result, path)
result[len(path)] = name
return result
}
func displayJSONPath(path []string) string {
if len(path) == 0 {
return "顶层"
}
result := path[0]
for _, segment := range path[1:] {
result += "." + segment
}
return result
}