985 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			985 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package report
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"text/template"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"golang.org/x/xerrors"
 | 
						|
 | 
						|
	"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("%+v", err)
 | 
						|
		return subcommands.ExitFailure
 | 
						|
	}
 | 
						|
	defer g.Close()
 | 
						|
 | 
						|
	g.SetManagerFunc(layout)
 | 
						|
	if err := keybindings(g); err != nil {
 | 
						|
		util.Log.Errorf("%+v", 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("%+v", 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 xerrors.New("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)
 | 
						|
		}
 | 
						|
 | 
						|
		pkgNames := vinfo.AffectedPackages.Names()
 | 
						|
		pkgNames = append(pkgNames, vinfo.CpeURIs...)
 | 
						|
		pkgNames = append(pkgNames, vinfo.GitHubSecurityAlerts.Names()...)
 | 
						|
		pkgNames = append(pkgNames, vinfo.WpPackageFixStats.Names()...)
 | 
						|
 | 
						|
		exploits := ""
 | 
						|
		if 0 < len(vinfo.Exploits) {
 | 
						|
			exploits = "POC"
 | 
						|
		}
 | 
						|
 | 
						|
		var cols []string
 | 
						|
		cols = []string{
 | 
						|
			fmt.Sprintf(indexFormat, i+1),
 | 
						|
			vinfo.CveID,
 | 
						|
			cvssScore + " |",
 | 
						|
			fmt.Sprintf("%4s |", vinfo.AttackVector()),
 | 
						|
			fmt.Sprintf("%3s |", exploits),
 | 
						|
			fmt.Sprintf("%6s |", vinfo.AlertDict.FormatSource()),
 | 
						|
			fmt.Sprintf("%7s |", vinfo.PatchStatus(r.Packages)),
 | 
						|
			strings.Join(pkgNames, ", "),
 | 
						|
		}
 | 
						|
		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 Port: %s",
 | 
						|
							p.PID, p.Name, p.ListenPorts))
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					// lines = append(lines, fmt.Sprintf("  * No affected process"))
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		sort.Strings(vinfo.CpeURIs)
 | 
						|
		for _, uri := range vinfo.CpeURIs {
 | 
						|
			lines = append(lines, "* "+uri)
 | 
						|
		}
 | 
						|
 | 
						|
		for _, alert := range vinfo.GitHubSecurityAlerts {
 | 
						|
			lines = append(lines, "* "+alert.PackageName)
 | 
						|
		}
 | 
						|
 | 
						|
		r := currentScanResult
 | 
						|
		// check wordpress fixedin
 | 
						|
		if r.WordPressPackages != nil {
 | 
						|
			for _, wp := range vinfo.WpPackageFixStats {
 | 
						|
				if p, ok := r.WordPressPackages.Find(wp.Name); ok {
 | 
						|
					if p.Type == models.WPCore {
 | 
						|
						lines = append(lines, fmt.Sprintf("* %s-%s, FixedIn: %s",
 | 
						|
							wp.Name, p.Version, wp.FixedIn))
 | 
						|
					} else {
 | 
						|
						lines = append(lines,
 | 
						|
							fmt.Sprintf("* %s-%s, Update: %s, FixedIn: %s, %s",
 | 
						|
								wp.Name, p.Version, p.Update, wp.FixedIn, p.Status))
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					lines = append(lines, fmt.Sprintf("* %s", wp.Name))
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// check library fixedin
 | 
						|
		for _, scanner := range r.LibraryScanners {
 | 
						|
			key := scanner.GetLibraryKey()
 | 
						|
			for _, fixedin := range vinfo.LibraryFixedIns {
 | 
						|
				for _, lib := range scanner.Libs {
 | 
						|
					if fixedin.Key == key && lib.Name == fixedin.Name {
 | 
						|
						lines = append(lines, fmt.Sprintf("* %s-%s, FixedIn: %s",
 | 
						|
							lib.Name, lib.Version, fixedin.FixedIn))
 | 
						|
						continue
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for _, adv := range vinfo.DistroAdvisories {
 | 
						|
			lines = append(lines, "\n",
 | 
						|
				"Advisories",
 | 
						|
				"==========",
 | 
						|
			)
 | 
						|
			lines = append(lines, adv.Format())
 | 
						|
		}
 | 
						|
 | 
						|
		if len(vinfo.Exploits) != 0 {
 | 
						|
			lines = append(lines, "\n",
 | 
						|
				"Exploit Codes",
 | 
						|
				"=============",
 | 
						|
			)
 | 
						|
			for _, exploit := range vinfo.Exploits {
 | 
						|
				lines = append(lines, fmt.Sprintf("* [%s](%s)", exploit.Description, exploit.URL))
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(vinfo.AlertDict.En) > 0 {
 | 
						|
			lines = append(lines, "\n",
 | 
						|
				"USCERT Alert",
 | 
						|
				"=============",
 | 
						|
			)
 | 
						|
			for _, alert := range vinfo.AlertDict.En {
 | 
						|
				lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(vinfo.AlertDict.Ja) > 0 {
 | 
						|
			lines = append(lines, "\n",
 | 
						|
				"JPCERT Alert",
 | 
						|
				"=============",
 | 
						|
			)
 | 
						|
			for _, alert := range vinfo.AlertDict.Ja {
 | 
						|
				if config.Conf.Lang == "ja" {
 | 
						|
					lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
 | 
						|
				} else {
 | 
						|
					lines = append(lines, fmt.Sprintf("* [JPCERT](%s)", alert.URL))
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		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
 | 
						|
	Exploits         []models.Exploit
 | 
						|
	Summary          string
 | 
						|
	Mitigation       string
 | 
						|
	Confidences      models.Confidences
 | 
						|
	Cwes             []models.CweDictEntry
 | 
						|
	Alerts           []models.Alert
 | 
						|
	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{}
 | 
						|
	if strings.HasPrefix(vinfo.CveID, "CVE-") {
 | 
						|
		links = append(links, 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}}
 | 
						|
 | 
						|
`
 |