github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/oracle/jsonpath/jsonpath.go (about)

     1  package jsonpath
     2  
     3  import (
     4  	"strconv"
     5  	"strings"
     6  
     7  	json "github.com/nspcc-dev/go-ordered-json"
     8  )
     9  
    10  type (
    11  	// pathTokenType represents a single JSONPath token.
    12  	pathTokenType byte
    13  
    14  	// pathParser combines a JSONPath and a position to start parsing from.
    15  	pathParser struct {
    16  		s     string
    17  		i     int
    18  		depth int
    19  	}
    20  )
    21  
    22  const (
    23  	pathInvalid pathTokenType = iota
    24  	pathRoot
    25  	pathDot
    26  	pathLeftBracket
    27  	pathRightBracket
    28  	pathAsterisk
    29  	pathComma
    30  	pathColon
    31  	pathIdentifier
    32  	pathString
    33  	pathNumber
    34  )
    35  
    36  const (
    37  	maxNestingDepth = 6
    38  	maxObjects      = 1024
    39  )
    40  
    41  // Get returns substructures of value selected by path.
    42  // The result is always non-nil unless the path is invalid.
    43  func Get(path string, value any) ([]any, bool) {
    44  	if path == "" {
    45  		return []any{value}, true
    46  	}
    47  
    48  	p := pathParser{
    49  		depth: maxNestingDepth,
    50  		s:     path,
    51  	}
    52  
    53  	typ, _ := p.nextToken()
    54  	if typ != pathRoot {
    55  		return nil, false
    56  	}
    57  
    58  	objs := []any{value}
    59  	for p.i < len(p.s) {
    60  		var ok bool
    61  
    62  		switch typ, _ := p.nextToken(); typ {
    63  		case pathDot:
    64  			objs, ok = p.processDot(objs)
    65  		case pathLeftBracket:
    66  			objs, ok = p.processLeftBracket(objs)
    67  		}
    68  
    69  		if !ok || maxObjects < len(objs) {
    70  			return nil, false
    71  		}
    72  	}
    73  
    74  	if objs == nil {
    75  		objs = []any{}
    76  	}
    77  	return objs, true
    78  }
    79  
    80  func (p *pathParser) nextToken() (pathTokenType, string) {
    81  	var (
    82  		typ     pathTokenType
    83  		value   string
    84  		ok      = true
    85  		numRead = 1
    86  	)
    87  
    88  	if p.i >= len(p.s) {
    89  		return pathInvalid, ""
    90  	}
    91  
    92  	switch c := p.s[p.i]; c {
    93  	case '$':
    94  		typ = pathRoot
    95  	case '.':
    96  		typ = pathDot
    97  	case '[':
    98  		typ = pathLeftBracket
    99  	case ']':
   100  		typ = pathRightBracket
   101  	case '*':
   102  		typ = pathAsterisk
   103  	case ',':
   104  		typ = pathComma
   105  	case ':':
   106  		typ = pathColon
   107  	case '\'':
   108  		typ = pathString
   109  		value, numRead, ok = p.parseString()
   110  	default:
   111  		switch {
   112  		case c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
   113  			typ = pathIdentifier
   114  			value, numRead, ok = p.parseIdent()
   115  		case c == '-' || ('0' <= c && c <= '9'):
   116  			typ = pathNumber
   117  			value, numRead, ok = p.parseNumber()
   118  		default:
   119  			return pathInvalid, ""
   120  		}
   121  	}
   122  
   123  	if !ok {
   124  		return pathInvalid, ""
   125  	}
   126  
   127  	p.i += numRead
   128  	return typ, value
   129  }
   130  
   131  // parseString parses a JSON string surrounded by single quotes.
   132  // It returns the number of characters consumed and true on success.
   133  func (p *pathParser) parseString() (string, int, bool) {
   134  	var end int
   135  	for end = p.i + 1; end < len(p.s); end++ {
   136  		if p.s[end] == '\'' {
   137  			return p.s[p.i : end+1], end + 1 - p.i, true
   138  		}
   139  	}
   140  
   141  	return "", 0, false
   142  }
   143  
   144  // parseIdent parses an alphanumeric identifier.
   145  // It returns the number of characters consumed and true on success.
   146  func (p *pathParser) parseIdent() (string, int, bool) {
   147  	var end int
   148  	for end = p.i + 1; end < len(p.s); end++ {
   149  		c := p.s[end]
   150  		if c != '_' && !('a' <= c && c <= 'z') &&
   151  			!('A' <= c && c <= 'Z') && !('0' <= c && c <= '9') {
   152  			break
   153  		}
   154  	}
   155  
   156  	return p.s[p.i:end], end - p.i, true
   157  }
   158  
   159  // parseNumber parses an integer number.
   160  // Only string representation is returned, size-checking is done on the first use.
   161  // It also returns the number of characters consumed and true on success.
   162  func (p *pathParser) parseNumber() (string, int, bool) {
   163  	var end int
   164  	for end = p.i + 1; end < len(p.s); end++ {
   165  		c := p.s[end]
   166  		if c < '0' || '9' < c {
   167  			break
   168  		}
   169  	}
   170  
   171  	return p.s[p.i:end], end - p.i, true
   172  }
   173  
   174  // processDot handles `.` operator.
   175  // It either descends 1 level down or performs recursive descent.
   176  func (p *pathParser) processDot(objs []any) ([]any, bool) {
   177  	typ, value := p.nextToken()
   178  	switch typ {
   179  	case pathAsterisk:
   180  		return p.descend(objs)
   181  	case pathDot:
   182  		return p.descendRecursive(objs)
   183  	case pathIdentifier:
   184  		return p.descendByIdent(objs, value)
   185  	default:
   186  		return nil, false
   187  	}
   188  }
   189  
   190  // descend descends 1 level down.
   191  // It flattens arrays and returns map values for maps.
   192  func (p *pathParser) descend(objs []any) ([]any, bool) {
   193  	if p.depth <= 0 {
   194  		return nil, false
   195  	}
   196  	p.depth--
   197  
   198  	var values []any
   199  	for i := range objs {
   200  		switch obj := objs[i].(type) {
   201  		case []any:
   202  			if maxObjects < len(values)+len(obj) {
   203  				return nil, false
   204  			}
   205  			values = append(values, obj...)
   206  		case json.OrderedObject:
   207  			if maxObjects < len(values)+len(obj) {
   208  				return nil, false
   209  			}
   210  			for i := range obj {
   211  				values = append(values, obj[i].Value)
   212  			}
   213  		}
   214  	}
   215  
   216  	return values, true
   217  }
   218  
   219  // descendRecursive performs recursive descent.
   220  func (p *pathParser) descendRecursive(objs []any) ([]any, bool) {
   221  	typ, val := p.nextToken()
   222  	if typ != pathIdentifier {
   223  		return nil, false
   224  	}
   225  
   226  	var values []any
   227  
   228  	for len(objs) > 0 {
   229  		newObjs, _ := p.descendByIdentAux(objs, false, val)
   230  		if maxObjects < len(values)+len(newObjs) {
   231  			return nil, false
   232  		}
   233  		values = append(values, newObjs...)
   234  		objs, _ = p.descend(objs)
   235  	}
   236  
   237  	return values, true
   238  }
   239  
   240  // descendByIdent performs map's field access by name.
   241  func (p *pathParser) descendByIdent(objs []any, names ...string) ([]any, bool) {
   242  	return p.descendByIdentAux(objs, true, names...)
   243  }
   244  
   245  func (p *pathParser) descendByIdentAux(objs []any, checkDepth bool, names ...string) ([]any, bool) {
   246  	if checkDepth {
   247  		if p.depth <= 0 {
   248  			return nil, false
   249  		}
   250  		p.depth--
   251  	}
   252  
   253  	var values []any
   254  	for i := range objs {
   255  		obj, ok := objs[i].(json.OrderedObject)
   256  		if !ok {
   257  			continue
   258  		}
   259  
   260  		for j := range names {
   261  			for k := range obj {
   262  				if obj[k].Key == names[j] {
   263  					if maxObjects < len(values)+1 {
   264  						return nil, false
   265  					}
   266  					values = append(values, obj[k].Value)
   267  					break
   268  				}
   269  			}
   270  		}
   271  	}
   272  	return values, true
   273  }
   274  
   275  // descendByIndex performs array access by index.
   276  func (p *pathParser) descendByIndex(objs []any, indices ...int) ([]any, bool) {
   277  	if p.depth <= 0 {
   278  		return nil, false
   279  	}
   280  	p.depth--
   281  
   282  	var values []any
   283  	for i := range objs {
   284  		obj, ok := objs[i].([]any)
   285  		if !ok {
   286  			continue
   287  		}
   288  
   289  		for _, j := range indices {
   290  			if j < 0 {
   291  				j += len(obj)
   292  			}
   293  			if 0 <= j && j < len(obj) {
   294  				if maxObjects < len(values)+1 {
   295  					return nil, false
   296  				}
   297  				values = append(values, obj[j])
   298  			}
   299  		}
   300  	}
   301  
   302  	return values, true
   303  }
   304  
   305  // processLeftBracket processes index expressions which can be either
   306  // array/map access, array sub-slice or union of indices.
   307  func (p *pathParser) processLeftBracket(objs []any) ([]any, bool) {
   308  	typ, value := p.nextToken()
   309  	switch typ {
   310  	case pathAsterisk:
   311  		typ, _ := p.nextToken()
   312  		if typ != pathRightBracket {
   313  			return nil, false
   314  		}
   315  
   316  		return p.descend(objs)
   317  	case pathColon:
   318  		return p.processSlice(objs, 0)
   319  	case pathNumber:
   320  		subTyp, _ := p.nextToken()
   321  		switch subTyp {
   322  		case pathColon:
   323  			index, err := strconv.ParseInt(value, 10, 32)
   324  			if err != nil {
   325  				return nil, false
   326  			}
   327  
   328  			return p.processSlice(objs, int(index))
   329  		case pathComma:
   330  			return p.processUnion(objs, pathNumber, value)
   331  		case pathRightBracket:
   332  			index, err := strconv.ParseInt(value, 10, 32)
   333  			if err != nil {
   334  				return nil, false
   335  			}
   336  
   337  			return p.descendByIndex(objs, int(index))
   338  		default:
   339  			return nil, false
   340  		}
   341  	case pathString:
   342  		subTyp, _ := p.nextToken()
   343  		switch subTyp {
   344  		case pathComma:
   345  			return p.processUnion(objs, pathString, value)
   346  		case pathRightBracket:
   347  			s := strings.Trim(value, "'")
   348  			err := json.Unmarshal([]byte(`"`+s+`"`), &s)
   349  			if err != nil {
   350  				return nil, false
   351  			}
   352  			return p.descendByIdent(objs, s)
   353  		default:
   354  			return nil, false
   355  		}
   356  	default:
   357  		return nil, false
   358  	}
   359  }
   360  
   361  // processUnion processes union of multiple indices.
   362  // firstTyp is assumed to be either pathNumber or pathString.
   363  func (p *pathParser) processUnion(objs []any, firstTyp pathTokenType, firstVal string) ([]any, bool) {
   364  	items := []string{firstVal}
   365  	for {
   366  		typ, val := p.nextToken()
   367  		if typ != firstTyp {
   368  			return nil, false
   369  		}
   370  
   371  		items = append(items, val)
   372  		typ, _ = p.nextToken()
   373  		if typ == pathRightBracket {
   374  			break
   375  		} else if typ != pathComma {
   376  			return nil, false
   377  		}
   378  	}
   379  
   380  	switch firstTyp {
   381  	case pathNumber:
   382  		values := make([]int, len(items))
   383  		for i := range items {
   384  			index, err := strconv.ParseInt(items[i], 10, 32)
   385  			if err != nil {
   386  				return nil, false
   387  			}
   388  			values[i] = int(index)
   389  		}
   390  		return p.descendByIndex(objs, values...)
   391  	case pathString:
   392  		for i := range items {
   393  			s := strings.Trim(items[i], "'")
   394  			err := json.Unmarshal([]byte(`"`+s+`"`), &items[i])
   395  			if err != nil {
   396  				return nil, false
   397  			}
   398  		}
   399  		return p.descendByIdent(objs, items...)
   400  	default:
   401  		panic("token in union must be either number or string")
   402  	}
   403  }
   404  
   405  // processSlice processes a slice with the specified start index.
   406  func (p *pathParser) processSlice(objs []any, start int) ([]any, bool) {
   407  	typ, val := p.nextToken()
   408  	switch typ {
   409  	case pathNumber:
   410  		typ, _ := p.nextToken()
   411  		if typ != pathRightBracket {
   412  			return nil, false
   413  		}
   414  
   415  		index, err := strconv.ParseInt(val, 10, 32)
   416  		if err != nil {
   417  			return nil, false
   418  		}
   419  
   420  		return p.descendByRange(objs, start, int(index))
   421  	case pathRightBracket:
   422  		return p.descendByRange(objs, start, 0)
   423  	default:
   424  		return nil, false
   425  	}
   426  }
   427  
   428  // descendByRange is similar to descend but skips maps and returns sub-slices for arrays.
   429  func (p *pathParser) descendByRange(objs []any, start, end int) ([]any, bool) {
   430  	if p.depth <= 0 {
   431  		return nil, false
   432  	}
   433  	p.depth--
   434  
   435  	var values []any
   436  	for i := range objs {
   437  		arr, ok := objs[i].([]any)
   438  		if !ok {
   439  			continue
   440  		}
   441  
   442  		subStart := start
   443  		if subStart < 0 {
   444  			subStart += len(arr)
   445  		}
   446  
   447  		subEnd := end
   448  		if subEnd <= 0 {
   449  			subEnd += len(arr)
   450  		}
   451  
   452  		if subEnd > len(arr) {
   453  			subEnd = len(arr)
   454  		}
   455  
   456  		if subEnd <= subStart {
   457  			continue
   458  		}
   459  		if maxObjects < len(values)+subEnd-subStart {
   460  			return nil, false
   461  		}
   462  		values = append(values, arr[subStart:subEnd]...)
   463  	}
   464  
   465  	return values, true
   466  }