mirror of
https://github.com/anchore/syft.git
synced 2026-02-13 02:56:42 +01:00
* feat: update to go 1.24.x Update to building with go 1.24.x so that the main module version gets set during `go build` Signed-off-by: Weston Steimel <author@code.w.steimel.me.uk> * chore: bump golangci-lint for go 1.24.x support Signed-off-by: Weston Steimel <author@code.w.steimel.me.uk> * chore: appease the updated linter Signed-off-by: Weston Steimel <author@code.w.steimel.me.uk> * chore: fix test logging for go 1.24 Signed-off-by: Weston Steimel <author@code.w.steimel.me.uk> --------- Signed-off-by: Weston Steimel <author@code.w.steimel.me.uk>
599 lines
10 KiB
Go
599 lines
10 KiB
Go
package lua
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/anchore/syft/syft/internal/parsing"
|
|
)
|
|
|
|
type rockspec struct {
|
|
value []rockspecNode
|
|
}
|
|
|
|
type rockspecNode struct {
|
|
key string
|
|
value interface{}
|
|
}
|
|
|
|
func (r rockspecNode) Slice() []rockspecNode {
|
|
out, ok := r.value.([]rockspecNode)
|
|
if ok {
|
|
return out
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r rockspecNode) String() string {
|
|
out, ok := r.value.(string)
|
|
if ok {
|
|
return out
|
|
}
|
|
return ""
|
|
}
|
|
|
|
var noReturn = rockspec{
|
|
value: nil,
|
|
}
|
|
|
|
// parseRockspec basic parser for rockspec
|
|
func parseRockspecData(reader io.Reader) (rockspec, error) {
|
|
data, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return noReturn, err
|
|
}
|
|
|
|
i := 0
|
|
locals := make(map[string]string)
|
|
blocks, err := parseRockspecBlock(data, &i, locals)
|
|
|
|
if err != nil {
|
|
return noReturn, err
|
|
}
|
|
|
|
return rockspec{
|
|
value: blocks,
|
|
}, nil
|
|
}
|
|
|
|
func parseRockspecBlock(data []byte, i *int, locals map[string]string) ([]rockspecNode, error) {
|
|
var out []rockspecNode
|
|
var iterator func(data []byte, i *int, locals map[string]string) (*rockspecNode, error)
|
|
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
if *i >= len(data) && len(out) > 0 {
|
|
return nil, fmt.Errorf("unexpected end of block at %d", *i)
|
|
}
|
|
|
|
c := data[*i]
|
|
|
|
// Block starting with a comment
|
|
if c == '-' {
|
|
parseComment(data, i)
|
|
parsing.SkipWhitespace(data, i)
|
|
c = data[*i]
|
|
}
|
|
|
|
switch {
|
|
case c == '"' || c == '\'':
|
|
iterator = parseRockspecListItem
|
|
case isLiteral(c):
|
|
iterator = parseRockspecNode
|
|
default:
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
|
|
for *i < len(data) {
|
|
item, err := iterator(data, i, locals)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w\n%s", err, parsing.PrintError(data, *i))
|
|
}
|
|
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
if (item.key == "," || item.key == "-") && item.value == nil {
|
|
continue
|
|
}
|
|
|
|
if item.key == "}" && item.value == nil {
|
|
break
|
|
}
|
|
|
|
out = append(out, *item)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
//nolint:funlen, gocognit
|
|
func parseRockspecNode(data []byte, i *int, locals map[string]string) (*rockspecNode, error) {
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
if *i >= len(data) {
|
|
return nil, fmt.Errorf("unexpected end of node at %d", *i)
|
|
}
|
|
|
|
c := data[*i]
|
|
|
|
if c == ',' || c == ';' || c == '}' {
|
|
*i++
|
|
return &rockspecNode{
|
|
key: string(c),
|
|
}, nil
|
|
}
|
|
|
|
if c == '-' {
|
|
offset := *i + 1
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
c2 := data[offset]
|
|
|
|
if c2 != '-' {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c2))
|
|
}
|
|
|
|
parseComment(data, i)
|
|
return &rockspecNode{
|
|
key: string(c),
|
|
}, nil
|
|
}
|
|
|
|
if !isLiteral(c) {
|
|
return nil, fmt.Errorf("invalid literal character: %s", string(c))
|
|
}
|
|
|
|
key, err := parseRockspecLiteral(data, i, locals)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
if *i >= len(data) {
|
|
return nil, fmt.Errorf("unexpected end of node at %d", *i)
|
|
}
|
|
|
|
if key == "local" {
|
|
err := parseLocal(data, i, locals)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &rockspecNode{
|
|
key: ",",
|
|
}, nil
|
|
}
|
|
|
|
c = data[*i]
|
|
if c != '=' {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
|
|
*i++
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
if *i >= len(data) {
|
|
return nil, fmt.Errorf("unexpected end of node at %d", *i)
|
|
}
|
|
|
|
if key == "build" {
|
|
skipBuildNode(data, i)
|
|
|
|
return &rockspecNode{
|
|
key: ",",
|
|
}, nil
|
|
}
|
|
|
|
c = data[*i]
|
|
|
|
switch c {
|
|
case '{':
|
|
offset := *i + 1
|
|
parsing.SkipWhitespace(data, &offset)
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("unterminated block at %d", *i)
|
|
}
|
|
c2 := data[offset]
|
|
|
|
// Add support for empty lists
|
|
if c == '{' && c2 == '}' {
|
|
*i = offset + 1
|
|
return &rockspecNode{}, nil
|
|
}
|
|
|
|
*i = offset
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
obj, err := parseRockspecBlock(data, i, locals)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value := obj
|
|
|
|
return &rockspecNode{
|
|
key, value,
|
|
}, nil
|
|
case '(':
|
|
skipExpression(data, i)
|
|
return &rockspecNode{
|
|
key: ",",
|
|
}, nil
|
|
case '[':
|
|
offset := *i + 1
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("unterminated block at %d", *i)
|
|
}
|
|
c2 := data[offset]
|
|
|
|
if c2 != '[' {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
|
|
*i++
|
|
|
|
str, err := parseRockspecString(data, i, locals)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value := str.String()
|
|
|
|
c = data[*i]
|
|
|
|
if c != ']' {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
|
|
*i++
|
|
|
|
return &rockspecNode{
|
|
key, value,
|
|
}, nil
|
|
}
|
|
|
|
value, err := parseRockspecValue(data, i, locals, "")
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &rockspecNode{
|
|
key, value,
|
|
}, nil
|
|
}
|
|
|
|
func parseRockspecListItem(data []byte, i *int, locals map[string]string) (*rockspecNode, error) {
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
if *i >= len(data) {
|
|
return nil, fmt.Errorf("unexpected end of block at %d", *i)
|
|
}
|
|
|
|
c := data[*i]
|
|
if c == ',' || c == ';' || c == '}' {
|
|
*i++
|
|
return &rockspecNode{
|
|
key: string(c),
|
|
}, nil
|
|
}
|
|
|
|
if c == '-' {
|
|
offset := *i + 1
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
c2 := data[offset]
|
|
|
|
if c2 != '-' {
|
|
return nil, fmt.Errorf("unexpected character: %s", string(c2))
|
|
}
|
|
|
|
parseComment(data, i)
|
|
return &rockspecNode{
|
|
key: string(c),
|
|
}, nil
|
|
}
|
|
|
|
str, err := parseRockspecString(data, i, locals)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return str, nil
|
|
}
|
|
|
|
func parseRockspecValue(data []byte, i *int, locals map[string]string, initialValue string) (string, error) {
|
|
c := data[*i]
|
|
|
|
var value string
|
|
|
|
switch c {
|
|
case '"', '\'':
|
|
str, err := parseRockspecString(data, i, locals)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
value = str.value.(string)
|
|
default:
|
|
local, err := parseRockspecLiteral(data, i, locals)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
l, ok := locals[local]
|
|
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown local: %s", local)
|
|
}
|
|
|
|
value = l
|
|
}
|
|
|
|
value = fmt.Sprintf("%s%s", initialValue, value)
|
|
|
|
skipWhitespaceNoNewLine(data, i)
|
|
|
|
if len(data) > *i+2 {
|
|
if data[*i] == '.' && data[*i+1] == '.' {
|
|
*i += 2
|
|
|
|
skipWhitespaceNoNewLine(data, i)
|
|
|
|
if *i >= len(data) {
|
|
return "", fmt.Errorf("unexpected end of expression at %d", *i)
|
|
}
|
|
|
|
v, err := parseRockspecValue(data, i, locals, value)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
value = v
|
|
}
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
func parseRockspecLiteral(data []byte, i *int, locals map[string]string) (string, error) {
|
|
var buf bytes.Buffer
|
|
out:
|
|
for *i < len(data) {
|
|
c := data[*i]
|
|
switch {
|
|
case c == '[':
|
|
*i++
|
|
nested, err := parseRockspecString(data, i, locals)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
c = data[*i]
|
|
if c != ']' {
|
|
return "", fmt.Errorf("unterminated literal at %d", *i)
|
|
}
|
|
buf.WriteString(fmt.Sprintf("[\"%s\"]", nested.String()))
|
|
case isLiteral(c):
|
|
buf.WriteByte(c)
|
|
default:
|
|
break out
|
|
}
|
|
*i++
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func parseRockspecString(data []byte, i *int, _ map[string]string) (*rockspecNode, error) {
|
|
delim := data[*i]
|
|
var endDelim byte
|
|
switch delim {
|
|
case '"', '\'':
|
|
endDelim = delim
|
|
case '[':
|
|
endDelim = ']'
|
|
}
|
|
|
|
*i++
|
|
var buf bytes.Buffer
|
|
for *i < len(data) {
|
|
c := data[*i]
|
|
if c == endDelim {
|
|
*i++
|
|
str := rockspecNode{value: buf.String()}
|
|
return &str, nil
|
|
}
|
|
buf.WriteByte(c)
|
|
*i++
|
|
}
|
|
return nil, fmt.Errorf("unterminated string at %d", *i)
|
|
}
|
|
|
|
func parseComment(data []byte, i *int) {
|
|
for *i < len(data) {
|
|
c := data[*i]
|
|
|
|
*i++
|
|
|
|
// Rest of a line is a comment. Deals with CR, LF and CR/LF
|
|
if c == '\n' {
|
|
break
|
|
} else if c == '\r' && data[*i] == '\n' {
|
|
*i++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
//nolint:funlen
|
|
func parseLocal(data []byte, i *int, locals map[string]string) error {
|
|
keys := []string{}
|
|
values := []string{}
|
|
|
|
keys:
|
|
for {
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
key, err := parseRockspecLiteral(data, i, locals)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if key == "function" {
|
|
err := skipFunction(data, i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
keys = append(keys, key)
|
|
|
|
parsing.SkipWhitespace(data, i)
|
|
|
|
c := data[*i]
|
|
|
|
switch c {
|
|
case ',':
|
|
*i++
|
|
continue
|
|
case '=':
|
|
*i++
|
|
break keys
|
|
default:
|
|
return fmt.Errorf("unexpected character: %s", string(c))
|
|
}
|
|
}
|
|
|
|
values:
|
|
for {
|
|
skipWhitespaceNoNewLine(data, i)
|
|
|
|
c := data[*i]
|
|
|
|
switch c {
|
|
case '"', '\'':
|
|
value, err := parseRockspecString(data, i, locals)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values = append(values, value.value.(string))
|
|
default:
|
|
ref, err := parseRockspecLiteral(data, i, locals)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Skip if it's an expression
|
|
skipWhitespaceNoNewLine(data, i)
|
|
c := data[*i]
|
|
|
|
var value string
|
|
|
|
if c != '\n' && c != '\r' {
|
|
skipExpression(data, i)
|
|
value = ""
|
|
} else {
|
|
value = locals[ref]
|
|
}
|
|
|
|
values = append(values, value)
|
|
}
|
|
|
|
skipWhitespaceNoNewLine(data, i)
|
|
|
|
c = data[*i]
|
|
|
|
switch c {
|
|
case ',':
|
|
*i++
|
|
continue
|
|
case '\n', '\r':
|
|
parsing.SkipWhitespace(data, i)
|
|
break values
|
|
}
|
|
}
|
|
|
|
if len(keys) != len(values) {
|
|
return fmt.Errorf("expected %d values got %d", len(keys), len(values))
|
|
}
|
|
|
|
for i := 0; i < len(keys); i++ {
|
|
locals[keys[i]] = values[i]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func skipBuildNode(data []byte, i *int) {
|
|
bracesCount := 0
|
|
|
|
for *i < len(data) {
|
|
c := data[*i]
|
|
|
|
switch c {
|
|
case '{':
|
|
bracesCount++
|
|
case '}':
|
|
bracesCount--
|
|
}
|
|
|
|
if bracesCount == 0 {
|
|
return
|
|
}
|
|
|
|
*i++
|
|
}
|
|
}
|
|
|
|
func skipFunction(data []byte, i *int) error {
|
|
blocks := 1
|
|
|
|
for *i < len(data)-5 {
|
|
if parsing.IsWhitespace(data[*i]) {
|
|
switch {
|
|
case string(data[*i+1:*i+3]) == "if" && parsing.IsWhitespace(data[*i+3]):
|
|
blocks++
|
|
*i += 3
|
|
case string(data[*i+1:*i+4]) == "end" && parsing.IsWhitespace(data[*i+4]):
|
|
blocks--
|
|
*i += 4
|
|
|
|
if blocks == 0 {
|
|
return nil
|
|
}
|
|
default:
|
|
*i++
|
|
}
|
|
} else {
|
|
*i++
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("unterminated function at %d", *i)
|
|
}
|
|
|
|
func skipExpression(data []byte, i *int) {
|
|
parseComment(data, i)
|
|
}
|
|
|
|
func skipWhitespaceNoNewLine(data []byte, i *int) {
|
|
for *i < len(data) && (data[*i] == ' ' || data[*i] == '\t') {
|
|
*i++
|
|
}
|
|
}
|
|
|
|
func isLiteral(c byte) bool {
|
|
if c == '[' || c == ']' {
|
|
return true
|
|
}
|
|
if c == '.' {
|
|
return false
|
|
}
|
|
return parsing.IsLiteral(c)
|
|
}
|