Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion syft/pkg/cataloger/dart/package.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package dart

import (
"context"

"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/licenses"
)

func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...file.Location) pkg.Package {
Expand All @@ -29,7 +32,7 @@ func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...fil
return p
}

func newPubspecPackage(raw pubspecPackage, locations ...file.Location) pkg.Package {
func newPubspecPackage(ctx context.Context, resolver file.Resolver, raw pubspecPackage, locations ...file.Location) pkg.Package {
var env *pkg.DartPubspecEnvironment
if raw.Environment.SDK != "" || raw.Environment.Flutter != "" {
// this is required only after pubspec v2, but might have been optional before this
Expand Down Expand Up @@ -58,6 +61,8 @@ func newPubspecPackage(raw pubspecPackage, locations ...file.Location) pkg.Packa

p.SetID()

p = licenses.RelativeToPackage(ctx, resolver, p)

return p
}

Expand Down
4 changes: 3 additions & 1 deletion syft/pkg/cataloger/dart/parse_pubspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type dartPubspecEnvironment struct {
Flutter string `mapstructure:"flutter" yaml:"flutter"`
}

func parsePubspec(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parsePubspec(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package

dec := yaml.NewDecoder(reader)
Expand All @@ -41,6 +41,8 @@ func parsePubspec(_ context.Context, _ file.Resolver, _ *generic.Environment, re

pkgs = append(pkgs,
newPubspecPackage(
ctx,
resolver,
p,
reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Expand Down
67 changes: 35 additions & 32 deletions syft/pkg/cataloger/golang/license_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,53 @@ import (
"context"
"fmt"
"path/filepath"
"regexp"
"strings"

"github.com/spf13/afero"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/licenses"
)

// resolveModuleLicenses finds and parses license files for Go modules
func resolveModuleLicenses(ctx context.Context, pkgInfos []pkgInfo, fs afero.Fs) pkg.LicenseSet {
licenses := pkg.NewLicenseSet()
func resolveModuleLicenses(ctx context.Context, scanRoot string, pkgInfos []pkgInfo, fs afero.Fs) pkg.LicenseSet {
out := pkg.NewLicenseSet()

for _, info := range pkgInfos {
modDir, pkgDir, err := getAbsolutePkgPaths(info)
if err != nil {
continue
}

licenseFiles, err := findAllLicenseCandidatesUpwards(pkgDir, licenseRegexp, modDir, fs)
licenseFiles, err := findAllLicenseCandidatesUpwards(pkgDir, modDir, fs)
if err != nil {
continue
}

for _, f := range licenseFiles {
contents, err := fs.Open(f)
if err != nil {
continue
}
licenses.Add(pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(file.Location{}, contents))...)
_ = contents.Close()
out.Add(readLicenses(ctx, scanRoot, fs, f)...)
}
}

return licenses
return out
}

func readLicenses(ctx context.Context, scanRoot string, fs afero.Fs, f string) []pkg.License {
contents, err := fs.Open(f)
if err != nil {
log.WithFields("file", f, "error", err).Debug("unable to read license file")
return nil
}
defer internal.CloseAndLogError(contents, f)
location := file.Location{}
if scanRoot != "" && strings.HasPrefix(f, scanRoot) {
// include location when licenses are found within the scan target
location = file.NewLocation(strings.TrimPrefix(f, scanRoot))
}
return pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(location, contents))
}

/*
Expand All @@ -60,7 +72,7 @@ When we should consider redesign tip to stem:
- We need to consider the case here where nested modules are visited by accident and licenses
are erroneously associated to a 'parent module'; bubble up currently prevents this
*/
func findAllLicenseCandidatesUpwards(dir string, r *regexp.Regexp, stopAt string, fs afero.Fs) ([]string, error) {
func findAllLicenseCandidatesUpwards(dir string, stopAt string, fs afero.Fs) ([]string, error) {
// Validate that both paths are absolute
if !filepath.IsAbs(dir) {
return nil, fmt.Errorf("dir must be an absolute path, got: %s", dir)
Expand All @@ -69,43 +81,34 @@ func findAllLicenseCandidatesUpwards(dir string, r *regexp.Regexp, stopAt string
return nil, fmt.Errorf("stopAt must be an absolute path, got: %s", stopAt)
}

licenses, err := findLicenseCandidates(dir, r, stopAt, fs)
if err != nil {
return nil, err
}

// Ensure we return an empty slice rather than nil for consistency
if licenses == nil {
return []string{}, nil
}
return licenses, nil
return findLicenseCandidates(dir, stopAt, fs)
}

func findLicenseCandidates(dir string, r *regexp.Regexp, stopAt string, fs afero.Fs) ([]string, error) {
func findLicenseCandidates(dir string, stopAt string, fs afero.Fs) ([]string, error) {
// stop if we've gone outside the stopAt directory
if !strings.HasPrefix(dir, stopAt) {
return []string{}, nil
}

licenses, err := findLicensesInDir(dir, r, fs)
out, err := findLicensesInDir(dir, fs)
if err != nil {
return nil, err
}

parent := filepath.Dir(dir)
// can't go any higher up the directory tree: "/" case
if parent == dir {
return licenses, nil
return out, nil
}

// search parent directory and combine results
parentLicenses, err := findLicenseCandidates(parent, r, stopAt, fs)
parentLicenses, err := findLicenseCandidates(parent, stopAt, fs)
if err != nil {
return nil, err
}

// Combine current directory licenses with parent directory licenses
return append(licenses, parentLicenses...), nil
return append(out, parentLicenses...), nil
}

func getAbsolutePkgPaths(info pkgInfo) (modDir string, pkgDir string, err error) {
Expand All @@ -126,8 +129,8 @@ func getAbsolutePkgPaths(info pkgInfo) (modDir string, pkgDir string, err error)
return modDir, pkgDir, nil
}

func findLicensesInDir(dir string, r *regexp.Regexp, fs afero.Fs) ([]string, error) {
var licenses []string
func findLicensesInDir(dir string, fs afero.Fs) ([]string, error) {
var out []string

dirContents, err := afero.ReadDir(fs, dir)
if err != nil {
Expand All @@ -139,11 +142,11 @@ func findLicensesInDir(dir string, r *regexp.Regexp, fs afero.Fs) ([]string, err
continue
}

if r.MatchString(f.Name()) {
if licenses.IsLicenseFile(f.Name()) {
path := filepath.Join(dir, f.Name())
licenses = append(licenses, path)
out = append(out, path)
}
}

return licenses, nil
return out, nil
}
6 changes: 3 additions & 3 deletions syft/pkg/cataloger/golang/license_finder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ func TestFindAllLicenseCandidatesUpwards(t *testing.T) {
fs.MkdirAll("/empty/dir/tree", 0755)
// No license files
},
expectedFiles: []string{},
description: "Should return empty slice when no license files found",
expectedFiles: nil,
description: "Should return nil when no license files found",
},
{
name: "handles directory at filesystem root",
Expand Down Expand Up @@ -205,7 +205,7 @@ func TestFindAllLicenseCandidatesUpwards(t *testing.T) {
tt.setupFS(fs)

// Run the function
result, err := findAllLicenseCandidatesUpwards(tt.startDir, licenseRegexp, tt.stopAt, fs)
result, err := findAllLicenseCandidatesUpwards(tt.startDir, tt.stopAt, fs)

// Check error expectation
if tt.expectedError {
Expand Down
41 changes: 15 additions & 26 deletions syft/pkg/cataloger/golang/licenses.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,21 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/scylladb/go-set/strset"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/cache"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/licenses"
)

type goLicenseResolver struct {
catalogerName string
opts CatalogerConfig
localModCacheDir fs.FS
localVendorDir fs.FS
licenseCache cache.Resolver[[]pkg.License]
lowerLicenseFileNames *strset.Set
catalogerName string
opts CatalogerConfig
localModCacheDir fs.FS
localVendorDir fs.FS
licenseCache cache.Resolver[[]pkg.License]
}

func newGoLicenseResolver(catalogerName string, opts CatalogerConfig) goLicenseResolver {
Expand All @@ -59,23 +57,14 @@ func newGoLicenseResolver(catalogerName string, opts CatalogerConfig) goLicenseR
}

return goLicenseResolver{
catalogerName: catalogerName,
opts: opts,
localModCacheDir: localModCacheDir,
localVendorDir: localVendorDir,
licenseCache: cache.GetResolverCachingErrors[[]pkg.License]("golang", "v2"),
lowerLicenseFileNames: strset.New(lowercaseLicenseFiles()...),
catalogerName: catalogerName,
opts: opts,
localModCacheDir: localModCacheDir,
localVendorDir: localVendorDir,
licenseCache: cache.GetResolverCachingErrors[[]pkg.License]("golang", "v2"),
}
}

func lowercaseLicenseFiles() []string {
fileNames := licenses.FileNames()
for i := range fileNames {
fileNames[i] = strings.ToLower(fileNames[i])
}
return fileNames
}

func remotesForModule(proxies []string, noProxy []string, module string) []string {
for _, pattern := range noProxy {
if matched, err := path.Match(pattern, module); err == nil && matched {
Expand Down Expand Up @@ -194,7 +183,7 @@ func (c *goLicenseResolver) findLicensesInFS(ctx context.Context, urlPrefix stri
log.Debugf("nil entry for %s#%s", urlPrefix, filePath)
return nil
}
if !c.lowerLicenseFileNames.Has(strings.ToLower(d.Name())) {
if !licenses.IsLicenseFile(d.Name()) {
return nil
}
rdr, err := fsys.Open(filePath)
Expand All @@ -203,11 +192,11 @@ func (c *goLicenseResolver) findLicensesInFS(ctx context.Context, urlPrefix stri
return nil
}
defer internal.CloseAndLogError(rdr, filePath)
licenses := pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(file.NewLocation(filePath), rdr))
foundLicenses := pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(file.NewLocation(filePath), rdr))
// since these licenses are found in an external fs.FS, not in the scanned source,
// get rid of the locations but keep information about the where the license was found
// by prepending the urlPrefix to the internal path for an accurate representation
for _, l := range licenses {
for _, l := range foundLicenses {
l.URLs = []string{urlPrefix + filePath}
l.Locations = file.NewLocationSet()
out = append(out, l)
Expand Down Expand Up @@ -246,7 +235,7 @@ func (c *goLicenseResolver) findLicensesInSource(ctx context.Context, resolver f
func (c *goLicenseResolver) parseLicenseFromLocation(ctx context.Context, l file.Location, resolver file.Resolver) ([]pkg.License, error) {
var out []pkg.License
fileName := path.Base(l.RealPath)
if c.lowerLicenseFileNames.Has(strings.ToLower(fileName)) {
if licenses.IsLicenseFile(fileName) {
contents, err := resolver.FileContentsByLocation(l)
if err != nil {
return nil, err
Expand Down
25 changes: 16 additions & 9 deletions syft/pkg/cataloger/golang/parse_go_mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"go/build"
"io"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"

Expand All @@ -20,14 +20,11 @@ import (
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/fileresolver"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)

var (
licenseRegexp = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING|NOTICE).*$`)
)

type goModCataloger struct {
licenseResolver goLicenseResolver
}
Expand All @@ -46,9 +43,14 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
log.Debugf("unable to get go.sum: %v", err)
}

scanRoot := ""
if dir, ok := resolver.(*fileresolver.Directory); ok && dir != nil {
scanRoot = dir.Chroot.Base()
}

// source analysis using go toolchain if available
syftSourcePackages, sourceModules, sourceDependencies, unknownErr := c.loadPackages(modDir, reader.Location)
catalogedModules, sourceModuleToPkg := c.catalogModules(ctx, syftSourcePackages, sourceModules, reader, digests)
catalogedModules, sourceModuleToPkg := c.catalogModules(ctx, scanRoot, syftSourcePackages, sourceModules, reader, digests)
relationships := buildModuleRelationships(catalogedModules, sourceDependencies, sourceModuleToPkg)

// base case go.mod file parsing
Expand Down Expand Up @@ -208,12 +210,16 @@ func (c *goModCataloger) visitPackages(
}
}
}
pkgs[module.Path] = append(pkgs[module.Path], pkgInfo{

info := pkgInfo{
pkgPath: p.PkgPath,
modulePath: module.Path,
pkgDir: pkgDir,
moduleDir: module.Dir,
})
}
if !slices.Contains(pkgs[module.Path], info) { // avoid duplicates
pkgs[module.Path] = append(pkgs[module.Path], info)
}
modules[p.Module.Path] = module

return true
Expand All @@ -224,6 +230,7 @@ func (c *goModCataloger) visitPackages(
// create syft packages from Go modules found by the go toolchain
func (c *goModCataloger) catalogModules(
ctx context.Context,
scanRoot string,
pkgs map[string][]pkgInfo,
modules map[string]*packages.Module,
reader file.LocationReadCloser,
Expand All @@ -243,7 +250,7 @@ func (c *goModCataloger) catalogModules(
}

pkgInfos := pkgs[m.Path]
moduleLicenses := resolveModuleLicenses(ctx, pkgInfos, afero.NewOsFs())
moduleLicenses := resolveModuleLicenses(ctx, scanRoot, pkgInfos, afero.NewOsFs())
// we do out of source lookups for module parsing
// locations are NOT included in the SBOM because of this
goModulePkg := pkg.Package{
Expand Down
Loading
Loading