| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package scan |
| |
| import ( |
| "flag" |
| "fmt" |
| "io" |
| "os" |
| "strings" |
| |
| "golang.org/x/tools/go/buildutil" |
| "golang.org/x/vuln/internal/govulncheck" |
| ) |
| |
| type config struct { |
| govulncheck.Config |
| patterns []string |
| mode string |
| db string |
| json bool |
| dir string |
| tags []string |
| test bool |
| show []string |
| env []string |
| } |
| |
| const ( |
| modeBinary = "binary" |
| modeSource = "source" |
| modeConvert = "convert" // only intended for use by gopls |
| modeQuery = "query" // only intended for use by gopls |
| ) |
| |
| func parseFlags(cfg *config, stderr io.Writer, args []string) error { |
| var tagsFlag buildutil.TagsFlag |
| var showFlag showFlag |
| var version bool |
| flags := flag.NewFlagSet("", flag.ContinueOnError) |
| flags.SetOutput(stderr) |
| flags.BoolVar(&cfg.json, "json", false, "output JSON") |
| flags.BoolVar(&cfg.test, "test", false, "analyze test files (only valid for source mode, default false)") |
| flags.StringVar(&cfg.dir, "C", "", "change to `dir` before running govulncheck") |
| flags.StringVar(&cfg.db, "db", "https://vuln.go.dev", "vulnerability database `url`") |
| flags.StringVar(&cfg.mode, "mode", modeSource, "supports source or binary") |
| flags.Var(&tagsFlag, "tags", "comma-separated `list` of build tags") |
| flags.Var(&showFlag, "show", "enable display of additional information specified by the comma separated `list`\nThe only supported value is 'traces'") |
| flags.BoolVar(&version, "version", false, "print the version information") |
| scanLevel := flags.String("scan", "symbol", "set the scanning level desired, one of module, package or symbol") |
| flags.Usage = func() { |
| fmt.Fprint(flags.Output(), `Govulncheck reports known vulnerabilities in dependencies. |
| |
| Usage: |
| |
| govulncheck [flags] [patterns] |
| govulncheck -mode=binary [flags] [binary] |
| |
| `) |
| flags.PrintDefaults() |
| fmt.Fprintf(flags.Output(), "\n%s\n", detailsMessage) |
| } |
| if err := flags.Parse(args); err != nil { |
| if err == flag.ErrHelp { |
| return errHelp |
| } |
| return err |
| } |
| cfg.patterns = flags.Args() |
| cfg.tags = tagsFlag |
| cfg.show = showFlag |
| if version { |
| cfg.show = append(cfg.show, "version") |
| } |
| cfg.ScanLevel = govulncheck.ScanLevel(*scanLevel) |
| if err := validateConfig(cfg); err != nil { |
| fmt.Fprintln(flags.Output(), err) |
| return errUsage |
| } |
| return nil |
| } |
| |
| var supportedModes = map[string]bool{ |
| modeSource: true, |
| modeBinary: true, |
| modeConvert: true, |
| modeQuery: true, |
| } |
| |
| var supportedLevels = map[string]bool{ |
| govulncheck.ScanLevelModule: true, |
| govulncheck.ScanLevelPackage: true, |
| govulncheck.ScanLevelSymbol: true, |
| } |
| |
| func validateConfig(cfg *config) error { |
| if _, ok := supportedModes[cfg.mode]; !ok { |
| return fmt.Errorf("%q is not a valid mode", cfg.mode) |
| } |
| if _, ok := supportedLevels[string(cfg.ScanLevel)]; !ok { |
| return fmt.Errorf("%q is not a valid scan level", cfg.ScanLevel) |
| } |
| switch cfg.mode { |
| case modeSource: |
| if len(cfg.patterns) == 1 && isFile(cfg.patterns[0]) { |
| return fmt.Errorf("%q is a file.\n\n%v", cfg.patterns[0], errNoBinaryFlag) |
| } |
| case modeBinary: |
| if cfg.test { |
| return fmt.Errorf("the -test flag is not supported in binary mode") |
| } |
| if len(cfg.tags) > 0 { |
| return fmt.Errorf("the -tags flag is not supported in binary mode") |
| } |
| if len(cfg.patterns) != 1 { |
| return fmt.Errorf("only 1 binary can be analyzed at a time") |
| } |
| if !isFile(cfg.patterns[0]) { |
| return fmt.Errorf("%q is not a file", cfg.patterns[0]) |
| } |
| case modeConvert: |
| if len(cfg.patterns) != 0 { |
| return fmt.Errorf("patterns are not accepted in convert mode") |
| } |
| if cfg.dir != "" { |
| return fmt.Errorf("the -C flag is not supported in convert mode") |
| } |
| if cfg.test { |
| return fmt.Errorf("the -test flag is not supported in convert mode") |
| } |
| if len(cfg.tags) > 0 { |
| return fmt.Errorf("the -tags flag is not supported in convert mode") |
| } |
| case modeQuery: |
| if cfg.test { |
| return fmt.Errorf("the -test flag is not supported in query mode") |
| } |
| if len(cfg.tags) > 0 { |
| return fmt.Errorf("the -tags flag is not supported in query mode") |
| } |
| if !cfg.json { |
| return fmt.Errorf("the -json flag must be set in query mode") |
| } |
| for _, pattern := range cfg.patterns { |
| // Parse the input here so that we can catch errors before |
| // outputting the Config. |
| if _, _, err := parseModuleQuery(pattern); err != nil { |
| return err |
| } |
| } |
| } |
| if cfg.json && len(cfg.show) > 0 { |
| return fmt.Errorf("the -show flag is not supported for JSON output") |
| } |
| return nil |
| } |
| |
| func isFile(path string) bool { |
| s, err := os.Stat(path) |
| if err != nil { |
| return false |
| } |
| return !s.IsDir() |
| } |
| |
| type showFlag []string |
| |
| func (v *showFlag) Set(s string) error { |
| *v = append(*v, strings.Split(s, ",")...) |
| return nil |
| } |
| |
| func (f *showFlag) Get() interface{} { return *f } |
| func (f *showFlag) String() string { return "<options>" } |