Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/reporting/trackers/linear/jsonutil/jsonutil.go
2070 views
1
// Package jsonutil provides a function for decoding JSON
2
// into a GraphQL query data structure.
3
//
4
// Taken from: https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/internal/jsonutil/graphql.go
5
package jsonutil
6
7
import (
8
"bytes"
9
"encoding/json"
10
"errors"
11
"fmt"
12
"io"
13
"reflect"
14
"strings"
15
16
sonic "github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
17
)
18
19
// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores
20
// the result in the GraphQL query data structure pointed to by v.
21
//
22
// The implementation is created on top of the JSON tokenizer available
23
// in "encoding/json".Decoder.
24
func UnmarshalGraphQL(data []byte, v any) error {
25
dec := json.NewDecoder(bytes.NewReader(data))
26
dec.UseNumber()
27
err := (&decoder{tokenizer: dec}).Decode(v)
28
if err != nil {
29
return err
30
}
31
tok, err := dec.Token()
32
switch err {
33
case io.EOF:
34
// Expect to get io.EOF. There shouldn't be any more
35
// tokens left after we've decoded v successfully.
36
return nil
37
case nil:
38
return fmt.Errorf("invalid token '%v' after top-level value", tok)
39
default:
40
return err
41
}
42
}
43
44
// decoder is a JSON decoder that performs custom unmarshaling behavior
45
// for GraphQL query data structures. It's implemented on top of a JSON tokenizer.
46
type decoder struct {
47
tokenizer interface {
48
Token() (json.Token, error)
49
}
50
51
// Stack of what part of input JSON we're in the middle of - objects, arrays.
52
parseState []json.Delim
53
54
// Stacks of values where to unmarshal.
55
// The top of each stack is the reflect.Value where to unmarshal next JSON value.
56
//
57
// The reason there's more than one stack is because we might be unmarshaling
58
// a single JSON value into multiple GraphQL fragments or embedded structs, so
59
// we keep track of them all.
60
vs [][]reflect.Value
61
}
62
63
// Decode decodes a single JSON value from d.tokenizer into v.
64
func (d *decoder) Decode(v any) error {
65
rv := reflect.ValueOf(v)
66
if rv.Kind() != reflect.Ptr {
67
return fmt.Errorf("cannot decode into non-pointer %T", v)
68
}
69
d.vs = [][]reflect.Value{{rv.Elem()}}
70
return d.decode()
71
}
72
73
// decode decodes a single JSON value from d.tokenizer into d.vs.
74
func (d *decoder) decode() error {
75
// The loop invariant is that the top of each d.vs stack
76
// is where we try to unmarshal the next JSON value we see.
77
for len(d.vs) > 0 {
78
tok, err := d.tokenizer.Token()
79
if err == io.EOF {
80
return errors.New("unexpected end of JSON input")
81
} else if err != nil {
82
return err
83
}
84
85
switch {
86
87
// Are we inside an object and seeing next key (rather than end of object)?
88
case d.state() == '{' && tok != json.Delim('}'):
89
key, ok := tok.(string)
90
if !ok {
91
return errors.New("unexpected non-key in JSON input")
92
}
93
someFieldExist := false
94
for i := range d.vs {
95
v := d.vs[i][len(d.vs[i])-1]
96
if v.Kind() == reflect.Ptr {
97
v = v.Elem()
98
}
99
var f reflect.Value
100
if v.Kind() == reflect.Struct {
101
f = fieldByGraphQLName(v, key)
102
if f.IsValid() {
103
someFieldExist = true
104
}
105
}
106
d.vs[i] = append(d.vs[i], f)
107
}
108
if !someFieldExist {
109
return fmt.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))
110
}
111
112
// We've just consumed the current token, which was the key.
113
// Read the next token, which should be the value, and let the rest of code process it.
114
tok, err = d.tokenizer.Token()
115
if err == io.EOF {
116
return errors.New("unexpected end of JSON input")
117
} else if err != nil {
118
return err
119
}
120
121
// Are we inside an array and seeing next value (rather than end of array)?
122
case d.state() == '[' && tok != json.Delim(']'):
123
someSliceExist := false
124
for i := range d.vs {
125
v := d.vs[i][len(d.vs[i])-1]
126
if v.Kind() == reflect.Ptr {
127
v = v.Elem()
128
}
129
var f reflect.Value
130
if v.Kind() == reflect.Slice {
131
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T).
132
f = v.Index(v.Len() - 1)
133
someSliceExist = true
134
}
135
d.vs[i] = append(d.vs[i], f)
136
}
137
if !someSliceExist {
138
return fmt.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs))
139
}
140
}
141
142
switch tok := tok.(type) {
143
case string, json.Number, bool, nil:
144
// Value.
145
146
for i := range d.vs {
147
v := d.vs[i][len(d.vs[i])-1]
148
if !v.IsValid() {
149
continue
150
}
151
err := unmarshalValue(tok, v)
152
if err != nil {
153
return err
154
}
155
}
156
d.popAllVs()
157
158
case json.Delim:
159
switch tok {
160
case '{':
161
// Start of object.
162
163
d.pushState(tok)
164
165
frontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs.
166
for i := range d.vs {
167
v := d.vs[i][len(d.vs[i])-1]
168
frontier[i] = v
169
// TODO: Do this recursively or not? Add a test case if needed.
170
if v.Kind() == reflect.Ptr && v.IsNil() {
171
v.Set(reflect.New(v.Type().Elem())) // v = new(T).
172
}
173
}
174
// Find GraphQL fragments/embedded structs recursively, adding to frontier
175
// as new ones are discovered and exploring them further.
176
for len(frontier) > 0 {
177
v := frontier[0]
178
frontier = frontier[1:]
179
if v.Kind() == reflect.Ptr {
180
v = v.Elem()
181
}
182
if v.Kind() != reflect.Struct {
183
continue
184
}
185
for i := 0; i < v.NumField(); i++ {
186
if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {
187
// Add GraphQL fragment or embedded struct.
188
d.vs = append(d.vs, []reflect.Value{v.Field(i)})
189
frontier = append(frontier, v.Field(i))
190
}
191
}
192
}
193
case '[':
194
// Start of array.
195
196
d.pushState(tok)
197
198
for i := range d.vs {
199
v := d.vs[i][len(d.vs[i])-1]
200
// TODO: Confirm this is needed, write a test case.
201
//if v.Kind() == reflect.Ptr && v.IsNil() {
202
// v.Set(reflect.New(v.Type().Elem())) // v = new(T).
203
//}
204
205
// Reset slice to empty (in case it had non-zero initial value).
206
if v.Kind() == reflect.Ptr {
207
v = v.Elem()
208
}
209
if v.Kind() != reflect.Slice {
210
continue
211
}
212
v.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0).
213
}
214
case '}', ']':
215
// End of object or array.
216
d.popAllVs()
217
d.popState()
218
default:
219
return errors.New("unexpected delimiter in JSON input")
220
}
221
default:
222
return errors.New("unexpected token in JSON input")
223
}
224
}
225
return nil
226
}
227
228
// pushState pushes a new parse state s onto the stack.
229
func (d *decoder) pushState(s json.Delim) {
230
d.parseState = append(d.parseState, s)
231
}
232
233
// popState pops a parse state (already obtained) off the stack.
234
// The stack must be non-empty.
235
func (d *decoder) popState() {
236
d.parseState = d.parseState[:len(d.parseState)-1]
237
}
238
239
// state reports the parse state on top of stack, or 0 if empty.
240
func (d *decoder) state() json.Delim {
241
if len(d.parseState) == 0 {
242
return 0
243
}
244
return d.parseState[len(d.parseState)-1]
245
}
246
247
// popAllVs pops from all d.vs stacks, keeping only non-empty ones.
248
func (d *decoder) popAllVs() {
249
var nonEmpty [][]reflect.Value
250
for i := range d.vs {
251
d.vs[i] = d.vs[i][:len(d.vs[i])-1]
252
if len(d.vs[i]) > 0 {
253
nonEmpty = append(nonEmpty, d.vs[i])
254
}
255
}
256
d.vs = nonEmpty
257
}
258
259
// fieldByGraphQLName returns an exported struct field of struct v
260
// that matches GraphQL name, or invalid reflect.Value if none found.
261
func fieldByGraphQLName(v reflect.Value, name string) reflect.Value {
262
for i := 0; i < v.NumField(); i++ {
263
if v.Type().Field(i).PkgPath != "" {
264
// Skip unexported field.
265
continue
266
}
267
if hasGraphQLName(v.Type().Field(i), name) {
268
return v.Field(i)
269
}
270
}
271
return reflect.Value{}
272
}
273
274
// hasGraphQLName reports whether struct field f has GraphQL name.
275
func hasGraphQLName(f reflect.StructField, name string) bool {
276
value, ok := f.Tag.Lookup("graphql")
277
if !ok {
278
// TODO: caseconv package is relatively slow. Optimize it, then consider using it here.
279
//return caseconv.MixedCapsToLowerCamelCase(f.Name) == name
280
return strings.EqualFold(f.Name, name)
281
}
282
value = strings.TrimSpace(value) // TODO: Parse better.
283
if strings.HasPrefix(value, "...") {
284
// GraphQL fragment. It doesn't have a name.
285
return false
286
}
287
// Cut off anything that follows the field name,
288
// such as field arguments, aliases, directives.
289
if i := strings.IndexAny(value, "(:@"); i != -1 {
290
value = value[:i]
291
}
292
return strings.TrimSpace(value) == name
293
}
294
295
// isGraphQLFragment reports whether struct field f is a GraphQL fragment.
296
func isGraphQLFragment(f reflect.StructField) bool {
297
value, ok := f.Tag.Lookup("graphql")
298
if !ok {
299
return false
300
}
301
value = strings.TrimSpace(value) // TODO: Parse better.
302
return strings.HasPrefix(value, "...")
303
}
304
305
// unmarshalValue unmarshals JSON value into v.
306
// v must be addressable and not obtained by the use of unexported
307
// struct fields, otherwise unmarshalValue will panic.
308
func unmarshalValue(value json.Token, v reflect.Value) error {
309
b, err := sonic.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).
310
if err != nil {
311
return err
312
}
313
return sonic.Unmarshal(b, v.Addr().Interface())
314
}
315
316