mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 00:46:38 +01:00
fix: rebar lock file decoding panic (#1628)
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
package erlang
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type erlangNode struct {
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (e erlangNode) Slice() []erlangNode {
|
||||
out, ok := e.value.([]erlangNode)
|
||||
if ok {
|
||||
return out
|
||||
}
|
||||
return []erlangNode{}
|
||||
}
|
||||
|
||||
func (e erlangNode) String() string {
|
||||
out, ok := e.value.(string)
|
||||
if ok {
|
||||
return out
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e erlangNode) Get(index int) erlangNode {
|
||||
s := e.Slice()
|
||||
if len(s) > index {
|
||||
return s[index]
|
||||
}
|
||||
return erlangNode{}
|
||||
}
|
||||
|
||||
func node(value interface{}) erlangNode {
|
||||
return erlangNode{
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// parseErlang basic parser for erlang, used by rebar.lock
|
||||
func parseErlang(reader io.Reader) (erlangNode, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return node(nil), err
|
||||
}
|
||||
|
||||
out := erlangNode{
|
||||
value: []erlangNode{},
|
||||
}
|
||||
|
||||
i := 0
|
||||
for i < len(data) {
|
||||
item, err := parseErlangBlock(data, &i)
|
||||
if err != nil {
|
||||
return node(nil), fmt.Errorf("%w\n%s", err, printError(data, i))
|
||||
}
|
||||
|
||||
skipWhitespace(data, &i)
|
||||
|
||||
if i, ok := item.value.(string); ok && i == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
out.value = append(out.value.([]erlangNode), item)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func printError(data []byte, i int) string {
|
||||
line := 1
|
||||
char := 1
|
||||
|
||||
prev := []string{}
|
||||
curr := bytes.Buffer{}
|
||||
|
||||
for idx, c := range data {
|
||||
if c == '\n' {
|
||||
prev = append(prev, curr.String())
|
||||
curr.Reset()
|
||||
if idx >= i {
|
||||
break
|
||||
} else {
|
||||
line++
|
||||
}
|
||||
char = 1
|
||||
continue
|
||||
}
|
||||
if idx < i {
|
||||
char++
|
||||
}
|
||||
curr.WriteByte(c)
|
||||
}
|
||||
|
||||
l1 := fmt.Sprintf("%d", line-1)
|
||||
l2 := fmt.Sprintf("%d", line)
|
||||
|
||||
if len(l1) < len(l2) {
|
||||
l1 = " " + l1
|
||||
}
|
||||
|
||||
sep := ": "
|
||||
|
||||
lines := ""
|
||||
if len(prev) > 1 {
|
||||
lines += fmt.Sprintf("%s%s%s\n", l1, sep, prev[len(prev)-2])
|
||||
}
|
||||
if len(prev) > 0 {
|
||||
lines += fmt.Sprintf("%s%s%s\n", l2, sep, prev[len(prev)-1])
|
||||
}
|
||||
|
||||
pointer := strings.Repeat(" ", len(l2)+len(sep)+char-1) + "^"
|
||||
|
||||
return fmt.Sprintf("line: %v, char: %v\n%s%s", line, char, lines, pointer)
|
||||
}
|
||||
|
||||
func skipWhitespace(data []byte, i *int) {
|
||||
for *i < len(data) && isWhitespace(data[*i]) {
|
||||
*i++
|
||||
}
|
||||
}
|
||||
|
||||
func parseErlangBlock(data []byte, i *int) (erlangNode, error) {
|
||||
block, err := parseErlangNode(data, i)
|
||||
if err != nil {
|
||||
return node(nil), err
|
||||
}
|
||||
|
||||
skipWhitespace(data, i)
|
||||
*i++ // skip the trailing .
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func parseErlangNode(data []byte, i *int) (erlangNode, error) {
|
||||
skipWhitespace(data, i)
|
||||
c := data[*i]
|
||||
switch c {
|
||||
case '[', '{':
|
||||
return parseErlangList(data, i)
|
||||
case '"':
|
||||
return parseErlangString(data, i)
|
||||
case '<':
|
||||
return parseErlangAngleString(data, i)
|
||||
}
|
||||
|
||||
if isLiteral(c) {
|
||||
return parseErlangLiteral(data, i)
|
||||
}
|
||||
|
||||
return erlangNode{}, fmt.Errorf("invalid literal character: %s", string(c))
|
||||
}
|
||||
|
||||
func isWhitespace(c byte) bool {
|
||||
return unicode.IsSpace(rune(c))
|
||||
}
|
||||
|
||||
func isLiteral(c byte) bool {
|
||||
r := rune(c)
|
||||
return unicode.IsNumber(r) || unicode.IsLetter(r) || r == '.' || r == '_'
|
||||
}
|
||||
|
||||
func parseErlangLiteral(data []byte, i *int) (erlangNode, error) {
|
||||
var buf bytes.Buffer
|
||||
for *i < len(data) {
|
||||
c := data[*i]
|
||||
if isLiteral(c) {
|
||||
buf.WriteByte(c)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
*i++
|
||||
}
|
||||
return node(buf.String()), nil
|
||||
}
|
||||
|
||||
func parseErlangAngleString(data []byte, i *int) (erlangNode, error) {
|
||||
*i += 2
|
||||
out, err := parseErlangString(data, i)
|
||||
*i += 2
|
||||
return out, err
|
||||
}
|
||||
|
||||
func parseErlangString(data []byte, i *int) (erlangNode, error) {
|
||||
delim := data[*i]
|
||||
*i++
|
||||
var buf bytes.Buffer
|
||||
for *i < len(data) {
|
||||
c := data[*i]
|
||||
if c == delim {
|
||||
*i++
|
||||
return node(buf.String()), nil
|
||||
}
|
||||
if c == '\\' {
|
||||
*i++
|
||||
if len(data) >= *i {
|
||||
return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
|
||||
}
|
||||
c = data[*i]
|
||||
}
|
||||
buf.WriteByte(c)
|
||||
*i++
|
||||
}
|
||||
return node(buf.String()), nil
|
||||
}
|
||||
|
||||
func parseErlangList(data []byte, i *int) (erlangNode, error) {
|
||||
*i++
|
||||
out := erlangNode{
|
||||
value: []erlangNode{},
|
||||
}
|
||||
for *i < len(data) {
|
||||
item, err := parseErlangNode(data, i)
|
||||
if err != nil {
|
||||
return node(nil), err
|
||||
}
|
||||
out.value = append(out.value.([]erlangNode), item)
|
||||
skipWhitespace(data, i)
|
||||
c := data[*i]
|
||||
switch c {
|
||||
case ',':
|
||||
*i++
|
||||
continue
|
||||
case ']', '}':
|
||||
*i++
|
||||
return out, nil
|
||||
default:
|
||||
return node(nil), fmt.Errorf("unexpected character: %s", string(c))
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user