github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/language/analyze.go (about)

     1  package language
     2  
     3  import (
     4  	"io"
     5  	"strings"
     6  
     7  	"golang.org/x/xerrors"
     8  
     9  	dio "github.com/aquasecurity/go-dep-parser/pkg/io"
    10  	godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
    11  	"github.com/devseccon/trivy/pkg/digest"
    12  	"github.com/devseccon/trivy/pkg/fanal/analyzer"
    13  	"github.com/devseccon/trivy/pkg/fanal/types"
    14  	"github.com/devseccon/trivy/pkg/licensing"
    15  	"github.com/devseccon/trivy/pkg/log"
    16  	xio "github.com/devseccon/trivy/pkg/x/io"
    17  )
    18  
    19  // Analyze returns an analysis result of the lock file
    20  func Analyze(fileType types.LangType, filePath string, r dio.ReadSeekerAt, parser godeptypes.Parser) (*analyzer.AnalysisResult, error) {
    21  	app, err := Parse(fileType, filePath, r, parser)
    22  	if err != nil {
    23  		return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
    24  	}
    25  
    26  	if app == nil {
    27  		return nil, nil
    28  	}
    29  
    30  	return &analyzer.AnalysisResult{Applications: []types.Application{*app}}, nil
    31  }
    32  
    33  // AnalyzePackage returns an analysis result of the package file other than lock files
    34  func AnalyzePackage(fileType types.LangType, filePath string, r dio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*analyzer.AnalysisResult, error) {
    35  	app, err := ParsePackage(fileType, filePath, r, parser, checksum)
    36  	if err != nil {
    37  		return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
    38  	}
    39  
    40  	if app == nil {
    41  		return nil, nil
    42  	}
    43  
    44  	return &analyzer.AnalysisResult{Applications: []types.Application{*app}}, nil
    45  }
    46  
    47  // Parse returns a parsed result of the lock file
    48  func Parse(fileType types.LangType, filePath string, r io.Reader, parser godeptypes.Parser) (*types.Application, error) {
    49  	rr, err := xio.NewReadSeekerAt(r)
    50  	if err != nil {
    51  		return nil, xerrors.Errorf("reader error: %w", err)
    52  	}
    53  	parsedLibs, parsedDependencies, err := parser.Parse(rr)
    54  	if err != nil {
    55  		return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
    56  	}
    57  
    58  	// The file path of each library should be empty in case of dependency list such as lock file
    59  	// since they all will be the same path.
    60  	return toApplication(fileType, filePath, "", nil, parsedLibs, parsedDependencies), nil
    61  }
    62  
    63  // ParsePackage returns a parsed result of the package file
    64  func ParsePackage(fileType types.LangType, filePath string, r dio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*types.Application, error) {
    65  	parsedLibs, parsedDependencies, err := parser.Parse(r)
    66  	if err != nil {
    67  		return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
    68  	}
    69  
    70  	// The reader is not passed if the checksum is not necessarily calculated.
    71  	if !checksum {
    72  		r = nil
    73  	}
    74  
    75  	// The file path of each library should be empty in case of dependency list such as lock file
    76  	// since they all will be the same path.
    77  	return toApplication(fileType, filePath, filePath, r, parsedLibs, parsedDependencies), nil
    78  }
    79  
    80  func toApplication(fileType types.LangType, filePath, libFilePath string, r dio.ReadSeekerAt, libs []godeptypes.Library, depGraph []godeptypes.Dependency) *types.Application {
    81  	if len(libs) == 0 {
    82  		return nil
    83  	}
    84  
    85  	// Calculate the file digest when one of `spdx` formats is selected
    86  	d, err := calculateDigest(r)
    87  	if err != nil {
    88  		log.Logger.Warnf("Unable to get checksum for %s: %s", filePath, err)
    89  	}
    90  
    91  	deps := make(map[string][]string)
    92  	for _, dep := range depGraph {
    93  		deps[dep.ID] = dep.DependsOn
    94  	}
    95  
    96  	var pkgs []types.Package
    97  	for _, lib := range libs {
    98  		var licenses []string
    99  		if lib.License != "" {
   100  			licenses = licensing.SplitLicenses(lib.License)
   101  			for i, license := range licenses {
   102  				licenses[i] = licensing.Normalize(strings.TrimSpace(license))
   103  			}
   104  		}
   105  		var locs []types.Location
   106  		for _, loc := range lib.Locations {
   107  			l := types.Location{
   108  				StartLine: loc.StartLine,
   109  				EndLine:   loc.EndLine,
   110  			}
   111  			locs = append(locs, l)
   112  		}
   113  
   114  		// This file path is populated for virtual file paths within archives, such as nested JAR files.
   115  		libPath := libFilePath
   116  		if lib.FilePath != "" {
   117  			libPath = lib.FilePath
   118  		}
   119  		pkgs = append(pkgs, types.Package{
   120  			ID:        lib.ID,
   121  			Name:      lib.Name,
   122  			Version:   lib.Version,
   123  			Dev:       lib.Dev,
   124  			FilePath:  libPath,
   125  			Indirect:  lib.Indirect,
   126  			Licenses:  licenses,
   127  			DependsOn: deps[lib.ID],
   128  			Locations: locs,
   129  			Digest:    d,
   130  		})
   131  	}
   132  
   133  	return &types.Application{
   134  		Type:      fileType,
   135  		FilePath:  filePath,
   136  		Libraries: pkgs,
   137  	}
   138  }
   139  
   140  func calculateDigest(r dio.ReadSeekerAt) (digest.Digest, error) {
   141  	if r == nil {
   142  		return "", nil
   143  	}
   144  	// return reader to start after it has been read in analyzer
   145  	if _, err := r.Seek(0, io.SeekStart); err != nil {
   146  		return "", xerrors.Errorf("unable to seek: %w", err)
   147  	}
   148  
   149  	return digest.CalcSHA1(r)
   150  }