Files
aquasecurity-trivy/pkg/dependency/parser/rust/cargo/parse.go
Teppei Fukuda 94d6e8ced6 refactor: replace zap with slog (#6466)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: Nikita Pivkin <nikita.pivkin@smartforce.io>
Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com>
2024-04-11 18:59:09 +00:00

133 lines
3.6 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"
"github.com/aquasecurity/trivy/pkg/dependency/types"
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() types.Parser {
return &Parser{
logger: log.WithPrefix("cargo"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.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
pkgs := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) {
return pkg.Name, pkg
})
var libs []types.Library
var deps []types.Dependency
for _, pkg := range lockfile.Packages {
pkgID := packageID(pkg.Name, pkg.Version)
lib := types.Library{
ID: pkgID,
Name: pkg.Name,
Version: pkg.Version,
}
if pos, ok := lineNumIdx[pkgID]; ok {
lib.Locations = []types.Location{
{
StartLine: pos.start,
EndLine: pos.end,
},
}
}
libs = append(libs, lib)
dep := p.parseDependencies(pkgID, pkg, pkgs)
if dep != nil {
deps = append(deps, *dep)
}
}
sort.Sort(types.Libraries(libs))
sort.Sort(types.Dependencies(deps))
return libs, deps, nil
}
func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *types.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 &types.Dependency{
ID: pkgId,
DependsOn: dependOn,
}
} else {
return nil
}
}
func packageID(name, version string) string {
return dependency.ID(ftypes.Cargo, name, version)
}