Path: blob/dev/pkg/reporting/trackers/linear/jsonutil/jsonutil.go
2070 views
// Package jsonutil provides a function for decoding JSON1// into a GraphQL query data structure.2//3// Taken from: https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/internal/jsonutil/graphql.go4package jsonutil56import (7"bytes"8"encoding/json"9"errors"10"fmt"11"io"12"reflect"13"strings"1415sonic "github.com/projectdiscovery/nuclei/v3/pkg/utils/json"16)1718// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores19// the result in the GraphQL query data structure pointed to by v.20//21// The implementation is created on top of the JSON tokenizer available22// in "encoding/json".Decoder.23func UnmarshalGraphQL(data []byte, v any) error {24dec := json.NewDecoder(bytes.NewReader(data))25dec.UseNumber()26err := (&decoder{tokenizer: dec}).Decode(v)27if err != nil {28return err29}30tok, err := dec.Token()31switch err {32case io.EOF:33// Expect to get io.EOF. There shouldn't be any more34// tokens left after we've decoded v successfully.35return nil36case nil:37return fmt.Errorf("invalid token '%v' after top-level value", tok)38default:39return err40}41}4243// decoder is a JSON decoder that performs custom unmarshaling behavior44// for GraphQL query data structures. It's implemented on top of a JSON tokenizer.45type decoder struct {46tokenizer interface {47Token() (json.Token, error)48}4950// Stack of what part of input JSON we're in the middle of - objects, arrays.51parseState []json.Delim5253// Stacks of values where to unmarshal.54// The top of each stack is the reflect.Value where to unmarshal next JSON value.55//56// The reason there's more than one stack is because we might be unmarshaling57// a single JSON value into multiple GraphQL fragments or embedded structs, so58// we keep track of them all.59vs [][]reflect.Value60}6162// Decode decodes a single JSON value from d.tokenizer into v.63func (d *decoder) Decode(v any) error {64rv := reflect.ValueOf(v)65if rv.Kind() != reflect.Ptr {66return fmt.Errorf("cannot decode into non-pointer %T", v)67}68d.vs = [][]reflect.Value{{rv.Elem()}}69return d.decode()70}7172// decode decodes a single JSON value from d.tokenizer into d.vs.73func (d *decoder) decode() error {74// The loop invariant is that the top of each d.vs stack75// is where we try to unmarshal the next JSON value we see.76for len(d.vs) > 0 {77tok, err := d.tokenizer.Token()78if err == io.EOF {79return errors.New("unexpected end of JSON input")80} else if err != nil {81return err82}8384switch {8586// Are we inside an object and seeing next key (rather than end of object)?87case d.state() == '{' && tok != json.Delim('}'):88key, ok := tok.(string)89if !ok {90return errors.New("unexpected non-key in JSON input")91}92someFieldExist := false93for i := range d.vs {94v := d.vs[i][len(d.vs[i])-1]95if v.Kind() == reflect.Ptr {96v = v.Elem()97}98var f reflect.Value99if v.Kind() == reflect.Struct {100f = fieldByGraphQLName(v, key)101if f.IsValid() {102someFieldExist = true103}104}105d.vs[i] = append(d.vs[i], f)106}107if !someFieldExist {108return fmt.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))109}110111// We've just consumed the current token, which was the key.112// Read the next token, which should be the value, and let the rest of code process it.113tok, err = d.tokenizer.Token()114if err == io.EOF {115return errors.New("unexpected end of JSON input")116} else if err != nil {117return err118}119120// Are we inside an array and seeing next value (rather than end of array)?121case d.state() == '[' && tok != json.Delim(']'):122someSliceExist := false123for i := range d.vs {124v := d.vs[i][len(d.vs[i])-1]125if v.Kind() == reflect.Ptr {126v = v.Elem()127}128var f reflect.Value129if v.Kind() == reflect.Slice {130v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T).131f = v.Index(v.Len() - 1)132someSliceExist = true133}134d.vs[i] = append(d.vs[i], f)135}136if !someSliceExist {137return fmt.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs))138}139}140141switch tok := tok.(type) {142case string, json.Number, bool, nil:143// Value.144145for i := range d.vs {146v := d.vs[i][len(d.vs[i])-1]147if !v.IsValid() {148continue149}150err := unmarshalValue(tok, v)151if err != nil {152return err153}154}155d.popAllVs()156157case json.Delim:158switch tok {159case '{':160// Start of object.161162d.pushState(tok)163164frontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs.165for i := range d.vs {166v := d.vs[i][len(d.vs[i])-1]167frontier[i] = v168// TODO: Do this recursively or not? Add a test case if needed.169if v.Kind() == reflect.Ptr && v.IsNil() {170v.Set(reflect.New(v.Type().Elem())) // v = new(T).171}172}173// Find GraphQL fragments/embedded structs recursively, adding to frontier174// as new ones are discovered and exploring them further.175for len(frontier) > 0 {176v := frontier[0]177frontier = frontier[1:]178if v.Kind() == reflect.Ptr {179v = v.Elem()180}181if v.Kind() != reflect.Struct {182continue183}184for i := 0; i < v.NumField(); i++ {185if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {186// Add GraphQL fragment or embedded struct.187d.vs = append(d.vs, []reflect.Value{v.Field(i)})188frontier = append(frontier, v.Field(i))189}190}191}192case '[':193// Start of array.194195d.pushState(tok)196197for i := range d.vs {198v := d.vs[i][len(d.vs[i])-1]199// TODO: Confirm this is needed, write a test case.200//if v.Kind() == reflect.Ptr && v.IsNil() {201// v.Set(reflect.New(v.Type().Elem())) // v = new(T).202//}203204// Reset slice to empty (in case it had non-zero initial value).205if v.Kind() == reflect.Ptr {206v = v.Elem()207}208if v.Kind() != reflect.Slice {209continue210}211v.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0).212}213case '}', ']':214// End of object or array.215d.popAllVs()216d.popState()217default:218return errors.New("unexpected delimiter in JSON input")219}220default:221return errors.New("unexpected token in JSON input")222}223}224return nil225}226227// pushState pushes a new parse state s onto the stack.228func (d *decoder) pushState(s json.Delim) {229d.parseState = append(d.parseState, s)230}231232// popState pops a parse state (already obtained) off the stack.233// The stack must be non-empty.234func (d *decoder) popState() {235d.parseState = d.parseState[:len(d.parseState)-1]236}237238// state reports the parse state on top of stack, or 0 if empty.239func (d *decoder) state() json.Delim {240if len(d.parseState) == 0 {241return 0242}243return d.parseState[len(d.parseState)-1]244}245246// popAllVs pops from all d.vs stacks, keeping only non-empty ones.247func (d *decoder) popAllVs() {248var nonEmpty [][]reflect.Value249for i := range d.vs {250d.vs[i] = d.vs[i][:len(d.vs[i])-1]251if len(d.vs[i]) > 0 {252nonEmpty = append(nonEmpty, d.vs[i])253}254}255d.vs = nonEmpty256}257258// fieldByGraphQLName returns an exported struct field of struct v259// that matches GraphQL name, or invalid reflect.Value if none found.260func fieldByGraphQLName(v reflect.Value, name string) reflect.Value {261for i := 0; i < v.NumField(); i++ {262if v.Type().Field(i).PkgPath != "" {263// Skip unexported field.264continue265}266if hasGraphQLName(v.Type().Field(i), name) {267return v.Field(i)268}269}270return reflect.Value{}271}272273// hasGraphQLName reports whether struct field f has GraphQL name.274func hasGraphQLName(f reflect.StructField, name string) bool {275value, ok := f.Tag.Lookup("graphql")276if !ok {277// TODO: caseconv package is relatively slow. Optimize it, then consider using it here.278//return caseconv.MixedCapsToLowerCamelCase(f.Name) == name279return strings.EqualFold(f.Name, name)280}281value = strings.TrimSpace(value) // TODO: Parse better.282if strings.HasPrefix(value, "...") {283// GraphQL fragment. It doesn't have a name.284return false285}286// Cut off anything that follows the field name,287// such as field arguments, aliases, directives.288if i := strings.IndexAny(value, "(:@"); i != -1 {289value = value[:i]290}291return strings.TrimSpace(value) == name292}293294// isGraphQLFragment reports whether struct field f is a GraphQL fragment.295func isGraphQLFragment(f reflect.StructField) bool {296value, ok := f.Tag.Lookup("graphql")297if !ok {298return false299}300value = strings.TrimSpace(value) // TODO: Parse better.301return strings.HasPrefix(value, "...")302}303304// unmarshalValue unmarshals JSON value into v.305// v must be addressable and not obtained by the use of unexported306// struct fields, otherwise unmarshalValue will panic.307func unmarshalValue(value json.Token, v reflect.Value) error {308b, err := sonic.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).309if err != nil {310return err311}312return sonic.Unmarshal(b, v.Addr().Interface())313}314315316