github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/language/dart/pub/pubspec.go (about) 1 package pub 2 3 import ( 4 "context" 5 "io" 6 "io/fs" 7 "os" 8 "path/filepath" 9 "runtime" 10 "sort" 11 12 "github.com/samber/lo" 13 "golang.org/x/exp/maps" 14 "golang.org/x/xerrors" 15 "gopkg.in/yaml.v3" 16 17 "github.com/aquasecurity/go-dep-parser/pkg/dart/pub" 18 godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types" 19 "github.com/aquasecurity/go-dep-parser/pkg/utils" 20 "github.com/devseccon/trivy/pkg/fanal/analyzer" 21 "github.com/devseccon/trivy/pkg/fanal/analyzer/language" 22 "github.com/devseccon/trivy/pkg/fanal/types" 23 "github.com/devseccon/trivy/pkg/log" 24 "github.com/devseccon/trivy/pkg/utils/fsutils" 25 ) 26 27 func init() { 28 analyzer.RegisterPostAnalyzer(analyzer.TypePubSpecLock, newPubSpecLockAnalyzer) 29 } 30 31 const ( 32 version = 2 33 pubSpecYamlFileName = "pubspec.yaml" 34 ) 35 36 // pubSpecLockAnalyzer analyzes `pubspec.lock` 37 type pubSpecLockAnalyzer struct { 38 parser godeptypes.Parser 39 } 40 41 func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { 42 return pubSpecLockAnalyzer{ 43 parser: pub.NewParser(), 44 }, nil 45 } 46 47 func (a pubSpecLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { 48 var apps []types.Application 49 50 // get all DependsOn from cache dir 51 // lib ID -> DependsOn names 52 allDependsOn, err := findDependsOn() 53 if err != nil { 54 log.Logger.Warnf("Unable to parse cache dir: %s", err) 55 } 56 57 required := func(path string, d fs.DirEntry) bool { 58 return filepath.Base(path) == types.PubSpecLock 59 } 60 61 err = fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { 62 app, err := language.Parse(types.Pub, path, r, a.parser) 63 if err != nil { 64 return xerrors.Errorf("unable to parse %q: %w", path, err) 65 } 66 67 if app == nil { 68 return nil 69 } 70 71 if allDependsOn != nil { 72 // Required to search for library versions for DependsOn. 73 libs := lo.SliceToMap(app.Libraries, func(lib types.Package) (string, string) { 74 return lib.Name, lib.ID 75 }) 76 77 for i, lib := range app.Libraries { 78 var dependsOn []string 79 for _, depName := range allDependsOn[lib.ID] { 80 if depID, ok := libs[depName]; ok { 81 dependsOn = append(dependsOn, depID) 82 } 83 } 84 app.Libraries[i].DependsOn = dependsOn 85 } 86 } 87 88 sort.Sort(app.Libraries) 89 apps = append(apps, *app) 90 return nil 91 }) 92 if err != nil { 93 return nil, xerrors.Errorf("walk error: %w", err) 94 } 95 96 return &analyzer.AnalysisResult{ 97 Applications: apps, 98 }, nil 99 } 100 101 func findDependsOn() (map[string][]string, error) { 102 dir := cacheDir() 103 if !fsutils.DirExists(dir) { 104 log.Logger.Debugf("Cache dir (%s) not found. Need 'dart pub get' to fill dependency relationships", dir) 105 return nil, nil 106 } 107 108 required := func(path string, d fs.DirEntry) bool { 109 return filepath.Base(path) == pubSpecYamlFileName 110 } 111 112 deps := make(map[string][]string) 113 if err := fsutils.WalkDir(os.DirFS(dir), ".", required, func(path string, d fs.DirEntry, r io.Reader) error { 114 id, dependsOn, err := parsePubSpecYaml(r) 115 if err != nil { 116 log.Logger.Debugf("Unable to parse %q: %s", path, err) 117 return nil 118 } 119 if id != "" { 120 deps[id] = dependsOn 121 } 122 return nil 123 124 }); err != nil { 125 return nil, xerrors.Errorf("walk error: %w", err) 126 } 127 return deps, nil 128 } 129 130 // https://dart.dev/tools/pub/glossary#system-cache 131 func cacheDir() string { 132 if dir := os.Getenv("PUB_CACHE"); dir != "" { 133 return dir 134 } 135 136 // `%LOCALAPPDATA%\Pub\Cache` for Windows 137 if runtime.GOOS == "windows" { 138 return filepath.Join(os.Getenv("LOCALAPPDATA"), "Pub", "Cache") 139 } 140 141 // `~/.pub-cache` for Linux or Mac 142 return filepath.Join(os.Getenv("HOME"), ".pub_cache") 143 } 144 145 type pubSpecYaml struct { 146 Name string `yaml:"name"` 147 Version string `yaml:"version,omitempty"` 148 Dependencies map[string]interface{} `yaml:"dependencies,omitempty"` 149 } 150 151 func parsePubSpecYaml(r io.Reader) (string, []string, error) { 152 var spec pubSpecYaml 153 if err := yaml.NewDecoder(r).Decode(&spec); err != nil { 154 return "", nil, xerrors.Errorf("unable to decode: %w", err) 155 } 156 157 // Version is a required field only for packages from pub.dev: 158 // https://dart.dev/tools/pub/pubspec#version 159 // We can skip packages without version, 160 // because we compare packages by ID (name+version) 161 if spec.Version == "" || len(spec.Dependencies) == 0 { 162 return "", nil, nil 163 } 164 165 // pubspec.yaml uses version ranges 166 // save only dependencies names 167 dependsOn := maps.Keys(spec.Dependencies) 168 169 return utils.PackageID(spec.Name, spec.Version), dependsOn, nil 170 } 171 172 func (a pubSpecLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { 173 return filepath.Base(filePath) == types.PubSpecLock 174 } 175 176 func (a pubSpecLockAnalyzer) Type() analyzer.Type { 177 return analyzer.TypePubSpecLock 178 } 179 180 func (a pubSpecLockAnalyzer) Version() int { 181 return version 182 }