diff --git a/syft/cataloger/javascript/cataloger.go b/syft/cataloger/javascript/cataloger.go index f39a6db9071..cd926b1be94 100644 --- a/syft/cataloger/javascript/cataloger.go +++ b/syft/cataloger/javascript/cataloger.go @@ -1,4 +1,4 @@ -package npm +package javascript import ( "github.com/anchore/stereoscope/pkg/file" @@ -14,6 +14,7 @@ type Cataloger struct { func NewCataloger() *Cataloger { globParsers := map[string]common.ParserFn{ "**/package-lock.json": parsePackageLock, + "**/yarn.lock": parseYarnLock, } return &Cataloger{ diff --git a/syft/cataloger/javascript/parse_yarn_lock.go b/syft/cataloger/javascript/parse_yarn_lock.go new file mode 100644 index 00000000000..6e245c73912 --- /dev/null +++ b/syft/cataloger/javascript/parse_yarn_lock.go @@ -0,0 +1,75 @@ +package javascript + +import ( + "bufio" + "fmt" + "io" + "regexp" + "strings" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/pkg" +) + +var composedNameExp = regexp.MustCompile("^\"(@{1}[^@]+)") +var simpleNameExp = regexp.MustCompile(`^[a-zA-Z\-]+@`) +var versionExp = regexp.MustCompile(`^\W+(version)\W+`) + +func parseYarnLock(_ string, reader io.Reader) ([]pkg.Package, error) { + packages := make([]pkg.Package, 0) + fields := make(map[string]string) + var currentName string + + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimRight(line, "\n") + + // create the entry so that the loop can keep appending versions later + _, ok := fields[currentName] + if !ok { + fields[currentName] = "" + } + + switch { + case composedNameExp.MatchString(line): + name := composedNameExp.FindString(line) + if len(name) == 0 { + log.Errorf("unable to parse line: '%s'", line) + } + currentName = strings.TrimLeft(name, "\"") + case simpleNameExp.MatchString(line): + parts := strings.Split(line, "@") + currentName = parts[0] + case versionExp.MatchString(line): + parts := strings.Split(line, " \"") + version := parts[len(parts)-1] + + versions, ok := fields[currentName] + if !ok { + return nil, fmt.Errorf("no previous key exists, expecting: %s", currentName) + } + + if strings.Contains(versions, version) { + // already exists from another dependency declaration + continue + } + + // append the version as a string so that we can check on it later + fields[currentName] = versions + " " + version + packages = append(packages, pkg.Package{ + Name: currentName, + Version: strings.Trim(version, "\""), + Language: pkg.JavaScript, + Type: pkg.YarnPkg, + }) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to parse yarn.lock file: %w", err) + } + + return packages, nil +}