@@ -2,35 +2,171 @@ package pub
2
2
3
3
import (
4
4
"context"
5
+ "io"
6
+ "io/fs"
5
7
"os"
6
8
"path/filepath"
9
+ "runtime"
10
+ "sort"
7
11
12
+ "github.com/samber/lo"
13
+ "golang.org/x/exp/maps"
8
14
"golang.org/x/xerrors"
15
+ "gopkg.in/yaml.v3"
9
16
10
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"
11
20
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
12
21
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
13
22
"github.com/aquasecurity/trivy/pkg/fanal/types"
23
+ "github.com/aquasecurity/trivy/pkg/log"
24
+ "github.com/aquasecurity/trivy/pkg/utils/fsutils"
14
25
)
15
26
16
27
func init () {
17
- analyzer .RegisterAnalyzer ( & pubSpecLockAnalyzer {} )
28
+ analyzer .RegisterPostAnalyzer ( analyzer . TypePubSpecLock , newPubSpecLockAnalyzer )
18
29
}
19
30
20
31
const (
21
- version = 1
32
+ version = 2
33
+ pubSpecYamlFileName = "pubspec.yaml"
22
34
)
23
35
24
- // pubSpecLockAnalyzer analyzes pubspec.lock
25
- type pubSpecLockAnalyzer struct {}
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
+ }
26
46
27
- func (a pubSpecLockAnalyzer ) Analyze (_ context.Context , input analyzer.AnalysisInput ) (* analyzer.AnalysisResult , error ) {
28
- p := pub .NewParser ()
29
- res , err := language .Analyze (types .Pub , input .FilePath , input .Content , p )
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 ()
30
53
if err != nil {
31
- return nil , xerrors .Errorf ("%s parse error: %w" , input .FilePath , err )
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
32
110
}
33
- return res , nil
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
34
170
}
35
171
36
172
func (a pubSpecLockAnalyzer ) Required (filePath string , _ os.FileInfo ) bool {
0 commit comments