package maintenanceguard import ( "bytes" "encoding/json" "errors" "io" "reflect" "strings" ) const MaxWireBytes = 16 << 10 var ErrInvalid = errors.New("maintenance guard: invalid record") // decode checks exact field names and duplicates before typed decoding. The // expected schema bounds nesting; arbitrary JSON trees are never traversed. func decode(raw []byte, dst any) error { if len(raw) == 0 || len(raw) > MaxWireBytes { return ErrInvalid } typ := reflect.TypeOf(dst) if typ == nil || typ.Kind() != reflect.Pointer { return ErrInvalid } dec := json.NewDecoder(bytes.NewReader(raw)) dec.UseNumber() if !jsonShape(dec, typ.Elem()) { return ErrInvalid } if _, e := dec.Token(); e != io.EOF { return ErrInvalid } if json.Unmarshal(raw, dst) != nil { return ErrInvalid } return nil } func jsonShape(dec *json.Decoder, typ reflect.Type) bool { for typ.Kind() == reflect.Pointer { typ = typ.Elem() } token, e := dec.Token() if e != nil || token == nil { return false } if delim, ok := token.(json.Delim); ok { switch delim { case '{': if typ.Kind() != reflect.Struct { return false } fields := map[string]reflect.Type{} for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) name := strings.Split(field.Tag.Get("json"), ",")[0] if name != "" && name != "-" { fields[name] = field.Type } } seen := map[string]bool{} for dec.More() { key, e := dec.Token() if e != nil { return false } name, ok := key.(string) field, known := fields[name] if !ok || !known || seen[name] { return false } seen[name] = true if !jsonShape(dec, field) { return false } } end, e := dec.Token() return e == nil && end == json.Delim('}') case '[': if typ.Kind() != reflect.Slice { return false } for dec.More() { if !jsonShape(dec, typ.Elem()) { return false } } end, e := dec.Token() return e == nil && end == json.Delim(']') default: return false } } switch typ.Kind() { case reflect.String: _, ok := token.(string) return ok case reflect.Int, reflect.Int64: _, ok := token.(json.Number) return ok case reflect.Bool: _, ok := token.(bool) return ok } return false }