carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package maintenanceguard
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "io"
8 "reflect"
9 "strings"
10)
11
12const MaxWireBytes = 16 << 10
13
14var ErrInvalid = errors.New("maintenance guard: invalid record")
15
16// decode checks exact field names and duplicates before typed decoding. The
17// expected schema bounds nesting; arbitrary JSON trees are never traversed.
18func decode(raw []byte, dst any) error {
19 if len(raw) == 0 || len(raw) > MaxWireBytes {
20 return ErrInvalid
21 }
22 typ := reflect.TypeOf(dst)
23 if typ == nil || typ.Kind() != reflect.Pointer {
24 return ErrInvalid
25 }
26 dec := json.NewDecoder(bytes.NewReader(raw))
27 dec.UseNumber()
28 if !jsonShape(dec, typ.Elem()) {
29 return ErrInvalid
30 }
31 if _, e := dec.Token(); e != io.EOF {
32 return ErrInvalid
33 }
34 if json.Unmarshal(raw, dst) != nil {
35 return ErrInvalid
36 }
37 return nil
38}
39func jsonShape(dec *json.Decoder, typ reflect.Type) bool {
40 for typ.Kind() == reflect.Pointer {
41 typ = typ.Elem()
42 }
43 token, e := dec.Token()
44 if e != nil || token == nil {
45 return false
46 }
47 if delim, ok := token.(json.Delim); ok {
48 switch delim {
49 case '{':
50 if typ.Kind() != reflect.Struct {
51 return false
52 }
53 fields := map[string]reflect.Type{}
54 for i := 0; i < typ.NumField(); i++ {
55 field := typ.Field(i)
56 name := strings.Split(field.Tag.Get("json"), ",")[0]
57 if name != "" && name != "-" {
58 fields[name] = field.Type
59 }
60 }
61 seen := map[string]bool{}
62 for dec.More() {
63 key, e := dec.Token()
64 if e != nil {
65 return false
66 }
67 name, ok := key.(string)
68 field, known := fields[name]
69 if !ok || !known || seen[name] {
70 return false
71 }
72 seen[name] = true
73 if !jsonShape(dec, field) {
74 return false
75 }
76 }
77 end, e := dec.Token()
78 return e == nil && end == json.Delim('}')
79 case '[':
80 if typ.Kind() != reflect.Slice {
81 return false
82 }
83 for dec.More() {
84 if !jsonShape(dec, typ.Elem()) {
85 return false
86 }
87 }
88 end, e := dec.Token()
89 return e == nil && end == json.Delim(']')
90 default:
91 return false
92 }
93 }
94 switch typ.Kind() {
95 case reflect.String:
96 _, ok := token.(string)
97 return ok
98 case reflect.Int, reflect.Int64:
99 _, ok := token.(json.Number)
100 return ok
101 case reflect.Bool:
102 _, ok := token.(bool)
103 return ok
104 }
105 return false
106}
107