report/tui.go   F
last analyzed

Size/Duplication

Total Lines 925
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 176
eloc 612
dl 0
loc 925
rs 1.988
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A report.redrawChangelog 0 6 2
B report.RunTui 0 31 6
A report.debug 0 11 4
A report.pageUpDownJumpCount 0 11 4
B report.onMovingCursorRedrawView 0 15 6
B report.movable 0 26 8
A report.showMsg 0 21 4
B report.previousView 0 19 7
A report.cursorMoveTop 0 7 2
A report.getLine 0 20 5
A report.cursorUp 0 12 5
A report.cursorPageUp 0 13 3
A report.quit 0 2 1
A report.cursorMoveBottom 0 8 2
A report.previousSummary 0 16 5
A report.cursorDown 0 23 5
B report.nextView 0 19 7
A report.layout 0 11 4
A report.setSummaryLayout 0 15 3
A report.delMsg 0 6 2
A report.nextSummary 0 16 5
B report.setDetailLayout 0 26 5
B report.cursorPageDown 0 30 6
A report.redrawDetail 0 6 2
A report.cursorMoveMiddle 0 8 2
B report.setSideLayout 0 21 6
B report.summaryLines 0 51 8
F report.setChangelogLayout 0 138 28
F report.detailLines 0 89 17
B report.keybindings 0 102 3
C report.changeHost 0 34 9
1
/* Vuls - Vulnerability Scanner
2
Copyright (C) 2016  Future Corporation , Japan.
3
4
This program is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
package report
19
20
import (
21
	"bytes"
22
	"fmt"
23
	"os"
24
	"sort"
25
	"strings"
26
	"text/template"
27
	"time"
28
29
	"github.com/future-architect/vuls/alert"
30
	"golang.org/x/xerrors"
31
32
	"github.com/future-architect/vuls/config"
33
	"github.com/future-architect/vuls/models"
34
	"github.com/future-architect/vuls/util"
35
	"github.com/google/subcommands"
36
	"github.com/gosuri/uitable"
37
	"github.com/jroimartin/gocui"
38
)
39
40
var scanResults models.ScanResults
41
var currentScanResult models.ScanResult
42
var vinfos []models.VulnInfo
43
var currentVinfo int
44
var currentDetailLimitY int
45
var currentChangelogLimitY int
46
47
// RunTui execute main logic
48
func RunTui(results models.ScanResults) subcommands.ExitStatus {
49
	scanResults = results
50
	sort.Slice(scanResults, func(i, j int) bool {
51
		if scanResults[i].ServerName == scanResults[j].ServerName {
52
			return scanResults[i].Container.Name < scanResults[j].Container.Name
53
		}
54
		return scanResults[i].ServerName < scanResults[j].ServerName
55
	})
56
57
	g, err := gocui.NewGui(gocui.OutputNormal)
58
	if err != nil {
59
		util.Log.Errorf("%+v", err)
60
		return subcommands.ExitFailure
61
	}
62
	defer g.Close()
63
64
	g.SetManagerFunc(layout)
65
	if err := keybindings(g); err != nil {
66
		util.Log.Errorf("%+v", err)
67
		return subcommands.ExitFailure
68
	}
69
	g.SelBgColor = gocui.ColorGreen
70
	g.SelFgColor = gocui.ColorBlack
71
	g.Cursor = true
72
73
	if err := g.MainLoop(); err != nil {
74
		g.Close()
75
		util.Log.Errorf("%+v", err)
76
		os.Exit(1)
77
	}
78
	return subcommands.ExitSuccess
79
}
80
81
func keybindings(g *gocui.Gui) (err error) {
82
	errs := []error{}
83
84
	// Move beetween views
85
	errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView))
86
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView))
87
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView))
88
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView))
89
	errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
90
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
91
	errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
92
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
93
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
94
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
95
	errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown))
96
	errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
97
	errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
98
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown))
99
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp))
100
	errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView))
101
102
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
103
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg))
104
105
	// summary
106
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView))
107
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView))
108
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView))
109
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView))
110
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView))
111
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView))
112
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
113
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
114
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
115
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
116
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
117
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
118
	errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown))
119
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
120
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
121
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
122
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView))
123
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
124
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
125
126
	// detail
127
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView))
128
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView))
129
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView))
130
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView))
131
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView))
132
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
133
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
134
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
135
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
136
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
137
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
138
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
139
	errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown))
140
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
141
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
142
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
143
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
144
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
145
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
146
147
	// changelog
148
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyTab, gocui.ModNone, nextView))
149
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlQ, gocui.ModNone, previousView))
150
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlH, gocui.ModNone, nextView))
151
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlL, gocui.ModNone, nextView))
152
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModAlt, previousView))
153
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
154
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
155
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
156
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
157
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
158
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
159
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
160
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeySpace, gocui.ModNone, cursorPageDown))
161
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
162
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
163
	//  errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
164
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
165
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
166
	errs = append(errs, g.SetKeybinding("changelog", gocui.KeyEnter, gocui.ModNone, nextView))
167
168
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
169
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
170
171
	//TODO Help Ctrl-h
172
173
	errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
174
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
175
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
176
177
	for _, e := range errs {
178
		if e != nil {
179
			return e
180
		}
181
	}
182
	return nil
183
}
184
185
func nextView(g *gocui.Gui, v *gocui.View) error {
186
	var err error
187
188
	if v == nil {
189
		_, err = g.SetCurrentView("side")
190
	}
191
	switch v.Name() {
192
	case "side":
193
		_, err = g.SetCurrentView("summary")
194
	case "summary":
195
		_, err = g.SetCurrentView("detail")
196
	case "detail":
197
		_, err = g.SetCurrentView("changelog")
198
	case "changelog":
199
		_, err = g.SetCurrentView("side")
200
	default:
201
		_, err = g.SetCurrentView("summary")
202
	}
203
	return err
204
}
205
206
func previousView(g *gocui.Gui, v *gocui.View) error {
207
	var err error
208
209
	if v == nil {
210
		_, err = g.SetCurrentView("side")
211
	}
212
	switch v.Name() {
213
	case "side":
214
		_, err = g.SetCurrentView("side")
215
	case "summary":
216
		_, err = g.SetCurrentView("side")
217
	case "detail":
218
		_, err = g.SetCurrentView("summary")
219
	case "changelog":
220
		_, err = g.SetCurrentView("detail")
221
	default:
222
		_, err = g.SetCurrentView("side")
223
	}
224
	return err
225
}
226
227
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
228
	switch v.Name() {
229
	case "side":
230
		yLimit = len(scanResults) - 1
231
		if yLimit < nextY {
232
			return false, yLimit
233
		}
234
		return true, yLimit
235
	case "summary":
236
		yLimit = len(currentScanResult.ScannedCves) - 1
237
		if yLimit < nextY {
238
			return false, yLimit
239
		}
240
		return true, yLimit
241
	case "detail":
242
		// if currentDetailLimitY < nextY {
243
		// return false, currentDetailLimitY
244
		// }
245
		return true, currentDetailLimitY
246
	case "changelog":
247
		// if currentChangelogLimitY < nextY {
248
		// return false, currentChangelogLimitY
249
		// }
250
		return true, currentChangelogLimitY
251
	default:
252
		return true, 0
253
	}
254
}
255
256
func pageUpDownJumpCount(v *gocui.View) int {
257
	var jump int
258
	switch v.Name() {
259
	case "side", "summary":
260
		jump = 8
261
	case "detail", "changelog":
262
		jump = 30
263
	default:
264
		jump = 8
265
	}
266
	return jump
267
}
268
269
// redraw views
270
func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
271
	switch v.Name() {
272
	case "summary":
273
		if err := redrawDetail(g); err != nil {
274
			return err
275
		}
276
		if err := redrawChangelog(g); err != nil {
277
			return err
278
		}
279
	case "side":
280
		if err := changeHost(g, v); err != nil {
281
			return err
282
		}
283
	}
284
	return nil
285
}
286
287
func cursorDown(g *gocui.Gui, v *gocui.View) error {
288
	if v != nil {
289
		cx, cy := v.Cursor()
290
		ox, oy := v.Origin()
291
		//  ok,  := movable(v, oy+cy+1)
292
		//  _, maxY := v.Size()
293
		ok, _ := movable(v, oy+cy+1)
294
		//  log.Info(cy, oy)
295
		if !ok {
296
			return nil
297
		}
298
		if err := v.SetCursor(cx, cy+1); err != nil {
299
			if err := v.SetOrigin(ox, oy+1); err != nil {
300
				return err
301
			}
302
		}
303
		onMovingCursorRedrawView(g, v)
304
	}
305
306
	cx, cy := v.Cursor()
307
	ox, oy := v.Origin()
308
	debug(g, fmt.Sprintf("%v, %v, %v, %v", cx, cy, ox, oy))
309
	return nil
310
}
311
312
func cursorMoveTop(g *gocui.Gui, v *gocui.View) error {
313
	if v != nil {
314
		cx, _ := v.Cursor()
315
		v.SetCursor(cx, 0)
316
	}
317
	onMovingCursorRedrawView(g, v)
318
	return nil
319
}
320
321
func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error {
322
	if v != nil {
323
		_, maxY := v.Size()
324
		cx, _ := v.Cursor()
325
		v.SetCursor(cx, maxY-1)
326
	}
327
	onMovingCursorRedrawView(g, v)
328
	return nil
329
}
330
331
func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error {
332
	if v != nil {
333
		_, maxY := v.Size()
334
		cx, _ := v.Cursor()
335
		v.SetCursor(cx, maxY/2)
336
	}
337
	onMovingCursorRedrawView(g, v)
338
	return nil
339
}
340
341
func cursorPageDown(g *gocui.Gui, v *gocui.View) error {
342
	jump := pageUpDownJumpCount(v)
343
344
	if v != nil {
345
		cx, cy := v.Cursor()
346
		ox, oy := v.Origin()
347
		ok, yLimit := movable(v, oy+cy+jump)
348
		_, maxY := v.Size()
349
350
		if !ok {
351
			if yLimit < maxY {
352
				v.SetCursor(cx, yLimit)
353
			} else {
354
				v.SetCursor(cx, maxY-1)
355
				v.SetOrigin(ox, yLimit-maxY+1)
356
			}
357
		} else if yLimit < oy+jump+maxY {
358
			if yLimit < maxY {
359
				v.SetCursor(cx, yLimit)
360
			} else {
361
				v.SetOrigin(ox, yLimit-maxY+1)
362
				v.SetCursor(cx, maxY-1)
363
			}
364
		} else {
365
			v.SetCursor(cx, cy)
366
			v.SetOrigin(ox, oy+jump)
367
		}
368
		onMovingCursorRedrawView(g, v)
369
	}
370
	return nil
371
}
372
373
func cursorUp(g *gocui.Gui, v *gocui.View) error {
374
	if v != nil {
375
		ox, oy := v.Origin()
376
		cx, cy := v.Cursor()
377
		if err := v.SetCursor(cx, cy-1); err != nil && 0 < oy {
378
			if err := v.SetOrigin(ox, oy-1); err != nil {
379
				return err
380
			}
381
		}
382
	}
383
	onMovingCursorRedrawView(g, v)
384
	return nil
385
}
386
387
func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
388
	jump := pageUpDownJumpCount(v)
389
	if v != nil {
390
		cx, _ := v.Cursor()
391
		ox, oy := v.Origin()
392
		if err := v.SetOrigin(ox, oy-jump); err != nil {
393
			v.SetOrigin(ox, 0)
394
			v.SetCursor(cx, 0)
395
396
		}
397
		onMovingCursorRedrawView(g, v)
398
	}
399
	return nil
400
}
401
402
func previousSummary(g *gocui.Gui, v *gocui.View) error {
403
	if v != nil {
404
		// cursor to summary
405
		if _, err := g.SetCurrentView("summary"); err != nil {
406
			return err
407
		}
408
		// move next line
409
		if err := cursorUp(g, g.CurrentView()); err != nil {
410
			return err
411
		}
412
		// cursor to detail
413
		if _, err := g.SetCurrentView("detail"); err != nil {
414
			return err
415
		}
416
	}
417
	return nil
418
}
419
420
func nextSummary(g *gocui.Gui, v *gocui.View) error {
421
	if v != nil {
422
		// cursor to summary
423
		if _, err := g.SetCurrentView("summary"); err != nil {
424
			return err
425
		}
426
		// move next line
427
		if err := cursorDown(g, g.CurrentView()); err != nil {
428
			return err
429
		}
430
		// cursor to detail
431
		if _, err := g.SetCurrentView("detail"); err != nil {
432
			return err
433
		}
434
	}
435
	return nil
436
}
437
438
func changeHost(g *gocui.Gui, v *gocui.View) error {
439
440
	if err := g.DeleteView("summary"); err != nil {
441
		return err
442
	}
443
	if err := g.DeleteView("detail"); err != nil {
444
		return err
445
	}
446
	if err := g.DeleteView("changelog"); err != nil {
447
		return err
448
	}
449
450
	_, cy := v.Cursor()
451
	l, err := v.Line(cy)
452
	if err != nil {
453
		return err
454
	}
455
	serverName := strings.TrimSpace(l)
456
457
	for _, r := range scanResults {
458
		if serverName == strings.TrimSpace(r.ServerInfoTui()) {
459
			currentScanResult = r
460
			vinfos = r.ScannedCves.ToSortedSlice()
461
			break
462
		}
463
	}
464
465
	if err := setSummaryLayout(g); err != nil {
466
		return err
467
	}
468
	if err := setDetailLayout(g); err != nil {
469
		return err
470
	}
471
	return setChangelogLayout(g)
472
}
473
474
func redrawDetail(g *gocui.Gui) error {
475
	if err := g.DeleteView("detail"); err != nil {
476
		return err
477
	}
478
479
	return setDetailLayout(g)
480
}
481
482
func redrawChangelog(g *gocui.Gui) error {
483
	if err := g.DeleteView("changelog"); err != nil {
484
		return err
485
	}
486
487
	return setChangelogLayout(g)
488
}
489
490
func getLine(g *gocui.Gui, v *gocui.View) error {
491
	var l string
492
	var err error
493
494
	_, cy := v.Cursor()
495
	if l, err = v.Line(cy); err != nil {
496
		l = ""
497
	}
498
499
	maxX, maxY := g.Size()
500
	if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
501
		if err != gocui.ErrUnknownView {
502
			return err
503
		}
504
		fmt.Fprintln(v, l)
505
		if _, err := g.SetCurrentView("msg"); err != nil {
506
			return err
507
		}
508
	}
509
	return nil
510
}
511
512
func showMsg(g *gocui.Gui, v *gocui.View) error {
513
	jump := 8
514
	_, cy := v.Cursor()
515
	_, oy := v.Origin()
516
	ok, yLimit := movable(v, oy+cy+jump)
517
	//  maxX, maxY := v.Size()
518
	_, maxY := v.Size()
519
520
	l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v",
521
		cy, oy, maxY, yLimit, currentVinfo, ok)
522
	//  if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
523
	if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
524
		if err != gocui.ErrUnknownView {
525
			return err
526
		}
527
		fmt.Fprintln(v, l)
528
		if _, err := g.SetCurrentView("msg"); err != nil {
529
			return err
530
		}
531
	}
532
	return nil
533
}
534
535
func delMsg(g *gocui.Gui, v *gocui.View) error {
536
	if err := g.DeleteView("msg"); err != nil {
537
		return err
538
	}
539
	_, err := g.SetCurrentView("summary")
540
	return err
541
}
542
543
func quit(g *gocui.Gui, v *gocui.View) error {
544
	return gocui.ErrQuit
545
}
546
547
func layout(g *gocui.Gui) error {
548
	if err := setSideLayout(g); err != nil {
549
		return err
550
	}
551
	if err := setSummaryLayout(g); err != nil {
552
		return err
553
	}
554
	if err := setDetailLayout(g); err != nil {
555
		return err
556
	}
557
	return setChangelogLayout(g)
558
}
559
560
func debug(g *gocui.Gui, str string) error {
561
	if config.Conf.Debug {
562
		maxX, maxY := g.Size()
563
		if _, err := g.View("debug"); err != gocui.ErrUnknownView {
564
			g.DeleteView("debug")
565
		}
566
		if v, err := g.SetView("debug", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
567
			fmt.Fprintf(v, str)
0 ignored issues
show
introduced by
can't check non-constant format in call to Fprintf
Loading history...
568
		}
569
	}
570
	return nil
571
}
572
573
func setSideLayout(g *gocui.Gui) error {
574
	_, maxY := g.Size()
575
	if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil {
576
		if err != gocui.ErrUnknownView {
577
			return err
578
		}
579
		v.Highlight = true
580
581
		for _, result := range scanResults {
582
			fmt.Fprintln(v, result.ServerInfoTui())
583
		}
584
		if len(scanResults) == 0 {
585
			return xerrors.New("No scan results")
586
		}
587
		currentScanResult = scanResults[0]
588
		vinfos = scanResults[0].ScannedCves.ToSortedSlice()
589
		if _, err := g.SetCurrentView("side"); err != nil {
590
			return err
591
		}
592
	}
593
	return nil
594
}
595
596
func setSummaryLayout(g *gocui.Gui) error {
597
	maxX, maxY := g.Size()
598
	if v, err := g.SetView("summary", 40, -1, maxX, int(float64(maxY)*0.2)); err != nil {
599
		if err != gocui.ErrUnknownView {
600
			return err
601
		}
602
603
		lines := summaryLines(currentScanResult)
604
		fmt.Fprintf(v, lines)
0 ignored issues
show
introduced by
can't check non-constant format in call to Fprintf
Loading history...
605
606
		v.Highlight = true
607
		v.Editable = false
608
		v.Wrap = false
609
	}
610
	return nil
611
}
612
613
func summaryLines(r models.ScanResult) string {
614
	stable := uitable.New()
615
	stable.MaxColWidth = 1000
616
	stable.Wrap = false
617
618
	if len(r.Errors) != 0 {
619
		return "Error: Scan with --debug to view the details"
620
	}
621
622
	indexFormat := ""
623
	if len(r.ScannedCves) < 10 {
624
		indexFormat = "[%1d]"
625
	} else if len(r.ScannedCves) < 100 {
626
		indexFormat = "[%2d]"
627
	} else {
628
		indexFormat = "[%3d]"
629
	}
630
631
	for i, vinfo := range r.ScannedCves.ToSortedSlice() {
632
		max := vinfo.MaxCvssScore().Value.Score
633
		cvssScore := "|     "
634
		if 0 < max {
635
			cvssScore = fmt.Sprintf("| %4.1f", max)
636
		}
637
638
		pkgNames := vinfo.AffectedPackages.Names()
639
		pkgNames = append(pkgNames, vinfo.CpeURIs...)
640
		pkgNames = append(pkgNames, vinfo.GitHubSecurityAlerts.Names()...)
641
		pkgNames = append(pkgNames, vinfo.WpPackageFixStats.Names()...)
642
643
		alert := "  "
644
		if vinfo.AlertDict.HasAlert() {
645
			alert = "! "
646
		}
647
648
		var cols []string
649
		cols = []string{
650
			fmt.Sprintf(indexFormat, i+1),
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
651
			alert + vinfo.CveID,
652
			cvssScore + " |",
653
			fmt.Sprintf("%1s |", vinfo.AttackVector()),
654
			fmt.Sprintf("%7s |", vinfo.PatchStatus(r.Packages)),
655
			strings.Join(pkgNames, ", "),
656
		}
657
		icols := make([]interface{}, len(cols))
658
		for j := range cols {
659
			icols[j] = cols[j]
660
		}
661
		stable.AddRow(icols...)
662
	}
663
	return fmt.Sprintf("%s", stable)
664
}
665
666
func setDetailLayout(g *gocui.Gui) error {
667
	maxX, maxY := g.Size()
668
669
	summaryView, err := g.View("summary")
670
	if err != nil {
671
		return err
672
	}
673
	_, cy := summaryView.Cursor()
674
	_, oy := summaryView.Origin()
675
	currentVinfo = cy + oy
676
677
	if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil {
678
		if err != gocui.ErrUnknownView {
679
			return err
680
		}
681
		text, err := detailLines()
682
		if err != nil {
683
			return err
684
		}
685
		fmt.Fprint(v, text)
686
		v.Editable = false
687
		v.Wrap = true
688
689
		currentDetailLimitY = len(strings.Split(text, "\n")) - 1
690
	}
691
	return nil
692
}
693
694
func setChangelogLayout(g *gocui.Gui) error {
695
	summaryView, err := g.View("summary")
696
	if err != nil {
697
		return err
698
	}
699
700
	maxX, maxY := g.Size()
701
	if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
702
		if err != gocui.ErrUnknownView {
703
			return err
704
		}
705
		if len(currentScanResult.Errors) != 0 || len(currentScanResult.ScannedCves) == 0 {
706
			return nil
707
		}
708
709
		lines := []string{
710
			"Affected Packages, Processes",
711
			"============================",
712
		}
713
714
		_, cy := summaryView.Cursor()
715
		_, oy := summaryView.Origin()
716
		currentVinfo = cy + oy
717
		vinfo := vinfos[currentVinfo]
718
		vinfo.AffectedPackages.Sort()
719
		for _, affected := range vinfo.AffectedPackages {
720
			// packages detected by OVAL may not be actually installed
721
			if pack, ok := currentScanResult.Packages[affected.Name]; ok {
722
				var line string
723
				if pack.Repository != "" {
724
					line = fmt.Sprintf("* %s (%s)",
725
						pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
726
						pack.Repository)
727
				} else {
728
					line = fmt.Sprintf("* %s",
729
						pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
730
					)
731
				}
732
				lines = append(lines, line)
733
734
				if len(pack.AffectedProcs) != 0 {
735
					for _, p := range pack.AffectedProcs {
736
						lines = append(lines, fmt.Sprintf("  * PID: %s %s", p.PID, p.Name))
737
					}
738
				} else {
739
					// lines = append(lines, fmt.Sprintf("  * No affected process"))
740
				}
741
			}
742
		}
743
		sort.Strings(vinfo.CpeURIs)
744
		for _, uri := range vinfo.CpeURIs {
745
			lines = append(lines, "* "+uri)
746
		}
747
748
		for _, alert := range vinfo.GitHubSecurityAlerts {
749
			lines = append(lines, "* "+alert.PackageName)
750
		}
751
752
		r := currentScanResult
753
		for _, wp := range vinfo.WpPackageFixStats {
754
			if p, ok := r.WordPressPackages.Find(wp.Name); ok {
755
				if p.Type == models.WPCore {
756
					lines = append(lines, fmt.Sprintf("* %s-%s, FixedIn: %s",
757
						wp.Name, p.Version, wp.FixedIn))
758
				} else {
759
					lines = append(lines,
760
						fmt.Sprintf("* %s-%s, Update: %s, FixedIn: %s, %s",
761
							wp.Name, p.Version, p.Update, wp.FixedIn, p.Status))
762
				}
763
			} else {
764
				lines = append(lines, fmt.Sprintf("* %s", wp.Name))
765
			}
766
		}
767
768
		for _, adv := range vinfo.DistroAdvisories {
769
			lines = append(lines, "\n",
770
				"Advisories",
771
				"==========",
772
			)
773
			lines = append(lines, adv.Format())
774
		}
775
776
		if len(vinfo.Exploits) != 0 {
777
			lines = append(lines, "\n",
778
				"Exploit Codes",
779
				"=============",
780
			)
781
			for _, exploit := range vinfo.Exploits {
782
				lines = append(lines, fmt.Sprintf("* [%s](%s)", exploit.Description, exploit.URL))
783
			}
784
		}
785
786
		if len(vinfo.AlertDict.En) > 0 {
787
			lines = append(lines, "\n",
788
				"USCERT Alert",
789
				"=============",
790
			)
791
			for _, alert := range vinfo.AlertDict.En {
792
				lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
793
			}
794
		}
795
796
		if len(vinfo.AlertDict.Ja) > 0 {
797
			lines = append(lines, "\n",
798
				"JPCERT Alert",
799
				"=============",
800
			)
801
			for _, alert := range vinfo.AlertDict.Ja {
802
				if config.Conf.Lang == "ja" {
803
					lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
804
				} else {
805
					lines = append(lines, fmt.Sprintf("* [JPCERT](%s)", alert.URL))
806
				}
807
			}
808
		}
809
810
		if currentScanResult.IsDeepScanMode() {
811
			lines = append(lines, "\n",
812
				"ChangeLogs",
813
				"==========",
814
			)
815
			for _, affected := range vinfo.AffectedPackages {
816
				pack := currentScanResult.Packages[affected.Name]
817
				for _, p := range currentScanResult.Packages {
818
					if pack.Name == p.Name {
819
						lines = append(lines, p.FormatChangelog(), "\n")
820
					}
821
				}
822
			}
823
		}
824
		text := strings.Join(lines, "\n")
825
		fmt.Fprint(v, text)
826
		v.Editable = false
827
		v.Wrap = true
828
829
		currentChangelogLimitY = len(strings.Split(text, "\n")) - 1
830
	}
831
	return nil
832
}
833
834
type dataForTmpl struct {
835
	CveID            string
836
	Cvsses           string
837
	Exploits         []models.Exploit
838
	Summary          string
839
	Mitigation       string
840
	Confidences      models.Confidences
841
	Cwes             []models.CweDictEntry
842
	Alerts           []alert.Alert
843
	Links            []string
844
	References       []models.Reference
845
	Packages         []string
846
	CpeURIs          []string
847
	PublishedDate    time.Time
848
	LastModifiedDate time.Time
849
}
850
851
func detailLines() (string, error) {
852
	r := currentScanResult
853
	if len(r.Errors) != 0 {
854
		return "", nil
855
	}
856
857
	if len(r.ScannedCves) == 0 {
858
		return "No vulnerable packages", nil
859
	}
860
861
	tmpl, err := template.New("detail").Parse(mdTemplate)
862
	if err != nil {
863
		return "", err
864
	}
865
866
	vinfo := vinfos[currentVinfo]
867
	links := []string{}
868
	if strings.HasPrefix(vinfo.CveID, "CVE-") {
869
		links = append(links, vinfo.CveContents.SourceLinks(
870
			config.Conf.Lang, r.Family, vinfo.CveID)[0].Value,
871
			vinfo.Cvss2CalcURL(),
872
			vinfo.Cvss3CalcURL())
873
	}
874
	for _, url := range vinfo.VendorLinks(r.Family) {
875
		links = append(links, url)
876
	}
877
878
	refs := []models.Reference{}
879
	for _, rr := range vinfo.CveContents.References(r.Family) {
880
		for _, ref := range rr.Value {
881
			if ref.Source == "" {
882
				ref.Source = "-"
883
			}
884
			refs = append(refs, ref)
885
		}
886
	}
887
888
	summary := vinfo.Summaries(r.Lang, r.Family)[0]
889
	mitigation := vinfo.Mitigations(r.Family)[0]
890
891
	table := uitable.New()
892
	table.MaxColWidth = maxColWidth
893
	table.Wrap = true
894
	scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(r.Family)...)
895
	var cols []interface{}
896
	for _, score := range scores {
897
		if score.Value.Score == 0 && score.Value.Severity == "" {
898
			continue
899
		}
900
		scoreStr := "-"
901
		if 0 < score.Value.Score {
902
			scoreStr = fmt.Sprintf("%3.1f", score.Value.Score)
903
		}
904
		scoreVec := fmt.Sprintf("%s/%s", scoreStr, score.Value.Vector)
905
		cols = []interface{}{
906
			scoreVec,
907
			score.Value.Severity,
908
			score.Type,
909
		}
910
		table.AddRow(cols...)
911
	}
912
913
	uniqCweIDs := vinfo.CveContents.UniqCweIDs(r.Family)
914
	cwes := []models.CweDictEntry{}
915
	for _, cweID := range uniqCweIDs {
916
		if strings.HasPrefix(cweID.Value, "CWE-") {
917
			if dict, ok := r.CweDict[strings.TrimPrefix(cweID.Value, "CWE-")]; ok {
918
				cwes = append(cwes, dict)
919
			}
920
		}
921
	}
922
923
	data := dataForTmpl{
924
		CveID:       vinfo.CveID,
925
		Cvsses:      fmt.Sprintf("%s\n", table),
926
		Summary:     fmt.Sprintf("%s (%s)", summary.Value, summary.Type),
927
		Mitigation:  fmt.Sprintf("%s (%s)", mitigation.Value, mitigation.Type),
928
		Confidences: vinfo.Confidences,
929
		Cwes:        cwes,
930
		Links:       util.Distinct(links),
931
		References:  refs,
932
	}
933
934
	buf := bytes.NewBuffer(nil) // create empty buffer
935
	if err := tmpl.Execute(buf, data); err != nil {
936
		return "", err
937
	}
938
939
	return string(buf.Bytes()), nil
940
}
941
942
const mdTemplate = `
943
{{.CveID}}
944
================
945
946
CVSS Scores
947
-----------
948
{{.Cvsses }}
949
950
Summary
951
-----------
952
 {{.Summary }}
953
954
Mitigation
955
-----------
956
 {{.Mitigation }}
957
958
Links
959
-----------
960
{{range $link := .Links -}}
961
* {{$link}}
962
{{end}}
963
CWE
964
-----------
965
{{range .Cwes -}}
966
* {{.En.CweID}} [{{.En.Name}}](https://cwe.mitre.org/data/definitions/{{.En.CweID}}.html)
967
{{end}}
968
{{range $name := .CpeURIs -}}
969
* {{$name}}
970
{{end}}
971
Confidence
972
-----------
973
{{range $confidence := .Confidences -}}
974
* {{$confidence.DetectionMethod}}
975
{{end}}
976
References
977
-----------
978
{{range .References -}}
979
* [{{.Source}}]({{.Link}})
980
{{end}}
981
982
`
983