github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/lua/rockspec_parser.go (about) 1 package lua 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 8 "github.com/anchore/syft/syft/internal/parsing" 9 ) 10 11 type rockspec struct { 12 value []rockspecNode 13 } 14 15 type rockspecNode struct { 16 key string 17 value interface{} 18 } 19 20 func (r rockspecNode) Slice() []rockspecNode { 21 out, ok := r.value.([]rockspecNode) 22 if ok { 23 return out 24 } 25 return nil 26 } 27 28 func (r rockspecNode) String() string { 29 out, ok := r.value.(string) 30 if ok { 31 return out 32 } 33 return "" 34 } 35 36 var noReturn = rockspec{ 37 value: nil, 38 } 39 40 // parseRockspec basic parser for rockspec 41 func parseRockspecData(reader io.Reader) (rockspec, error) { 42 data, err := io.ReadAll(reader) 43 if err != nil { 44 return noReturn, err 45 } 46 47 i := 0 48 locals := make(map[string]string) 49 blocks, err := parseRockspecBlock(data, &i, locals) 50 51 if err != nil { 52 return noReturn, err 53 } 54 55 return rockspec{ 56 value: blocks, 57 }, nil 58 } 59 60 func parseRockspecBlock(data []byte, i *int, locals map[string]string) ([]rockspecNode, error) { 61 var out []rockspecNode 62 var iterator func(data []byte, i *int, locals map[string]string) (*rockspecNode, error) 63 64 parsing.SkipWhitespace(data, i) 65 66 if *i >= len(data) && len(out) > 0 { 67 return nil, fmt.Errorf("unexpected end of block at %d", *i) 68 } 69 70 c := data[*i] 71 72 // Block starting with a comment 73 if c == '-' { 74 parseComment(data, i) 75 parsing.SkipWhitespace(data, i) 76 c = data[*i] 77 } 78 79 switch { 80 case c == '"' || c == '\'': 81 iterator = parseRockspecListItem 82 case isLiteral(c): 83 iterator = parseRockspecNode 84 default: 85 return nil, fmt.Errorf("unexpected character: %s", string(c)) 86 } 87 88 for *i < len(data) { 89 item, err := iterator(data, i, locals) 90 if err != nil { 91 return nil, fmt.Errorf("%w\n%s", err, parsing.PrintError(data, *i)) 92 } 93 94 parsing.SkipWhitespace(data, i) 95 96 if (item.key == "," || item.key == "-") && item.value == nil { 97 continue 98 } 99 100 if item.key == "}" && item.value == nil { 101 break 102 } 103 104 out = append(out, *item) 105 } 106 107 return out, nil 108 } 109 110 //nolint:funlen, gocognit 111 func parseRockspecNode(data []byte, i *int, locals map[string]string) (*rockspecNode, error) { 112 parsing.SkipWhitespace(data, i) 113 114 if *i >= len(data) { 115 return nil, fmt.Errorf("unexpected end of node at %d", *i) 116 } 117 118 c := data[*i] 119 120 if c == ',' || c == ';' || c == '}' { 121 *i++ 122 return &rockspecNode{ 123 key: string(c), 124 }, nil 125 } 126 127 if c == '-' { 128 offset := *i + 1 129 if offset >= len(data) { 130 return nil, fmt.Errorf("unexpected character: %s", string(c)) 131 } 132 c2 := data[offset] 133 134 if c2 != '-' { 135 return nil, fmt.Errorf("unexpected character: %s", string(c2)) 136 } 137 138 parseComment(data, i) 139 return &rockspecNode{ 140 key: string(c), 141 }, nil 142 } 143 144 if !isLiteral(c) { 145 return nil, fmt.Errorf("invalid literal character: %s", string(c)) 146 } 147 148 key, err := parseRockspecLiteral(data, i, locals) 149 if err != nil { 150 return nil, err 151 } 152 153 parsing.SkipWhitespace(data, i) 154 155 if *i >= len(data) { 156 return nil, fmt.Errorf("unexpected end of node at %d", *i) 157 } 158 159 if key == "local" { 160 err := parseLocal(data, i, locals) 161 if err != nil { 162 return nil, err 163 } 164 return &rockspecNode{ 165 key: ",", 166 }, nil 167 } 168 169 c = data[*i] 170 if c != '=' { 171 return nil, fmt.Errorf("unexpected character: %s", string(c)) 172 } 173 174 *i++ 175 parsing.SkipWhitespace(data, i) 176 177 if *i >= len(data) { 178 return nil, fmt.Errorf("unexpected end of node at %d", *i) 179 } 180 181 if key == "build" { 182 skipBuildNode(data, i) 183 184 return &rockspecNode{ 185 key: ",", 186 }, nil 187 } 188 189 c = data[*i] 190 191 switch c { 192 case '{': 193 offset := *i + 1 194 parsing.SkipWhitespace(data, &offset) 195 if offset >= len(data) { 196 return nil, fmt.Errorf("unterminated block at %d", *i) 197 } 198 c2 := data[offset] 199 200 // Add support for empty lists 201 if c == '{' && c2 == '}' { 202 *i = offset + 1 203 return &rockspecNode{}, nil 204 } 205 206 *i = offset 207 parsing.SkipWhitespace(data, i) 208 209 obj, err := parseRockspecBlock(data, i, locals) 210 211 if err != nil { 212 return nil, err 213 } 214 value := obj 215 216 return &rockspecNode{ 217 key, value, 218 }, nil 219 case '(': 220 skipExpression(data, i) 221 return &rockspecNode{ 222 key: ",", 223 }, nil 224 case '[': 225 offset := *i + 1 226 if offset >= len(data) { 227 return nil, fmt.Errorf("unterminated block at %d", *i) 228 } 229 c2 := data[offset] 230 231 if c2 != '[' { 232 return nil, fmt.Errorf("unexpected character: %s", string(c)) 233 } 234 235 *i++ 236 237 str, err := parseRockspecString(data, i, locals) 238 239 if err != nil { 240 return nil, err 241 } 242 value := str.String() 243 244 c = data[*i] 245 246 if c != ']' { 247 return nil, fmt.Errorf("unexpected character: %s", string(c)) 248 } 249 250 *i++ 251 252 return &rockspecNode{ 253 key, value, 254 }, nil 255 } 256 257 value, err := parseRockspecValue(data, i, locals, "") 258 259 if err != nil { 260 return nil, err 261 } 262 263 return &rockspecNode{ 264 key, value, 265 }, nil 266 } 267 268 func parseRockspecListItem(data []byte, i *int, locals map[string]string) (*rockspecNode, error) { 269 parsing.SkipWhitespace(data, i) 270 271 if *i >= len(data) { 272 return nil, fmt.Errorf("unexpected end of block at %d", *i) 273 } 274 275 c := data[*i] 276 if c == ',' || c == ';' || c == '}' { 277 *i++ 278 return &rockspecNode{ 279 key: string(c), 280 }, nil 281 } 282 283 if c == '-' { 284 offset := *i + 1 285 if offset >= len(data) { 286 return nil, fmt.Errorf("unexpected character: %s", string(c)) 287 } 288 c2 := data[offset] 289 290 if c2 != '-' { 291 return nil, fmt.Errorf("unexpected character: %s", string(c2)) 292 } 293 294 parseComment(data, i) 295 return &rockspecNode{ 296 key: string(c), 297 }, nil 298 } 299 300 str, err := parseRockspecString(data, i, locals) 301 if err != nil { 302 return nil, err 303 } 304 return str, nil 305 } 306 307 func parseRockspecValue(data []byte, i *int, locals map[string]string, initialValue string) (string, error) { 308 c := data[*i] 309 310 var value string 311 312 switch c { 313 case '"', '\'': 314 str, err := parseRockspecString(data, i, locals) 315 316 if err != nil { 317 return "", err 318 } 319 value = str.value.(string) 320 default: 321 local, err := parseRockspecLiteral(data, i, locals) 322 323 if err != nil { 324 return "", err 325 } 326 327 l, ok := locals[local] 328 329 if !ok { 330 return "", fmt.Errorf("unknown local: %s", local) 331 } 332 333 value = l 334 } 335 336 value = fmt.Sprintf("%s%s", initialValue, value) 337 338 skipWhitespaceNoNewLine(data, i) 339 340 if len(data) > *i+2 { 341 if data[*i] == '.' && data[*i+1] == '.' { 342 *i += 2 343 344 skipWhitespaceNoNewLine(data, i) 345 346 if *i >= len(data) { 347 return "", fmt.Errorf("unexpected end of expression at %d", *i) 348 } 349 350 v, err := parseRockspecValue(data, i, locals, value) 351 352 if err != nil { 353 return "", err 354 } 355 356 value = v 357 } 358 } 359 360 return value, nil 361 } 362 363 func parseRockspecLiteral(data []byte, i *int, locals map[string]string) (string, error) { 364 var buf bytes.Buffer 365 out: 366 for *i < len(data) { 367 c := data[*i] 368 switch { 369 case c == '[': 370 *i++ 371 nested, err := parseRockspecString(data, i, locals) 372 if err != nil { 373 return "", err 374 } 375 c = data[*i] 376 if c != ']' { 377 return "", fmt.Errorf("unterminated literal at %d", *i) 378 } 379 buf.WriteString(fmt.Sprintf("[\"%s\"]", nested.String())) 380 case isLiteral(c): 381 buf.WriteByte(c) 382 default: 383 break out 384 } 385 *i++ 386 } 387 return buf.String(), nil 388 } 389 390 func parseRockspecString(data []byte, i *int, _ map[string]string) (*rockspecNode, error) { 391 delim := data[*i] 392 var endDelim byte 393 switch delim { 394 case '"', '\'': 395 endDelim = delim 396 case '[': 397 endDelim = ']' 398 } 399 400 *i++ 401 var buf bytes.Buffer 402 for *i < len(data) { 403 c := data[*i] 404 if c == endDelim { 405 *i++ 406 str := rockspecNode{value: buf.String()} 407 return &str, nil 408 } 409 buf.WriteByte(c) 410 *i++ 411 } 412 return nil, fmt.Errorf("unterminated string at %d", *i) 413 } 414 415 func parseComment(data []byte, i *int) { 416 for *i < len(data) { 417 c := data[*i] 418 419 *i++ 420 421 // Rest of a line is a comment. Deals with CR, LF and CR/LF 422 if c == '\n' { 423 break 424 } else if c == '\r' && data[*i] == '\n' { 425 *i++ 426 break 427 } 428 } 429 } 430 431 //nolint:funlen 432 func parseLocal(data []byte, i *int, locals map[string]string) error { 433 keys := []string{} 434 values := []string{} 435 436 keys: 437 for { 438 parsing.SkipWhitespace(data, i) 439 440 key, err := parseRockspecLiteral(data, i, locals) 441 if err != nil { 442 return err 443 } 444 445 if key == "function" { 446 err := skipFunction(data, i) 447 if err != nil { 448 return err 449 } 450 return nil 451 } 452 453 keys = append(keys, key) 454 455 parsing.SkipWhitespace(data, i) 456 457 c := data[*i] 458 459 switch c { 460 case ',': 461 *i++ 462 continue 463 case '=': 464 *i++ 465 break keys 466 default: 467 return fmt.Errorf("unexpected character: %s", string(c)) 468 } 469 } 470 471 values: 472 for { 473 skipWhitespaceNoNewLine(data, i) 474 475 c := data[*i] 476 477 switch c { 478 case '"', '\'': 479 value, err := parseRockspecString(data, i, locals) 480 481 if err != nil { 482 return err 483 } 484 values = append(values, value.value.(string)) 485 default: 486 ref, err := parseRockspecLiteral(data, i, locals) 487 if err != nil { 488 return err 489 } 490 491 // Skip if it's an expression 492 skipWhitespaceNoNewLine(data, i) 493 c := data[*i] 494 495 var value string 496 497 if c != '\n' && c != '\r' { 498 skipExpression(data, i) 499 value = "" 500 } else { 501 value = locals[ref] 502 } 503 504 values = append(values, value) 505 } 506 507 skipWhitespaceNoNewLine(data, i) 508 509 c = data[*i] 510 511 switch c { 512 case ',': 513 *i++ 514 continue 515 case '\n', '\r': 516 parsing.SkipWhitespace(data, i) 517 break values 518 } 519 } 520 521 if len(keys) != len(values) { 522 return fmt.Errorf("expected %d values got %d", len(keys), len(values)) 523 } 524 525 for i := 0; i < len(keys); i++ { 526 locals[keys[i]] = values[i] 527 } 528 529 return nil 530 } 531 532 func skipBuildNode(data []byte, i *int) { 533 bracesCount := 0 534 535 for *i < len(data) { 536 c := data[*i] 537 538 switch c { 539 case '{': 540 bracesCount++ 541 case '}': 542 bracesCount-- 543 } 544 545 if bracesCount == 0 { 546 return 547 } 548 549 *i++ 550 } 551 } 552 553 func skipFunction(data []byte, i *int) error { 554 blocks := 1 555 556 for *i < len(data)-5 { 557 if parsing.IsWhitespace(data[*i]) { 558 switch { 559 case string(data[*i+1:*i+3]) == "if" && parsing.IsWhitespace(data[*i+3]): 560 blocks++ 561 *i += 3 562 case string(data[*i+1:*i+4]) == "end" && parsing.IsWhitespace(data[*i+4]): 563 blocks-- 564 *i += 4 565 566 if blocks == 0 { 567 return nil 568 } 569 default: 570 *i++ 571 } 572 } else { 573 *i++ 574 } 575 } 576 577 return fmt.Errorf("unterminated function at %d", *i) 578 } 579 580 func skipExpression(data []byte, i *int) { 581 parseComment(data, i) 582 } 583 584 func skipWhitespaceNoNewLine(data []byte, i *int) { 585 for *i < len(data) && (data[*i] == ' ' || data[*i] == '\t') { 586 *i++ 587 } 588 } 589 590 func isLiteral(c byte) bool { 591 if c == '[' || c == ']' { 592 return true 593 } 594 if c == '.' { 595 return false 596 } 597 return parsing.IsLiteral(c) 598 }