cmd/rpm2cpe/rpm2cpe.go (156 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "encoding/csv" "flag" "fmt" "io" "os" "path" "strconv" "strings" "github.com/facebookincubator/nvdtools/wfn" "github.com/facebookincubator/nvdtools/rpm" ) var progname = path.Base(os.Args[0]) // custom type to be recognized by flag.Parse() type fieldsToSkip map[int]struct{} // remove elements from fields slice as per config // NB!: this modifies the underlying array of fields slice func (fs fieldsToSkip) skipFields(fields []string) []string { j := 0 for i := 0; i < len(fields); i++ { if _, ok := fs[i]; ok { continue } fields[j] = fields[i] j++ } return fields[:j] } // part of flag.Value interface implementation func (fs fieldsToSkip) String() string { fss := make([]string, 0, len(fs)) for i := range fs { fss = append(fss, fmt.Sprintf("%d", i+1)) } return strings.Join(fss, ",") } // part of flag.Value interface implementation func (fs *fieldsToSkip) Set(val string) error { if *fs == nil { *fs = fieldsToSkip{} } for _, v := range strings.Split(val, ",") { n, err := strconv.Atoi(v) if err != nil { return err } if n < 1 { return fmt.Errorf("illegal field index %d", n) } (*fs)[n-1] = struct{}{} } return nil } type config struct { rpmField int cpeField int inFieldSep string outFieldSep string skip fieldsToSkip defaultNA bool } func (c *config) addFlags() { flag.IntVar(&c.rpmField, "rpm", 0, "position of the field in DSV input that contains the RPM name (starts at 1)") flag.IntVar(&c.cpeField, "cpe", 0, "position of the field in the output to put generated CPE at (starts at 1)") flag.StringVar(&c.inFieldSep, "d", "\t", "input column delimiter") flag.StringVar(&c.outFieldSep, "o", "\t", "output column delimiter") flag.Var(&c.skip, "e", "optional comma-separated list of input fields that should be dropped from output (starts with 1) "+ "rpm name is extracted before dropping fields, CPE is added after that") flag.BoolVar(&c.defaultNA, "na", false, "if set, unknown CPE attributes are set to N/A, otherwise to ANY") } func sayErr(status int, msg string, args ...interface{}) { var statusStr string if status != 0 { statusStr = "fatal" } else { statusStr = "error" } fmt.Fprintf(os.Stderr, "%s: %s: %s\n", progname, statusStr, fmt.Sprintf(msg, args...)) if status != 0 { os.Exit(status) } } func init() { var indent bytes.Buffer for i := 0; i < len(progname); i++ { indent.WriteByte(' ') } flag.Usage = func() { usageStr := "%[1]s takes a delimiter-separated input with one of the fields containing RPM package name\n" + "%[2]s and produces delimiter-separated output consisting of the same fields plus CPE name\n" + "%[2]s parsed from RPM package name.\n" + "usage: %[1]s [flags]\n" + "flags:\n" fmt.Fprintf(os.Stderr, usageStr, progname, indent.String()) flag.PrintDefaults() os.Exit(1) } } // NB!: modifies underlying array of fields slice func processRecord(fields []string, cfg config) ([]string, error) { if cfg.rpmField > len(fields) { return nil, fmt.Errorf("not enough fields (%d)", len(fields)) } var attr *wfn.Attributes if cfg.defaultNA { attr = wfn.NewAttributesWithNA() } else { attr = wfn.NewAttributesWithAny() } attr.Vendor = wfn.Any if err := rpm.ToWFN(attr, fields[cfg.rpmField-1]); err != nil { return nil, fmt.Errorf("couldn't parse RPM name from field %q: %v", fields[cfg.rpmField-1], err) } cpe := attr.BindToURI() fields = cfg.skip.skipFields(fields) if cfg.cpeField > len(fields) { // if cfg.cpeField > len(fields)+1 we ignore that silently and just add CPE as the last field return append(fields, cpe), nil } outFields := make([]string, 0, len(fields)+1) outFields = append(outFields, fields[:cfg.cpeField-1]...) outFields = append(outFields, cpe) outFields = append(outFields, fields[cfg.cpeField-1:]...) return outFields, nil } func rpmname2cpe(in io.Reader, out io.Writer, cfg config) { r := csv.NewReader(in) r.Comma = rune(cfg.inFieldSep[0]) w := csv.NewWriter(out) w.Comma = rune(cfg.outFieldSep[0]) for { inRec, err := r.Read() if err != nil { if err == io.EOF { break } sayErr(-1, "read error: %v", err) } outRec, err := processRecord(inRec, cfg) if err != nil { sayErr(0, "couldn't process record %v: %v", outRec, err) continue } if err = w.Write(outRec); err != nil { sayErr(-1, "write error: %v", err) } } w.Flush() if err := w.Error(); err != nil { sayErr(-1, "write error: %v", err) } } func main() { var cfg config cfg.addFlags() flag.Parse() if cfg.rpmField == 0 || cfg.cpeField == 0 { flag.Usage() } rpmname2cpe(os.Stdin, os.Stdout, cfg) }