mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package cargo
|
|
|
|
import (
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/samber/lo"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/aquasecurity/trivy/pkg/dependency"
|
|
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
|
"github.com/aquasecurity/trivy/pkg/log"
|
|
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
|
)
|
|
|
|
type cargoPkg struct {
|
|
Name string `toml:"name"`
|
|
Version string `toml:"version"`
|
|
Source string `toml:"source,omitempty"`
|
|
Dependencies []string `toml:"dependencies,omitempty"`
|
|
}
|
|
type Lockfile struct {
|
|
Packages []cargoPkg `toml:"package"`
|
|
}
|
|
|
|
type Parser struct {
|
|
logger *log.Logger
|
|
}
|
|
|
|
func NewParser() *Parser {
|
|
return &Parser{
|
|
logger: log.WithPrefix("cargo"),
|
|
}
|
|
}
|
|
|
|
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
|
var lockfile Lockfile
|
|
decoder := toml.NewDecoder(r)
|
|
if _, err := decoder.Decode(&lockfile); err != nil {
|
|
return nil, nil, xerrors.Errorf("decode error: %w", err)
|
|
}
|
|
|
|
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
|
return nil, nil, xerrors.Errorf("seek error: %w", err)
|
|
}
|
|
|
|
// naive parser to get line numbers by package from lock file
|
|
pkgParser := naivePkgParser{r: r}
|
|
lineNumIdx := pkgParser.parse()
|
|
|
|
// We need to get version for unique dependencies for lockfile v3 from lockfile.Packages
|
|
pkgMap := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) {
|
|
return pkg.Name, pkg
|
|
})
|
|
|
|
var pkgs ftypes.Packages
|
|
var deps ftypes.Dependencies
|
|
for _, lpkg := range lockfile.Packages {
|
|
pkgID := packageID(lpkg.Name, lpkg.Version)
|
|
pkg := ftypes.Package{
|
|
ID: pkgID,
|
|
Name: lpkg.Name,
|
|
Version: lpkg.Version,
|
|
}
|
|
if pos, ok := lineNumIdx[pkgID]; ok {
|
|
pkg.Locations = []ftypes.Location{
|
|
{
|
|
StartLine: pos.start,
|
|
EndLine: pos.end,
|
|
},
|
|
}
|
|
}
|
|
|
|
pkgs = append(pkgs, pkg)
|
|
dep := p.parseDependencies(pkgID, lpkg, pkgMap)
|
|
if dep != nil {
|
|
deps = append(deps, *dep)
|
|
}
|
|
}
|
|
sort.Sort(pkgs)
|
|
sort.Sort(deps)
|
|
return pkgs, deps, nil
|
|
}
|
|
func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *ftypes.Dependency {
|
|
var dependOn []string
|
|
|
|
for _, pkgDep := range pkg.Dependencies {
|
|
/*
|
|
Dependency entries look like:
|
|
old Cargo.lock - https://github.com/rust-lang/cargo/blob/46bac2dc448ab12fe0f182bee8d35cc804d9a6af/tests/testsuite/lockfile_compat.rs#L48-L50
|
|
"unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)"
|
|
new Cargo.lock -https://github.com/rust-lang/cargo/blob/46bac2dc448ab12fe0f182bee8d35cc804d9a6af/tests/testsuite/lockfile_compat.rs#L39-L41
|
|
"unsafe-any" - if lock file contains only 1 version of dependency
|
|
"unsafe-any 0.4.2" if lock file contains more than 1 version of dependency
|
|
*/
|
|
fields := strings.Fields(pkgDep)
|
|
switch len(fields) {
|
|
// unique dependency in new lock file
|
|
case 1:
|
|
name := fields[0]
|
|
version, ok := pkgs[name]
|
|
if !ok {
|
|
p.logger.Debug("Cannot find version", log.String("name", name))
|
|
continue
|
|
}
|
|
dependOn = append(dependOn, packageID(name, version.Version))
|
|
// 2: non-unique dependency in new lock file
|
|
// 3: old lock file
|
|
case 2, 3:
|
|
dependOn = append(dependOn, packageID(fields[0], fields[1]))
|
|
default:
|
|
p.logger.Debug("Wrong dependency format", log.String("dep", pkgDep))
|
|
continue
|
|
}
|
|
}
|
|
if len(dependOn) > 0 {
|
|
sort.Strings(dependOn)
|
|
return &ftypes.Dependency{
|
|
ID: pkgId,
|
|
DependsOn: dependOn,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func packageID(name, version string) string {
|
|
return dependency.ID(ftypes.Cargo, name, version)
|
|
}
|