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 }