/* Vuls - Vulnerability Scanner Copyright (C) 2016 Future Corporation , Japan. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package report import ( "bytes" "fmt" "os" "sort" "strings" "text/template" "time" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" "github.com/google/subcommands" "github.com/gosuri/uitable" "github.com/jroimartin/gocui" ) var scanResults models.ScanResults var currentScanResult models.ScanResult var vinfos []models.VulnInfo var currentVinfo int var currentDetailLimitY int var currentChangelogLimitY int // RunTui execute main logic func RunTui(results models.ScanResults) subcommands.ExitStatus { scanResults = results sort.Slice(scanResults, func(i, j int) bool { if scanResults[i].ServerName == scanResults[j].ServerName { return scanResults[i].Container.Name < scanResults[j].Container.Name } return scanResults[i].ServerName < scanResults[j].ServerName }) g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { util.Log.Errorf("%s", err) return subcommands.ExitFailure } defer g.Close() g.SetManagerFunc(layout) if err := keybindings(g); err != nil { util.Log.Errorf("%s", err) return subcommands.ExitFailure } g.SelBgColor = gocui.ColorGreen g.SelFgColor = gocui.ColorBlack g.Cursor = true if err := g.MainLoop(); err != nil { g.Close() util.Log.Errorf("%s", err) os.Exit(1) } return subcommands.ExitSuccess } func keybindings(g *gocui.Gui) (err error) { errs := []error{} // Move beetween views errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView)) // errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView)) errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg)) // errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg)) // summary errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView)) // errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView)) // errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp)) // errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary)) errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary)) // detail errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView)) // errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp)) // errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary)) errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView)) // changelog errs = append(errs, g.SetKeybinding("changelog", gocui.KeyTab, gocui.ModNone, nextView)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlQ, gocui.ModNone, previousView)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlH, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlL, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModAlt, previousView)) // errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowLeft, gocui.ModAlt, nextView)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowDown, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlJ, gocui.ModNone, cursorDown)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlK, gocui.ModNone, cursorUp)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeySpace, gocui.ModNone, cursorPageDown)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace, gocui.ModNone, cursorPageUp)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp)) // errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlN, gocui.ModNone, nextSummary)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlP, gocui.ModNone, previousSummary)) errs = append(errs, g.SetKeybinding("changelog", gocui.KeyEnter, gocui.ModNone, nextView)) // errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg)) // errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg)) //TODO Help Ctrl-h errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)) // errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine)) // errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg)) for _, e := range errs { if e != nil { return e } } return nil } func nextView(g *gocui.Gui, v *gocui.View) error { var err error if v == nil { _, err = g.SetCurrentView("side") } switch v.Name() { case "side": _, err = g.SetCurrentView("summary") case "summary": _, err = g.SetCurrentView("detail") case "detail": _, err = g.SetCurrentView("changelog") case "changelog": _, err = g.SetCurrentView("side") default: _, err = g.SetCurrentView("summary") } return err } func previousView(g *gocui.Gui, v *gocui.View) error { var err error if v == nil { _, err = g.SetCurrentView("side") } switch v.Name() { case "side": _, err = g.SetCurrentView("side") case "summary": _, err = g.SetCurrentView("side") case "detail": _, err = g.SetCurrentView("summary") case "changelog": _, err = g.SetCurrentView("detail") default: _, err = g.SetCurrentView("side") } return err } func movable(v *gocui.View, nextY int) (ok bool, yLimit int) { switch v.Name() { case "side": yLimit = len(scanResults) - 1 if yLimit < nextY { return false, yLimit } return true, yLimit case "summary": yLimit = len(currentScanResult.ScannedCves) - 1 if yLimit < nextY { return false, yLimit } return true, yLimit case "detail": // if currentDetailLimitY < nextY { // return false, currentDetailLimitY // } return true, currentDetailLimitY case "changelog": // if currentChangelogLimitY < nextY { // return false, currentChangelogLimitY // } return true, currentChangelogLimitY default: return true, 0 } } func pageUpDownJumpCount(v *gocui.View) int { var jump int switch v.Name() { case "side", "summary": jump = 8 case "detail", "changelog": jump = 30 default: jump = 8 } return jump } // redraw views func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error { switch v.Name() { case "summary": if err := redrawDetail(g); err != nil { return err } if err := redrawChangelog(g); err != nil { return err } case "side": if err := changeHost(g, v); err != nil { return err } } return nil } func cursorDown(g *gocui.Gui, v *gocui.View) error { if v != nil { cx, cy := v.Cursor() ox, oy := v.Origin() // ok, := movable(v, oy+cy+1) // _, maxY := v.Size() ok, _ := movable(v, oy+cy+1) // log.Info(cy, oy) if !ok { return nil } if err := v.SetCursor(cx, cy+1); err != nil { if err := v.SetOrigin(ox, oy+1); err != nil { return err } } onMovingCursorRedrawView(g, v) } cx, cy := v.Cursor() ox, oy := v.Origin() debug(g, fmt.Sprintf("%v, %v, %v, %v", cx, cy, ox, oy)) return nil } func cursorMoveTop(g *gocui.Gui, v *gocui.View) error { if v != nil { cx, _ := v.Cursor() v.SetCursor(cx, 0) } onMovingCursorRedrawView(g, v) return nil } func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error { if v != nil { _, maxY := v.Size() cx, _ := v.Cursor() v.SetCursor(cx, maxY-1) } onMovingCursorRedrawView(g, v) return nil } func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error { if v != nil { _, maxY := v.Size() cx, _ := v.Cursor() v.SetCursor(cx, maxY/2) } onMovingCursorRedrawView(g, v) return nil } func cursorPageDown(g *gocui.Gui, v *gocui.View) error { jump := pageUpDownJumpCount(v) if v != nil { cx, cy := v.Cursor() ox, oy := v.Origin() ok, yLimit := movable(v, oy+cy+jump) _, maxY := v.Size() if !ok { if yLimit < maxY { v.SetCursor(cx, yLimit) } else { v.SetCursor(cx, maxY-1) v.SetOrigin(ox, yLimit-maxY+1) } } else if yLimit < oy+jump+maxY { if yLimit < maxY { v.SetCursor(cx, yLimit) } else { v.SetOrigin(ox, yLimit-maxY+1) v.SetCursor(cx, maxY-1) } } else { v.SetCursor(cx, cy) v.SetOrigin(ox, oy+jump) } onMovingCursorRedrawView(g, v) } return nil } func cursorUp(g *gocui.Gui, v *gocui.View) error { if v != nil { ox, oy := v.Origin() cx, cy := v.Cursor() if err := v.SetCursor(cx, cy-1); err != nil && 0 < oy { if err := v.SetOrigin(ox, oy-1); err != nil { return err } } } onMovingCursorRedrawView(g, v) return nil } func cursorPageUp(g *gocui.Gui, v *gocui.View) error { jump := pageUpDownJumpCount(v) if v != nil { cx, _ := v.Cursor() ox, oy := v.Origin() if err := v.SetOrigin(ox, oy-jump); err != nil { v.SetOrigin(ox, 0) v.SetCursor(cx, 0) } onMovingCursorRedrawView(g, v) } return nil } func previousSummary(g *gocui.Gui, v *gocui.View) error { if v != nil { // cursor to summary if _, err := g.SetCurrentView("summary"); err != nil { return err } // move next line if err := cursorUp(g, g.CurrentView()); err != nil { return err } // cursor to detail if _, err := g.SetCurrentView("detail"); err != nil { return err } } return nil } func nextSummary(g *gocui.Gui, v *gocui.View) error { if v != nil { // cursor to summary if _, err := g.SetCurrentView("summary"); err != nil { return err } // move next line if err := cursorDown(g, g.CurrentView()); err != nil { return err } // cursor to detail if _, err := g.SetCurrentView("detail"); err != nil { return err } } return nil } func changeHost(g *gocui.Gui, v *gocui.View) error { if err := g.DeleteView("summary"); err != nil { return err } if err := g.DeleteView("detail"); err != nil { return err } if err := g.DeleteView("changelog"); err != nil { return err } _, cy := v.Cursor() l, err := v.Line(cy) if err != nil { return err } serverName := strings.TrimSpace(l) for _, r := range scanResults { if serverName == strings.TrimSpace(r.ServerInfoTui()) { currentScanResult = r vinfos = r.ScannedCves.ToSortedSlice() break } } if err := setSummaryLayout(g); err != nil { return err } if err := setDetailLayout(g); err != nil { return err } return setChangelogLayout(g) } func redrawDetail(g *gocui.Gui) error { if err := g.DeleteView("detail"); err != nil { return err } return setDetailLayout(g) } func redrawChangelog(g *gocui.Gui) error { if err := g.DeleteView("changelog"); err != nil { return err } return setChangelogLayout(g) } func getLine(g *gocui.Gui, v *gocui.View) error { var l string var err error _, cy := v.Cursor() if l, err = v.Line(cy); err != nil { l = "" } maxX, maxY := g.Size() if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { if err != gocui.ErrUnknownView { return err } fmt.Fprintln(v, l) if _, err := g.SetCurrentView("msg"); err != nil { return err } } return nil } func showMsg(g *gocui.Gui, v *gocui.View) error { jump := 8 _, cy := v.Cursor() _, oy := v.Origin() ok, yLimit := movable(v, oy+cy+jump) // maxX, maxY := v.Size() _, maxY := v.Size() l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentVinfo, ok) // if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil { if err != gocui.ErrUnknownView { return err } fmt.Fprintln(v, l) if _, err := g.SetCurrentView("msg"); err != nil { return err } } return nil } func delMsg(g *gocui.Gui, v *gocui.View) error { if err := g.DeleteView("msg"); err != nil { return err } _, err := g.SetCurrentView("summary") return err } func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } func layout(g *gocui.Gui) error { if err := setSideLayout(g); err != nil { return err } if err := setSummaryLayout(g); err != nil { return err } if err := setDetailLayout(g); err != nil { return err } return setChangelogLayout(g) } func debug(g *gocui.Gui, str string) error { if config.Conf.Debug { maxX, maxY := g.Size() if _, err := g.View("debug"); err != gocui.ErrUnknownView { g.DeleteView("debug") } if v, err := g.SetView("debug", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil { fmt.Fprintf(v, str) } } return nil } func setSideLayout(g *gocui.Gui) error { _, maxY := g.Size() if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil { if err != gocui.ErrUnknownView { return err } v.Highlight = true for _, result := range scanResults { fmt.Fprintln(v, result.ServerInfoTui()) } if len(scanResults) == 0 { return fmt.Errorf("No scan results") } currentScanResult = scanResults[0] vinfos = scanResults[0].ScannedCves.ToSortedSlice() if _, err := g.SetCurrentView("side"); err != nil { return err } } return nil } func setSummaryLayout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("summary", 40, -1, maxX, int(float64(maxY)*0.2)); err != nil { if err != gocui.ErrUnknownView { return err } lines := summaryLines(currentScanResult) fmt.Fprintf(v, lines) v.Highlight = true v.Editable = false v.Wrap = false } return nil } func summaryLines(r models.ScanResult) string { stable := uitable.New() stable.MaxColWidth = 1000 stable.Wrap = false if len(r.Errors) != 0 { return "Error: Scan with --debug to view the details" } indexFormat := "" if len(r.ScannedCves) < 10 { indexFormat = "[%1d]" } else if len(r.ScannedCves) < 100 { indexFormat = "[%2d]" } else { indexFormat = "[%3d]" } for i, vinfo := range r.ScannedCves.ToSortedSlice() { max := vinfo.MaxCvssScore().Value.Score cvssScore := "| " if 0 < max { cvssScore = fmt.Sprintf("| %4.1f", max) } packname := vinfo.AffectedPackages.FormatTuiSummary() packname += strings.Join(vinfo.CpeURIs, ", ") var cols []string cols = []string{ fmt.Sprintf(indexFormat, i+1), vinfo.CveID, cvssScore + " |", fmt.Sprintf("%8s |", vinfo.AttackVector()), fmt.Sprintf("%7s |", vinfo.PatchStatus(r.Packages)), packname, } icols := make([]interface{}, len(cols)) for j := range cols { icols[j] = cols[j] } stable.AddRow(icols...) } return fmt.Sprintf("%s", stable) } func setDetailLayout(g *gocui.Gui) error { maxX, maxY := g.Size() summaryView, err := g.View("summary") if err != nil { return err } _, cy := summaryView.Cursor() _, oy := summaryView.Origin() currentVinfo = cy + oy if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil { if err != gocui.ErrUnknownView { return err } text, err := detailLines() if err != nil { return err } fmt.Fprint(v, text) v.Editable = false v.Wrap = true currentDetailLimitY = len(strings.Split(text, "\n")) - 1 } return nil } func setChangelogLayout(g *gocui.Gui) error { summaryView, err := g.View("summary") if err != nil { return err } maxX, maxY := g.Size() if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil { if err != gocui.ErrUnknownView { return err } if len(currentScanResult.Errors) != 0 || len(currentScanResult.ScannedCves) == 0 { return nil } lines := []string{ "Affected Packages, Processes", "============================", } _, cy := summaryView.Cursor() _, oy := summaryView.Origin() currentVinfo = cy + oy vinfo := vinfos[currentVinfo] vinfo.AffectedPackages.Sort() for _, affected := range vinfo.AffectedPackages { // packages detected by OVAL may not be actually installed if pack, ok := currentScanResult.Packages[affected.Name]; ok { var line string if pack.Repository != "" { line = fmt.Sprintf("* %s (%s)", pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState), pack.Repository) } else { line = fmt.Sprintf("* %s", pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState), ) } lines = append(lines, line) if len(pack.AffectedProcs) != 0 { for _, p := range pack.AffectedProcs { lines = append(lines, fmt.Sprintf(" * PID: %s %s", p.PID, p.Name)) } } else { // lines = append(lines, fmt.Sprintf(" * No affected process")) } } } sort.Strings(vinfo.CpeURIs) for _, uri := range vinfo.CpeURIs { lines = append(lines, "* "+uri) } for _, adv := range vinfo.DistroAdvisories { lines = append(lines, "\n", "Advisories", "==========", ) lines = append(lines, adv.Format()) } if currentScanResult.IsDeepScanMode() { lines = append(lines, "\n", "ChangeLogs", "==========", ) for _, affected := range vinfo.AffectedPackages { pack := currentScanResult.Packages[affected.Name] for _, p := range currentScanResult.Packages { if pack.Name == p.Name { lines = append(lines, p.FormatChangelog(), "\n") } } } } text := strings.Join(lines, "\n") fmt.Fprint(v, text) v.Editable = false v.Wrap = true currentChangelogLimitY = len(strings.Split(text, "\n")) - 1 } return nil } type dataForTmpl struct { CveID string Cvsses string Summary string Mitigation string Confidences models.Confidences Cwes []models.CweDictEntry Links []string References []models.Reference Packages []string CpeURIs []string PublishedDate time.Time LastModifiedDate time.Time } func detailLines() (string, error) { r := currentScanResult if len(r.Errors) != 0 { return "", nil } if len(r.ScannedCves) == 0 { return "No vulnerable packages", nil } tmpl, err := template.New("detail").Parse(mdTemplate) if err != nil { return "", err } vinfo := vinfos[currentVinfo] links := []string{vinfo.CveContents.SourceLinks( config.Conf.Lang, r.Family, vinfo.CveID)[0].Value, vinfo.Cvss2CalcURL(), vinfo.Cvss3CalcURL()} for _, url := range vinfo.VendorLinks(r.Family) { links = append(links, url) } refs := []models.Reference{} for _, rr := range vinfo.CveContents.References(r.Family) { for _, ref := range rr.Value { if ref.Source == "" { ref.Source = "-" } refs = append(refs, ref) } } summary := vinfo.Summaries(r.Lang, r.Family)[0] mitigation := vinfo.Mitigations(r.Family)[0] table := uitable.New() table.MaxColWidth = maxColWidth table.Wrap = true scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(r.Family)...) var cols []interface{} for _, score := range scores { if score.Value.Score == 0 && score.Value.Severity == "" { continue } scoreStr := "-" if 0 < score.Value.Score { scoreStr = fmt.Sprintf("%3.1f", score.Value.Score) } scoreVec := fmt.Sprintf("%s/%s", scoreStr, score.Value.Vector) cols = []interface{}{ scoreVec, score.Value.Severity, score.Type, } table.AddRow(cols...) } uniqCweIDs := vinfo.CveContents.UniqCweIDs(r.Family) cwes := []models.CweDictEntry{} for _, cweID := range uniqCweIDs { if strings.HasPrefix(cweID.Value, "CWE-") { if dict, ok := r.CweDict[strings.TrimPrefix(cweID.Value, "CWE-")]; ok { cwes = append(cwes, dict) } } } data := dataForTmpl{ CveID: vinfo.CveID, Cvsses: fmt.Sprintf("%s\n", table), Summary: fmt.Sprintf("%s (%s)", summary.Value, summary.Type), Mitigation: fmt.Sprintf("%s (%s)", mitigation.Value, mitigation.Type), Confidences: vinfo.Confidences, Cwes: cwes, Links: util.Distinct(links), References: refs, } buf := bytes.NewBuffer(nil) // create empty buffer if err := tmpl.Execute(buf, data); err != nil { return "", err } return string(buf.Bytes()), nil } const mdTemplate = ` {{.CveID}} ================ CVSS Scores ----------- {{.Cvsses }} Summary ----------- {{.Summary }} Mitigation ----------- {{.Mitigation }} Links ----------- {{range $link := .Links -}} * {{$link}} {{end}} CWE ----------- {{range .Cwes -}} * {{.En.CweID}} [{{.En.Name}}](https://cwe.mitre.org/data/definitions/{{.En.CweID}}.html) {{end}} {{range $name := .CpeURIs -}} * {{$name}} {{end}} Confidence ----------- {{range $confidence := .Confidences -}} * {{$confidence.DetectionMethod}} {{end}} References ----------- {{range .References -}} * [{{.Source}}]({{.Link}}) {{end}} `