src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 "" }