diff --git a/issue.go b/issue.go index b70b8e2877..1668baa5fb 100644 --- a/issue.go +++ b/issue.go @@ -15,9 +15,12 @@ package gosec import ( + "bufio" + "bytes" "encoding/json" "fmt" "go/ast" + "go/token" "os" "strconv" ) @@ -34,6 +37,10 @@ const ( High ) +// SnippetOffset defines the number of lines captured before +// the beginning and after the end of a code snippet +const SnippetOffset = 1 + // Cwe id and url type Cwe struct { ID string @@ -126,41 +133,53 @@ func (c Score) String() string { func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, error) { if n == nil { - return "", fmt.Errorf("Invalid AST node provided") + return "", fmt.Errorf("invalid AST node provided") } - - size := (int)(end - start) // Go bug, os.File.Read should return int64 ... - _, err := file.Seek(start, 0) // #nosec - if err != nil { - return "", fmt.Errorf("move to the beginning of file: %v", err) + var pos int64 + var buf bytes.Buffer + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + pos++ + if pos > end { + break + } else if pos >= start && pos <= end { + code := fmt.Sprintf("%d: %s\n", pos, scanner.Text()) + buf.WriteString(code) + } } + return buf.String(), nil +} - buf := make([]byte, size) - if nread, err := file.Read(buf); err != nil || nread != size { - return "", fmt.Errorf("Unable to read code") +func codeSnippetStartLine(node ast.Node, fobj *token.File) int64 { + s := (int64)(fobj.Line(node.Pos())) + if s-SnippetOffset > 0 { + return s - SnippetOffset } - return string(buf), nil + return s +} + +func codeSnippetEndLine(node ast.Node, fobj *token.File) int64 { + e := (int64)(fobj.Line(node.End())) + return e + SnippetOffset } // NewIssue creates a new Issue func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score, confidence Score) *Issue { - var code string fobj := ctx.FileSet.File(node.Pos()) name := fobj.Name() - start, end := fobj.Line(node.Pos()), fobj.Line(node.End()) line := strconv.Itoa(start) if start != end { line = fmt.Sprintf("%d-%d", start, end) } - col := strconv.Itoa(fobj.Position(node.Pos()).Column) - // #nosec + var code string if file, err := os.Open(fobj.Name()); err == nil { - defer file.Close() - s := (int64)(fobj.Position(node.Pos()).Offset) // Go bug, should be int64 - e := (int64)(fobj.Position(node.End()).Offset) // Go bug, should be int64 + defer file.Close() // #nosec + s := codeSnippetStartLine(node, fobj) + e := codeSnippetEndLine(node, fobj) code, err = codeSnippet(file, s, e, node) if err != nil { code = err.Error() diff --git a/output/formatter.go b/output/formatter.go index a40cea5368..7d7f18f65c 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -15,6 +15,8 @@ package output import ( + "bufio" + "bytes" "encoding/csv" "encoding/json" "encoding/xml" @@ -59,7 +61,7 @@ Golang errors in file: [{{ $filePath }}]: {{end}} {{ range $index, $issue := .Issues }} [{{ highlight $issue.FileLocation $issue.Severity }}] - {{ $issue.RuleID }} (CWE-{{ $issue.Cwe.ID }}): {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }}) - > {{ $issue.Code }} +{{ printCode $issue }} {{ end }} {{ notice "Summary:" }} @@ -286,6 +288,7 @@ func plainTextFuncMap(enableColor bool) plainTemplate.FuncMap { "danger": color.Danger.Render, "notice": color.Notice.Render, "success": color.Success.Render, + "printCode": printCodeSnippet, } } @@ -294,9 +297,10 @@ func plainTextFuncMap(enableColor bool) plainTemplate.FuncMap { "highlight": func(t string, s gosec.Score) string { return t }, - "danger": fmt.Sprint, - "notice": fmt.Sprint, - "success": fmt.Sprint, + "danger": fmt.Sprint, + "notice": fmt.Sprint, + "success": fmt.Sprint, + "printCode": printCodeSnippet, } } @@ -317,3 +321,18 @@ func highlight(t string, s gosec.Score) string { return defaultTheme.Sprint(t) } } + +func printCodeSnippet(issue *gosec.Issue) string { + scanner := bufio.NewScanner(strings.NewReader(issue.Code)) + var buf bytes.Buffer + for scanner.Scan() { + codeLine := scanner.Text() + if strings.HasPrefix(codeLine, issue.Line) { + codeLine = " > " + codeLine + "\n" + } else { + codeLine = " " + codeLine + "\n" + } + buf.WriteString(codeLine) + } + return buf.String() +} diff --git a/output/formatter_test.go b/output/formatter_test.go index 2cce179f21..9ea1e0377c 100644 --- a/output/formatter_test.go +++ b/output/formatter_test.go @@ -21,7 +21,7 @@ func createIssue(ruleID string, cwe gosec.Cwe) gosec.Issue { What: "test", Confidence: gosec.High, Severity: gosec.High, - Code: "testcode", + Code: "1: testcode", Cwe: cwe, } } @@ -264,7 +264,7 @@ var _ = Describe("Formatter", func() { buf := new(bytes.Buffer) err := CreateReport(buf, "csv", false, []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) Expect(err).ShouldNot(HaveOccurred()) - pattern := "/home/src/project/test.go,1,test,HIGH,HIGH,testcode,CWE-%s\n" + pattern := "/home/src/project/test.go,1,test,HIGH,HIGH,1: testcode,CWE-%s\n" expect := fmt.Sprintf(pattern, cwe.ID) Expect(string(buf.String())).To(Equal(expect)) } @@ -278,7 +278,7 @@ var _ = Describe("Formatter", func() { buf := new(bytes.Buffer) err := CreateReport(buf, "xml", false, []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0}, error) Expect(err).ShouldNot(HaveOccurred()) - pattern := "Results:\n\n\n[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)\n > testcode\n\n\nSummary:\n Files: 0\n Lines: 0\n Nosec: 0\n Issues: 0\n\n" + pattern := "Results:\n\n\n[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)\n > 1: testcode\n\n\n\nSummary:\n Files: 0\n Lines: 0\n Nosec: 0\n Issues: 0\n\n" expect := fmt.Sprintf(pattern, rule, cwe.ID) Expect(string(buf.String())).To(Equal(expect)) }