flags.go (8394B)
1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package scan 6 7 import ( 8 "errors" 9 "flag" 10 "fmt" 11 "io" 12 "os" 13 "strings" 14 15 "golang.org/x/tools/go/buildutil" 16 "golang.org/x/vuln/internal/govulncheck" 17 ) 18 19 type config struct { 20 govulncheck.Config 21 patterns []string 22 db string 23 dir string 24 tags buildutil.TagsFlag 25 test bool 26 show ShowFlag 27 format FormatFlag 28 version bool 29 env []string 30 } 31 32 func parseFlags(cfg *config, stderr io.Writer, args []string) error { 33 var version bool 34 var json bool 35 var scanFlag ScanFlag 36 var modeFlag ModeFlag 37 flags := flag.NewFlagSet("", flag.ContinueOnError) 38 flags.SetOutput(stderr) 39 flags.BoolVar(&json, "json", false, "output JSON (Go compatible legacy flag, see format flag)") 40 flags.BoolVar(&cfg.test, "test", false, "analyze test files (only valid for source mode, default false)") 41 flags.StringVar(&cfg.dir, "C", "", "change to `dir` before running govulncheck") 42 flags.StringVar(&cfg.db, "db", "https://vuln.go.dev", "vulnerability database `url`") 43 flags.Var(&modeFlag, "mode", "supports 'source', 'binary', and 'extract' (default 'source')") 44 flags.Var(&cfg.tags, "tags", "comma-separated `list` of build tags") 45 flags.Var(&cfg.show, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'") 46 flags.Var(&cfg.format, "format", "specify format output\nThe supported values are 'text', 'json', 'sarif', and 'openvex' (default 'text')") 47 flags.BoolVar(&version, "version", false, "print the version information") 48 flags.Var(&scanFlag, "scan", "set the scanning level desired, one of 'module', 'package', or 'symbol' (default 'symbol')") 49 50 // We don't want to print the whole usage message on each flags 51 // error, so we set to a no-op and do the printing ourselves. 52 flags.Usage = func() {} 53 usage := func() { 54 fmt.Fprint(flags.Output(), `Govulncheck reports known vulnerabilities in dependencies. 55 56 Usage: 57 58 govulncheck [flags] [patterns] 59 govulncheck -mode=binary [flags] [binary] 60 61 `) 62 flags.PrintDefaults() 63 fmt.Fprintf(flags.Output(), "\n%s\n", detailsMessage) 64 } 65 66 if err := flags.Parse(args); err != nil { 67 if err == flag.ErrHelp { 68 usage() // print usage only on help 69 return errHelp 70 } 71 return errUsage 72 } 73 cfg.patterns = flags.Args() 74 if version { 75 cfg.show = append(cfg.show, "version") 76 cfg.version = true 77 } 78 cfg.ScanLevel = govulncheck.ScanLevel(scanFlag) 79 cfg.ScanMode = govulncheck.ScanMode(modeFlag) 80 if err := validateConfig(cfg, json); err != nil { 81 fmt.Fprintln(flags.Output(), err) 82 return errUsage 83 } 84 return nil 85 } 86 87 func validateConfig(cfg *config, json bool) error { 88 // take care of default values 89 if cfg.ScanMode == "" { 90 cfg.ScanMode = govulncheck.ScanModeSource 91 } 92 if cfg.ScanLevel == "" { 93 cfg.ScanLevel = govulncheck.ScanLevelSymbol 94 } 95 if json { 96 if cfg.format != formatUnset { 97 return fmt.Errorf("the -json flag cannot be used with -format flag") 98 } 99 cfg.format = formatJSON 100 } else { 101 if cfg.format == formatUnset { 102 cfg.format = formatText 103 } 104 } 105 106 // show flag is only supported with text output 107 if cfg.format != formatText && len(cfg.show) > 0 { 108 return fmt.Errorf("the -show flag is not supported for %s output", cfg.format) 109 } 110 111 switch cfg.ScanMode { 112 case govulncheck.ScanModeSource: 113 if len(cfg.patterns) == 1 && isFile(cfg.patterns[0]) { 114 return fmt.Errorf("%q is a file.\n\n%v", cfg.patterns[0], errNoBinaryFlag) 115 } 116 if cfg.ScanLevel == govulncheck.ScanLevelModule && len(cfg.patterns) != 0 { 117 return fmt.Errorf("patterns are not accepted for module only scanning") 118 } 119 case govulncheck.ScanModeBinary: 120 if cfg.test { 121 return fmt.Errorf("the -test flag is not supported in binary mode") 122 } 123 if len(cfg.tags) > 0 { 124 return fmt.Errorf("the -tags flag is not supported in binary mode") 125 } 126 if len(cfg.patterns) != 1 { 127 return fmt.Errorf("only 1 binary can be analyzed at a time") 128 } 129 if !isFile(cfg.patterns[0]) { 130 return fmt.Errorf("%q is not a file", cfg.patterns[0]) 131 } 132 case govulncheck.ScanModeExtract: 133 if cfg.test { 134 return fmt.Errorf("the -test flag is not supported in extract mode") 135 } 136 if len(cfg.tags) > 0 { 137 return fmt.Errorf("the -tags flag is not supported in extract mode") 138 } 139 if len(cfg.patterns) != 1 { 140 return fmt.Errorf("only 1 binary can be extracted at a time") 141 } 142 if cfg.format == formatJSON { 143 return fmt.Errorf("the json format must be off in extract mode") 144 } 145 if !isFile(cfg.patterns[0]) { 146 return fmt.Errorf("%q is not a file (source extraction is not supported)", cfg.patterns[0]) 147 } 148 case govulncheck.ScanModeConvert: 149 if len(cfg.patterns) != 0 { 150 return fmt.Errorf("patterns are not accepted in convert mode") 151 } 152 if cfg.dir != "" { 153 return fmt.Errorf("the -C flag is not supported in convert mode") 154 } 155 if cfg.test { 156 return fmt.Errorf("the -test flag is not supported in convert mode") 157 } 158 if len(cfg.tags) > 0 { 159 return fmt.Errorf("the -tags flag is not supported in convert mode") 160 } 161 case govulncheck.ScanModeQuery: 162 if cfg.test { 163 return fmt.Errorf("the -test flag is not supported in query mode") 164 } 165 if len(cfg.tags) > 0 { 166 return fmt.Errorf("the -tags flag is not supported in query mode") 167 } 168 if cfg.format != formatJSON { 169 return fmt.Errorf("the json format must be set in query mode") 170 } 171 for _, pattern := range cfg.patterns { 172 // Parse the input here so that we can catch errors before 173 // outputting the Config. 174 if _, _, err := parseModuleQuery(pattern); err != nil { 175 return err 176 } 177 } 178 } 179 return nil 180 } 181 182 func isFile(path string) bool { 183 s, err := os.Stat(path) 184 if err != nil { 185 return false 186 } 187 return !s.IsDir() 188 } 189 190 var errFlagParse = errors.New("see -help for details") 191 192 // ShowFlag is used for parsing and validation of 193 // govulncheck -show flag. 194 type ShowFlag []string 195 196 var supportedShows = map[string]bool{ 197 "traces": true, 198 "color": true, 199 "verbose": true, 200 "version": true, 201 } 202 203 func (v *ShowFlag) Set(s string) error { 204 if s == "" { 205 return nil 206 } 207 for _, show := range strings.Split(s, ",") { 208 sh := strings.TrimSpace(show) 209 if _, ok := supportedShows[sh]; !ok { 210 return errFlagParse 211 } 212 *v = append(*v, sh) 213 } 214 return nil 215 } 216 217 func (v *ShowFlag) Get() interface{} { return *v } 218 func (v *ShowFlag) String() string { return "" } 219 220 // Update the text handler h with values of the flag. 221 func (v ShowFlag) Update(h *TextHandler) { 222 for _, show := range v { 223 switch show { 224 case "traces": 225 h.showTraces = true 226 case "color": 227 h.showColor = true 228 case "version": 229 h.showVersion = true 230 case "verbose": 231 h.showVerbose = true 232 } 233 } 234 } 235 236 // FormatFlag is used for parsing and validation of 237 // govulncheck -format flag. 238 type FormatFlag string 239 240 const ( 241 formatUnset = "" 242 formatJSON = "json" 243 formatText = "text" 244 formatSarif = "sarif" 245 formatOpenVEX = "openvex" 246 ) 247 248 var supportedFormats = map[string]bool{ 249 formatJSON: true, 250 formatText: true, 251 formatSarif: true, 252 formatOpenVEX: true, 253 } 254 255 func (f *FormatFlag) Get() interface{} { return *f } 256 func (f *FormatFlag) Set(s string) error { 257 if _, ok := supportedFormats[s]; !ok { 258 return errFlagParse 259 } 260 *f = FormatFlag(s) 261 return nil 262 } 263 func (f *FormatFlag) String() string { return "" } 264 265 // ModeFlag is used for parsing and validation of 266 // govulncheck -mode flag. 267 type ModeFlag string 268 269 var supportedModes = map[string]bool{ 270 govulncheck.ScanModeSource: true, 271 govulncheck.ScanModeBinary: true, 272 govulncheck.ScanModeConvert: true, 273 govulncheck.ScanModeQuery: true, 274 govulncheck.ScanModeExtract: true, 275 } 276 277 func (f *ModeFlag) Get() interface{} { return *f } 278 func (f *ModeFlag) Set(s string) error { 279 if _, ok := supportedModes[s]; !ok { 280 return errFlagParse 281 } 282 *f = ModeFlag(s) 283 return nil 284 } 285 func (f *ModeFlag) String() string { return "" } 286 287 // ScanFlag is used for parsing and validation of 288 // govulncheck -scan flag. 289 type ScanFlag string 290 291 var supportedLevels = map[string]bool{ 292 govulncheck.ScanLevelModule: true, 293 govulncheck.ScanLevelPackage: true, 294 govulncheck.ScanLevelSymbol: true, 295 } 296 297 func (f *ScanFlag) Get() interface{} { return *f } 298 func (f *ScanFlag) Set(s string) error { 299 if _, ok := supportedLevels[s]; !ok { 300 return errFlagParse 301 } 302 *f = ScanFlag(s) 303 return nil 304 } 305 func (f *ScanFlag) String() string { return "" }