Files
trivy/pkg/dependency/parser/dart/pub/parse.go
2024-08-02 10:41:56 +00:00

144 lines
4.3 KiB
Go

package pub
import (
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
goversion "github.com/aquasecurity/go-version/pkg/version"
"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"
)
const (
directMain = "direct main"
directDev = "direct dev"
transitiveDep = "transitive"
)
// Parser is a parser for pubspec.lock
type Parser struct {
logger *log.Logger
useMinVersion bool
}
func NewParser(useMinVersion bool) *Parser {
return &Parser{
logger: log.WithPrefix("pub"),
useMinVersion: useMinVersion,
}
}
type lock struct {
Packages map[string]Dep `yaml:"packages"`
Sdks map[string]string `yaml:"sdks"`
}
type Dep struct {
Dependency string `yaml:"dependency"`
Version string `yaml:"version"`
Source string `yaml:"source"`
Description Description `yaml:"description"`
}
type Description string
func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
l := &lock{}
if err := yaml.NewDecoder(r).Decode(&l); err != nil {
return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err)
}
var pkgs []ftypes.Package
for name, dep := range l.Packages {
version := dep.Version
if version == "0.0.0" && dep.Source == "sdk" && p.useMinVersion {
version = p.findSDKVersion(l, name, dep)
}
// We would like to exclude dev dependencies, but we cannot identify
// which indirect dependencies were introduced by dev dependencies
// as there are 3 dependency types, "direct main", "direct dev" and "transitive".
// It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies.
// We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev".
pkg := ftypes.Package{
ID: dependency.ID(ftypes.Pub, name, version),
Name: name,
Version: version,
Relationship: p.relationship(dep.Dependency),
}
pkgs = append(pkgs, pkg)
}
return pkgs, nil, nil
}
// findSDKVersion detects the minimum version of the SDK constraint specified in the Description.
// If the constraint is not found, it returns the original version.
func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string {
// Some dependencies use one of the SDK versions.
// In this case dep.Version == `0.0.0`.
// We can't get versions for these dependencies.
// Therefore, we use the minimum version of the SDK constraint specified in the Description.
// See https://github.com/aquasecurity/trivy/issues/6017
constraint, ok := l.Sdks[string(dep.Description)]
if !ok {
return dep.Version
}
v, err := minVersionOfConstrain(constraint)
if err != nil {
p.logger.Warn("Unable to get sdk version from constraint", log.Err(err))
return dep.Version
} else if v == "" {
return dep.Version
}
p.logger.Info("Using the minimum version of the constraint from the sdk source", log.String("dep", name),
log.String("constraint", constraint))
return v
}
func (p Parser) relationship(dep string) ftypes.Relationship {
switch dep {
case directMain, directDev:
return ftypes.RelationshipDirect
case transitiveDep:
return ftypes.RelationshipIndirect
}
return ftypes.RelationshipUnknown
}
// minVersionOfConstrain returns the minimum acceptable version for constraint
func minVersionOfConstrain(constraint string) (string, error) {
css, err := goversion.NewConstraints(constraint)
if err != nil {
return "", xerrors.Errorf("unable to parse constraints: %w", err)
}
// Dart uses only `>=` and `^` operators:
// cf. https://dart.dev/tools/pub/dependencies#traditional-syntax
constraints := css.List()
if len(constraints) == 0 || len(constraints[0]) == 0 {
return "", nil
}
// We only need to get the minimum version from the range
if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" {
return "", nil
}
return constraints[0][0].Version(), nil
}
func (d *Description) UnmarshalYAML(value *yaml.Node) error {
var tmp any
if err := value.Decode(&tmp); err != nil {
return err
}
// Description can be a string or a struct
// We only need a string value for SDK mapping
if desc, ok := tmp.(string); ok {
*d = Description(desc)
}
return nil
}