github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/language/php/composer/composer.go (about) 1 package composer 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "io" 8 "io/fs" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 14 "golang.org/x/exp/slices" 15 "golang.org/x/xerrors" 16 17 "github.com/aquasecurity/go-dep-parser/pkg/php/composer" 18 godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types" 19 "github.com/devseccon/trivy/pkg/fanal/analyzer" 20 "github.com/devseccon/trivy/pkg/fanal/analyzer/language" 21 "github.com/devseccon/trivy/pkg/fanal/types" 22 "github.com/devseccon/trivy/pkg/log" 23 "github.com/devseccon/trivy/pkg/utils/fsutils" 24 ) 25 26 func init() { 27 analyzer.RegisterPostAnalyzer(analyzer.TypeComposer, newComposerAnalyzer) 28 } 29 30 const version = 1 31 32 var requiredFiles = []string{ 33 types.ComposerLock, 34 types.ComposerJson, 35 } 36 37 type composerAnalyzer struct { 38 lockParser godeptypes.Parser 39 } 40 41 func newComposerAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { 42 return &composerAnalyzer{ 43 lockParser: composer.NewParser(), 44 }, nil 45 } 46 47 func (a composerAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { 48 var apps []types.Application 49 50 required := func(path string, d fs.DirEntry) bool { 51 return filepath.Base(path) == types.ComposerLock 52 } 53 54 err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { 55 // Parse composer.lock 56 app, err := a.parseComposerLock(path, r) 57 if err != nil { 58 return xerrors.Errorf("parse error: %w", err) 59 } else if app == nil { 60 return nil 61 } 62 63 // Parse composer.json alongside composer.lock to identify the direct dependencies 64 if err = a.mergeComposerJson(input.FS, filepath.Dir(path), app); err != nil { 65 log.Logger.Warnf("Unable to parse %q to identify direct dependencies: %s", filepath.Join(filepath.Dir(path), types.ComposerJson), err) 66 } 67 sort.Sort(app.Libraries) 68 apps = append(apps, *app) 69 70 return nil 71 }) 72 if err != nil { 73 return nil, xerrors.Errorf("composer walk error: %w", err) 74 } 75 76 return &analyzer.AnalysisResult{ 77 Applications: apps, 78 }, nil 79 } 80 81 func (a composerAnalyzer) Required(filePath string, _ os.FileInfo) bool { 82 fileName := filepath.Base(filePath) 83 if !slices.Contains(requiredFiles, fileName) { 84 return false 85 } 86 87 // Skip `composer.lock` inside `vendor` folder 88 if slices.Contains(strings.Split(filePath, "/"), "vendor") { 89 return false 90 } 91 return true 92 } 93 94 func (a composerAnalyzer) Type() analyzer.Type { 95 return analyzer.TypeComposer 96 } 97 98 func (a composerAnalyzer) Version() int { 99 return version 100 } 101 102 func (a composerAnalyzer) parseComposerLock(path string, r io.Reader) (*types.Application, error) { 103 return language.Parse(types.Composer, path, r, a.lockParser) 104 } 105 106 func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.Application) error { 107 // Parse composer.json to identify the direct dependencies 108 path := filepath.Join(dir, types.ComposerJson) 109 p, err := a.parseComposerJson(fsys, path) 110 if errors.Is(err, fs.ErrNotExist) { 111 // Assume all the packages are direct dependencies as it cannot identify them from composer.lock 112 log.Logger.Debugf("Unable to determine the direct dependencies: %s not found", path) 113 return nil 114 } else if err != nil { 115 return xerrors.Errorf("unable to parse %s: %w", path, err) 116 } 117 118 for i, lib := range app.Libraries { 119 // Identify the direct/transitive dependencies 120 if _, ok := p[lib.Name]; !ok { 121 app.Libraries[i].Indirect = true 122 } 123 } 124 125 return nil 126 } 127 128 type composerJson struct { 129 Require map[string]string `json:"require"` 130 } 131 132 func (a composerAnalyzer) parseComposerJson(fsys fs.FS, path string) (map[string]string, error) { 133 // Parse composer.json 134 f, err := fsys.Open(path) 135 if err != nil { 136 return nil, xerrors.Errorf("file open error: %w", err) 137 } 138 defer func() { _ = f.Close() }() 139 140 jsonFile := composerJson{} 141 err = json.NewDecoder(f).Decode(&jsonFile) 142 if err != nil { 143 return nil, xerrors.Errorf("json decode error: %w", err) 144 } 145 return jsonFile.Require, nil 146 }