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 }