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  }