Output changelog in report, TUI and JSON for Ubuntu/Debian/CentOS

This commit is contained in:
Kota Kanbe
2017-02-23 16:50:28 +09:00
parent c66b0f4db4
commit a86035c0bf
15 changed files with 504 additions and 156 deletions

View File

@@ -47,7 +47,7 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatOneLineText {
timestr := rs[0].ScannedAt.Format(time.RFC3339)
k := fmt.Sprintf(timestr + "/summary.txt")
text := toOneLineSummary(rs...)
text := formatOneLineSummary(rs...)
b := []byte(text)
if err := createBlockBlob(cli, k, b); err != nil {
return err
@@ -69,7 +69,7 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatShortText {
k := key + "_short.txt"
b := []byte(toShortPlainText(r))
b := []byte(formatShortPlainText(r))
if err := createBlockBlob(cli, k, b); err != nil {
return err
}
@@ -77,7 +77,7 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatFullText {
k := key + "_full.txt"
b := []byte(toFullPlainText(r))
b := []byte(formatFullPlainText(r))
if err := createBlockBlob(cli, k, b); err != nil {
return err
}

View File

@@ -40,7 +40,7 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
for _, r := range rs {
if conf.FormatOneEMail {
message += toFullPlainText(r) + "\r\n\r\n"
message += formatFullPlainText(r) + "\r\n\r\n"
totalResult.KnownCves = append(totalResult.KnownCves, r.KnownCves...)
totalResult.UnknownCves = append(totalResult.UnknownCves, r.UnknownCves...)
} else {
@@ -52,7 +52,7 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
subject = fmt.Sprintf("%s%s %s",
conf.EMail.SubjectPrefix, r.ServerInfo(), r.CveSummary())
}
message = toFullPlainText(r)
message = formatFullPlainText(r)
if err := sender.Send(subject, message); err != nil {
return err
}
@@ -68,7 +68,7 @@ One Line Summary
%s`,
toOneLineSummary(rs...), message)
formatOneLineSummary(rs...), message)
subject := fmt.Sprintf("%s %s",
conf.EMail.SubjectPrefix,

View File

@@ -38,7 +38,7 @@ type LocalFileWriter struct {
func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatOneLineText {
path := filepath.Join(w.CurrentDir, "summary.txt")
text := toOneLineSummary(rs...)
text := formatOneLineSummary(rs...)
if err := writeFile(path, []byte(text), 0600); err != nil {
return fmt.Errorf(
"Failed to write to file. path: %s, err: %s",
@@ -63,7 +63,7 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatShortText {
p := path + "_short.txt"
if err := writeFile(
p, []byte(toShortPlainText(r)), 0600); err != nil {
p, []byte(formatShortPlainText(r)), 0600); err != nil {
return fmt.Errorf(
"Failed to write text files. path: %s, err: %s", p, err)
}
@@ -72,7 +72,7 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatFullText {
p := path + "_full.txt"
if err := writeFile(
p, []byte(toFullPlainText(r)), 0600); err != nil {
p, []byte(formatFullPlainText(r)), 0600); err != nil {
return fmt.Errorf(
"Failed to write text files. path: %s, err: %s", p, err)
}

View File

@@ -55,7 +55,7 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatOneLineText {
timestr := rs[0].ScannedAt.Format(time.RFC3339)
k := fmt.Sprintf(timestr + "/summary.txt")
text := toOneLineSummary(rs...)
text := formatOneLineSummary(rs...)
if err := putObject(svc, k, []byte(text)); err != nil {
return err
}
@@ -76,7 +76,7 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatShortText {
k := key + "_short.txt"
text := toShortPlainText(r)
text := formatShortPlainText(r)
if err := putObject(svc, k, []byte(text)); err != nil {
return err
}
@@ -84,7 +84,7 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
if c.Conf.FormatFullText {
k := key + "_full.txt"
text := toFullPlainText(r)
text := formatFullPlainText(r)
if err := putObject(svc, k, []byte(text)); err != nil {
return err
}

View File

@@ -32,7 +32,7 @@ func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
fmt.Printf("\n\n")
fmt.Println("One Line Summary")
fmt.Println("================")
fmt.Printf("%s\n", toScanSummary(rs...))
fmt.Printf("%s\n", formatScanSummary(rs...))
}
func (w StdoutWriter) Write(rs ...models.ScanResult) error {
@@ -40,19 +40,19 @@ func (w StdoutWriter) Write(rs ...models.ScanResult) error {
fmt.Print("\n\n")
fmt.Println("One Line Summary")
fmt.Println("================")
fmt.Println(toOneLineSummary(rs...))
fmt.Println(formatOneLineSummary(rs...))
fmt.Print("\n")
}
if c.Conf.FormatShortText {
for _, r := range rs {
fmt.Println(toShortPlainText(r))
fmt.Println(formatShortPlainText(r))
}
}
if c.Conf.FormatFullText {
for _, r := range rs {
fmt.Println(toFullPlainText(r))
fmt.Println(formatFullPlainText(r))
}
}
return nil

View File

@@ -133,6 +133,27 @@ func keybindings(g *gocui.Gui) (err error) {
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))
@@ -162,6 +183,8 @@ func nextView(g *gocui.Gui, v *gocui.View) error {
case "summary":
_, err = g.SetCurrentView("detail")
case "detail":
_, err = g.SetCurrentView("changelog")
case "changelog":
_, err = g.SetCurrentView("side")
default:
_, err = g.SetCurrentView("summary")
@@ -182,6 +205,8 @@ func previousView(g *gocui.Gui, v *gocui.View) error {
_, err = g.SetCurrentView("side")
case "detail":
_, err = g.SetCurrentView("summary")
case "changelog":
_, err = g.SetCurrentView("detail")
default:
_, err = g.SetCurrentView("side")
}
@@ -207,6 +232,11 @@ func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
return false, currentDetailLimitY
}
return true, currentDetailLimitY
case "changelog":
if currentDetailLimitY < nextY {
return false, currentDetailLimitY
}
return true, currentDetailLimitY
default:
return true, 0
}
@@ -217,7 +247,7 @@ func pageUpDownJumpCount(v *gocui.View) int {
switch v.Name() {
case "side", "summary":
jump = 8
case "detail":
case "detail", "changelog":
jump = 30
default:
jump = 8
@@ -232,6 +262,9 @@ func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
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
@@ -395,6 +428,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
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)
@@ -416,6 +452,9 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
if err := setDetailLayout(g); err != nil {
return err
}
if err := setChangelogLayout(g); err != nil {
return err
}
return nil
}
@@ -430,6 +469,17 @@ func redrawDetail(g *gocui.Gui) error {
return nil
}
func redrawChangelog(g *gocui.Gui) error {
if err := g.DeleteView("changelog"); err != nil {
return err
}
if err := setChangelogLayout(g); err != nil {
return err
}
return nil
}
func getLine(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
@@ -498,12 +548,15 @@ func layout(g *gocui.Gui) error {
if err := setDetailLayout(g); err != nil {
return err
}
if err := setChangelogLayout(g); err != nil {
return err
}
return nil
}
func setSideLayout(g *gocui.Gui) error {
_, maxY := g.Size()
if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil {
if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil {
if err != gocui.ErrUnknownView {
return err
}
@@ -614,14 +667,10 @@ func setDetailLayout(g *gocui.Gui) error {
_, oy := summaryView.Origin()
currentCveInfo = cy + oy
if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil {
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 := report.ToPlainTextDetailsLangEn(
// currentScanResult.KnownCves[currentCveInfo],
// currentScanResult.Family)
text, err := detailLines()
if err != nil {
return err
@@ -635,6 +684,44 @@ func setDetailLayout(g *gocui.Gui) error {
return nil
}
func setChangelogLayout(g *gocui.Gui) error {
maxX, maxY := g.Size()
summaryView, err := g.View("summary")
if err != nil {
return err
}
_, cy := summaryView.Cursor()
_, oy := summaryView.Origin()
currentCveInfo = cy + oy
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.AllCves()) == 0 {
return nil
}
lines := []string{}
cveInfo := currentScanResult.AllCves()[currentCveInfo]
for _, pack := range cveInfo.Packages {
for _, p := range currentScanResult.Packages {
if pack.Name == p.Name {
lines = append(lines, formatOneChangelog(p), "\n")
}
}
}
text := strings.Join(lines, "\n")
fmt.Fprint(v, text)
v.Editable = false
v.Wrap = true
currentDetailLimitY = len(strings.Split(text, "\n")) - 1
}
return nil
}
type dataForTmpl struct {
CveID string
CvssScore string

View File

@@ -29,7 +29,7 @@ import (
const maxColWidth = 80
func toScanSummary(rs ...models.ScanResult) string {
func formatScanSummary(rs ...models.ScanResult) string {
table := uitable.New()
table.MaxColWidth = maxColWidth
table.Wrap = true
@@ -55,7 +55,7 @@ func toScanSummary(rs ...models.ScanResult) string {
return fmt.Sprintf("%s\n", table)
}
func toOneLineSummary(rs ...models.ScanResult) string {
func formatOneLineSummary(rs ...models.ScanResult) string {
table := uitable.New()
table.MaxColWidth = maxColWidth
table.Wrap = true
@@ -79,7 +79,7 @@ func toOneLineSummary(rs ...models.ScanResult) string {
return fmt.Sprintf("%s\n", table)
}
func toShortPlainText(r models.ScanResult) string {
func formatShortPlainText(r models.ScanResult) string {
stable := uitable.New()
stable.MaxColWidth = maxColWidth
stable.Wrap = true
@@ -181,7 +181,7 @@ No CVE-IDs are found in updatable packages.
return fmt.Sprintf("%s\n%s\n", header, stable)
}
func toFullPlainText(r models.ScanResult) string {
func formatFullPlainText(r models.ScanResult) string {
serverInfo := r.ServerInfo()
var buf bytes.Buffer
@@ -210,7 +210,7 @@ No CVE-IDs are found in updatable packages.
}
scoredReport, unscoredReport := []string{}, []string{}
scoredReport, unscoredReport = toPlainTextDetails(r, r.Family)
scoredReport, unscoredReport = formatPlainTextDetails(r, r.Family)
unscored := ""
if !config.Conf.IgnoreUnscoredCves {
@@ -226,41 +226,41 @@ No CVE-IDs are found in updatable packages.
scored,
unscored,
)
return fmt.Sprintf("%s\n%s\n", header, detail)
return fmt.Sprintf("%s\n%s\n%s", header, detail, formatChangelogs(r))
}
func toPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
func formatPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
for _, cve := range r.KnownCves {
switch config.Conf.Lang {
case "en":
if 0 < cve.CveDetail.Nvd.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
} else {
scoredReport = append(
scoredReport, toPlainTextUnknownCve(cve, osFamily))
scoredReport, formatPlainTextUnknownCve(cve, osFamily))
}
case "ja":
if 0 < cve.CveDetail.Jvn.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
scoredReport, formatPlainTextDetailsLangJa(cve, osFamily))
} else if 0 < cve.CveDetail.Nvd.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
} else {
scoredReport = append(
scoredReport, toPlainTextUnknownCve(cve, osFamily))
scoredReport, formatPlainTextUnknownCve(cve, osFamily))
}
}
}
for _, cve := range r.UnknownCves {
unscoredReport = append(
unscoredReport, toPlainTextUnknownCve(cve, osFamily))
unscoredReport, formatPlainTextUnknownCve(cve, osFamily))
}
return
}
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
func formatPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
cveID := cveInfo.CveDetail.CveID
dtable := uitable.New()
dtable.MaxColWidth = maxColWidth
@@ -284,7 +284,7 @@ func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
return fmt.Sprintf("%s", dtable)
}
func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
func formatPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
cveDetail := cveInfo.CveDetail
cveID := cveDetail.CveID
jvn := cveDetail.Jvn
@@ -327,7 +327,7 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
return fmt.Sprintf("%s", dtable)
}
func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
func formatPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
cveDetail := d.CveDetail
cveID := cveDetail.CveID
nvd := cveDetail.Nvd
@@ -467,3 +467,43 @@ func cweURL(cweID string) string {
func cweJvnURL(cweID string) string {
return fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
}
func formatChangelogs(r models.ScanResult) string {
buf := []string{}
for _, p := range r.Packages {
if p.NewVersion == "" {
continue
}
clog := formatOneChangelog(p)
buf = append(buf, clog, "\n\n")
}
return strings.Join(buf, "\n")
}
func formatOneChangelog(p models.PackageInfo) string {
buf := []string{}
if p.NewVersion == "" {
return ""
}
packVer := fmt.Sprintf("%s -> %s",
p.ToStringCurrentVersion(), p.ToStringNewVersion())
var delim bytes.Buffer
for i := 0; i < len(packVer); i++ {
delim.WriteString("-")
}
clog := p.Changelog.Contents
if lines := strings.Split(clog, "\n"); len(lines) != 0 {
clog = strings.Join(lines[0:len(lines)-1], "\n")
}
switch p.Changelog.Method {
case models.FailedToGetChangelog:
clog = "No changelogs"
case models.FailedToFindVersionInChangelog:
clog = "Failed to parse changelogs. For detials, check yourself"
}
buf = append(buf, packVer, delim.String(), clog)
return strings.Join(buf, "\n")
}