github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/language/nodejs/license/license.go (about) 1 package license 2 3 import ( 4 "errors" 5 "io" 6 "io/fs" 7 "path" 8 "strings" 9 10 "golang.org/x/xerrors" 11 12 "github.com/aquasecurity/go-dep-parser/pkg/nodejs/packagejson" 13 "github.com/devseccon/trivy/pkg/fanal/types" 14 "github.com/devseccon/trivy/pkg/licensing" 15 "github.com/devseccon/trivy/pkg/log" 16 "github.com/devseccon/trivy/pkg/utils/fsutils" 17 ) 18 19 type License struct { 20 parser *packagejson.Parser 21 classifierConfidenceLevel float64 22 } 23 24 func NewLicense(classifierConfidenceLevel float64) *License { 25 return &License{ 26 parser: packagejson.NewParser(), 27 classifierConfidenceLevel: classifierConfidenceLevel, 28 } 29 } 30 31 func (l *License) Traverse(fsys fs.FS, root string) (map[string][]string, error) { 32 licenses := make(map[string][]string) 33 walkDirFunc := func(pkgJSONPath string, d fs.DirEntry, r io.Reader) error { 34 pkg, err := l.parser.Parse(r) 35 if err != nil { 36 return xerrors.Errorf("unable to parse %q: %w", pkgJSONPath, err) 37 } 38 39 ok, licenseFileName := IsLicenseRefToFile(pkg.License) 40 if !ok { 41 licenses[pkg.ID] = []string{pkg.License} 42 return nil 43 } 44 45 log.Logger.Debugf("License names are missing in %q, an attempt to find them in the %q file", pkgJSONPath, licenseFileName) 46 licenseFilePath := path.Join(path.Dir(pkgJSONPath), licenseFileName) 47 48 if findings, err := classifyLicense(licenseFilePath, l.classifierConfidenceLevel, fsys); err != nil { 49 return xerrors.Errorf("unable to classify the license: %w", err) 50 } else if len(findings) > 0 { 51 // License found 52 licenses[pkg.ID] = findings.Names() 53 } else { 54 log.Logger.Debugf("The license file %q was not found or the license could not be classified", licenseFilePath) 55 } 56 return nil 57 } 58 if err := fsutils.WalkDir(fsys, root, fsutils.RequiredFile(types.NpmPkg), walkDirFunc); err != nil { 59 return nil, xerrors.Errorf("walk error: %w", err) 60 } 61 62 return licenses, nil 63 } 64 65 // IsLicenseRefToFile The license field can refer to a file 66 // https://docs.npmjs.com/cli/v9/configuring-npm/package-json 67 func IsLicenseRefToFile(maybeLicense string) (bool, string) { 68 if maybeLicense == "" { 69 // trying to find at least the LICENSE file 70 return true, "LICENSE" 71 } 72 73 var licenseFileName string 74 if strings.HasPrefix(maybeLicense, "LicenseRef-") { 75 // LicenseRef-<filename> 76 licenseFileName = strings.Split(maybeLicense, "-")[1] 77 } else if strings.HasPrefix(maybeLicense, "SEE LICENSE IN ") { 78 // SEE LICENSE IN <filename> 79 parts := strings.Split(maybeLicense, " ") 80 licenseFileName = parts[len(parts)-1] 81 } 82 83 return licenseFileName != "", licenseFileName 84 } 85 86 func classifyLicense(filePath string, classifierConfidenceLevel float64, fsys fs.FS) (types.LicenseFindings, error) { 87 f, err := fsys.Open(filePath) 88 if errors.Is(err, fs.ErrNotExist) { 89 return nil, nil 90 } else if err != nil { 91 return nil, xerrors.Errorf("file open error: %w", err) 92 } 93 defer f.Close() 94 95 l, err := licensing.Classify(filePath, f, classifierConfidenceLevel) 96 if err != nil { 97 return nil, xerrors.Errorf("license classify error: %w", err) 98 } 99 100 if l == nil { 101 return nil, nil 102 } 103 104 return l.Findings, nil 105 }