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 }