From b8cdc32174800d132a17e36aa69d3592d754af4d Mon Sep 17 00:00:00 2001 From: Delon Wong Her Laang Date: Fri, 26 Jan 2018 11:16:49 +0800 Subject: [PATCH 1/9] Working version of xml result format. --- output/formatter.go | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/output/formatter.go b/output/formatter.go index 39955c0..1659fdc 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -17,6 +17,7 @@ package output import ( "encoding/csv" "encoding/json" + "encoding/xml" htmlTemplate "html/template" "io" plainTemplate "text/template" @@ -36,6 +37,9 @@ const ( // ReportCSV set the output format to csv ReportCSV // CSV format + + // ReportXML set the output format to junit xml + ReportXML // JUnit XML format ) var text = `Results: @@ -57,6 +61,30 @@ type reportInfo struct { Stats *gas.Metrics } +type XMLReport struct { + XMLName xml.Name `xml:"testsuites"` + Testsuites []Testsuite `xml:"testsuite"` +} + +type Testsuite struct { + XMLName xml.Name `xml:"testsuite"` + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + Testcases []Testcase `xml:"testcase"` +} + +type Testcase struct { + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + Failure Failure `xml:"failure"` +} + +type Failure struct { + XMLName xml.Name `xml:"failure"` + Message string `xml:"message,attr"` + Text string `xml:",innerxml"` +} + // CreateReport generates a report based for the supplied issues and metrics given // the specified format. The formats currently accepted are: json, csv, html and text. func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.Metrics) error { @@ -70,6 +98,8 @@ func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas. err = reportJSON(w, data) case "csv": err = reportCSV(w, data) + case "xml": + err = reportXML(w, data) case "html": err = reportFromHTMLTemplate(w, html, data) case "text": @@ -112,6 +142,52 @@ func reportCSV(w io.Writer, data *reportInfo) error { return nil } +func reportXML(w io.Writer, data *reportInfo) error { + testsuites := make(map[string][]Testcase) + for _, issue := range data.Issues { + stacktrace, err := json.MarshalIndent(issue, "", "\t") + if err != nil { + panic(err) + } + testcase := Testcase{ + Name: issue.File, + Failure: Failure{ + Message: "Found 1 vulnerability. See stacktrace for details.", + Text: string(stacktrace), + }, + } + if _, ok := testsuites[issue.What]; ok { + testsuites[issue.What] = append(testsuites[issue.What], testcase) + } else { + testsuites[issue.What] = []Testcase{testcase} + } + } + + var xmlReport XMLReport + for what, testcases := range testsuites { + testsuite := Testsuite{ + Name: what, + Tests: len(testcases), + } + for _, testcase := range testcases { + testsuite.Testcases = append(testsuite.Testcases, testcase) + } + xmlReport.Testsuites = append(xmlReport.Testsuites, testsuite) + } + + raw, err := xml.Marshal(xmlReport) + if err != nil { + panic(err) + } + + _, err = w.Write(raw) + if err != nil { + panic(err) + } + + return err +} + func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error { t, e := plainTemplate.New("gas").Parse(reportTemplate) if e != nil { From 7539b3735fe331b36ece2ff80560d5f29d8f5f4f Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 11:49:58 +0800 Subject: [PATCH 2/9] Added xml header format. --- output/formatter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/output/formatter.go b/output/formatter.go index 1659fdc..49fa232 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -180,6 +180,7 @@ func reportXML(w io.Writer, data *reportInfo) error { panic(err) } + raw = append([]byte("\n"), raw...) _, err = w.Write(raw) if err != nil { panic(err) From 2c1a0b87329fb7b17d6e1a7cc038d56b1ef5a766 Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 12:14:35 +0800 Subject: [PATCH 3/9] Refactored code. --- output/formatter.go | 66 ++++------------------------------- output/junit_xml_format.go | 70 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 59 deletions(-) create mode 100644 output/junit_xml_format.go diff --git a/output/formatter.go b/output/formatter.go index 49fa232..c9e4597 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -38,8 +38,8 @@ const ( // ReportCSV set the output format to csv ReportCSV // CSV format - // ReportXML set the output format to junit xml - ReportXML // JUnit XML format + // ReportJUnitXML set the output format to junit xml + ReportJUnitXML // JUnit XML format ) var text = `Results: @@ -61,30 +61,6 @@ type reportInfo struct { Stats *gas.Metrics } -type XMLReport struct { - XMLName xml.Name `xml:"testsuites"` - Testsuites []Testsuite `xml:"testsuite"` -} - -type Testsuite struct { - XMLName xml.Name `xml:"testsuite"` - Name string `xml:"name,attr"` - Tests int `xml:"tests,attr"` - Testcases []Testcase `xml:"testcase"` -} - -type Testcase struct { - XMLName xml.Name `xml:"testcase"` - Name string `xml:"name,attr"` - Failure Failure `xml:"failure"` -} - -type Failure struct { - XMLName xml.Name `xml:"failure"` - Message string `xml:"message,attr"` - Text string `xml:",innerxml"` -} - // CreateReport generates a report based for the supplied issues and metrics given // the specified format. The formats currently accepted are: json, csv, html and text. func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.Metrics) error { @@ -143,44 +119,16 @@ func reportCSV(w io.Writer, data *reportInfo) error { } func reportXML(w io.Writer, data *reportInfo) error { - testsuites := make(map[string][]Testcase) - for _, issue := range data.Issues { - stacktrace, err := json.MarshalIndent(issue, "", "\t") - if err != nil { - panic(err) - } - testcase := Testcase{ - Name: issue.File, - Failure: Failure{ - Message: "Found 1 vulnerability. See stacktrace for details.", - Text: string(stacktrace), - }, - } - if _, ok := testsuites[issue.What]; ok { - testsuites[issue.What] = append(testsuites[issue.What], testcase) - } else { - testsuites[issue.What] = []Testcase{testcase} - } - } + groupedData := groupDataByRules(data) + junitXMLStruct := createJUnitXMLStruct(groupedData) - var xmlReport XMLReport - for what, testcases := range testsuites { - testsuite := Testsuite{ - Name: what, - Tests: len(testcases), - } - for _, testcase := range testcases { - testsuite.Testcases = append(testsuite.Testcases, testcase) - } - xmlReport.Testsuites = append(xmlReport.Testsuites, testsuite) - } - - raw, err := xml.Marshal(xmlReport) + raw, err := xml.Marshal(junitXMLStruct) if err != nil { panic(err) } - raw = append([]byte("\n"), raw...) + xmlHeader := []byte("\n") + raw = append(xmlHeader, raw...) _, err = w.Write(raw) if err != nil { panic(err) diff --git a/output/junit_xml_format.go b/output/junit_xml_format.go new file mode 100644 index 0000000..ca7b323 --- /dev/null +++ b/output/junit_xml_format.go @@ -0,0 +1,70 @@ +package output + +import ( + "encoding/json" + "encoding/xml" + + "github.com/GoASTScanner/gas" +) + +type JUnitXMLReport struct { + XMLName xml.Name `xml:"testsuites"` + Testsuites []Testsuite `xml:"testsuite"` +} + +type Testsuite struct { + XMLName xml.Name `xml:"testsuite"` + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + Testcases []Testcase `xml:"testcase"` +} + +type Testcase struct { + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + Failure Failure `xml:"failure"` +} + +type Failure struct { + XMLName xml.Name `xml:"failure"` + Message string `xml:"message,attr"` + Text string `xml:",innerxml"` +} + +func groupDataByRules(data *reportInfo) map[string][]*gas.Issue { + groupedData := make(map[string][]*gas.Issue) + for _, issue := range data.Issues { + if _, ok := groupedData[issue.What]; ok { + groupedData[issue.What] = append(groupedData[issue.What], issue) + } else { + groupedData[issue.What] = []*gas.Issue{issue} + } + } + return groupedData +} + +func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) JUnitXMLReport { + var xmlReport JUnitXMLReport + for what, issues := range groupedData { + testsuite := Testsuite{ + Name: what, + Tests: len(issues), + } + for _, issue := range issues { + stacktrace, err := json.MarshalIndent(issue, "", "\t") + if err != nil { + panic(err) + } + testcase := Testcase{ + Name: issue.File, + Failure: Failure{ + Message: "Found 1 vulnerability. See stacktrace for details.", + Text: string(stacktrace), + }, + } + testsuite.Testcases = append(testsuite.Testcases, testcase) + } + xmlReport.Testsuites = append(xmlReport.Testsuites, testsuite) + } + return xmlReport +} From 1346bd37ca856bd77a1901a7402e2ee5c0b784f5 Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 12:19:38 +0800 Subject: [PATCH 4/9] Edited README and help text. --- README.md | 2 +- cmd/gas/main.go | 2 +- output/formatter.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9c6c140..cc3b160 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ $ gas -nosec=true ./... ### Output formats -Gas currently supports text, json and csv output formats. By default +Gas currently supports text, json, csv and JUnit XML output formats. By default results will be reported to stdout, but can also be written to an output file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows: diff --git a/cmd/gas/main.go b/cmd/gas/main.go index 68a6277..deef06e 100644 --- a/cmd/gas/main.go +++ b/cmd/gas/main.go @@ -59,7 +59,7 @@ var ( flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") // format output - flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text") + flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, junit-xml, html, or text") // output file flagOutput = flag.String("out", "", "Set output file for results") diff --git a/output/formatter.go b/output/formatter.go index c9e4597..da6eb7e 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -74,8 +74,8 @@ func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas. err = reportJSON(w, data) case "csv": err = reportCSV(w, data) - case "xml": - err = reportXML(w, data) + case "junit-xml": + err = reportJUnitXML(w, data) case "html": err = reportFromHTMLTemplate(w, html, data) case "text": @@ -118,7 +118,7 @@ func reportCSV(w io.Writer, data *reportInfo) error { return nil } -func reportXML(w io.Writer, data *reportInfo) error { +func reportJUnitXML(w io.Writer, data *reportInfo) error { groupedData := groupDataByRules(data) junitXMLStruct := createJUnitXMLStruct(groupedData) From 4059facfb933c48d65d4fb9fb3f1f03615f19447 Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 12:25:54 +0800 Subject: [PATCH 5/9] Pretty print xml result for better viewing. --- output/formatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/output/formatter.go b/output/formatter.go index da6eb7e..572e807 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -122,7 +122,7 @@ func reportJUnitXML(w io.Writer, data *reportInfo) error { groupedData := groupDataByRules(data) junitXMLStruct := createJUnitXMLStruct(groupedData) - raw, err := xml.Marshal(junitXMLStruct) + raw, err := xml.MarshalIndent(junitXMLStruct, "", "\t") if err != nil { panic(err) } From fdc78c0c4739ab2fe02e97c450f971b6ae973f33 Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 12:43:08 +0800 Subject: [PATCH 6/9] Changed failure text from json to plaintext. --- output/junit_xml_format.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/output/junit_xml_format.go b/output/junit_xml_format.go index ca7b323..290be81 100644 --- a/output/junit_xml_format.go +++ b/output/junit_xml_format.go @@ -1,8 +1,8 @@ package output import ( - "encoding/json" "encoding/xml" + "strconv" "github.com/GoASTScanner/gas" ) @@ -51,15 +51,17 @@ func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) JUnitXMLReport { Tests: len(issues), } for _, issue := range issues { - stacktrace, err := json.MarshalIndent(issue, "", "\t") - if err != nil { - panic(err) - } + text := "Results:\n" + text += "[" + issue.File + ":" + issue.Line + "] - " + + issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + + ", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + text += "> " + issue.Code + testcase := Testcase{ Name: issue.File, Failure: Failure{ Message: "Found 1 vulnerability. See stacktrace for details.", - Text: string(stacktrace), + Text: text, }, } testsuite.Testcases = append(testsuite.Testcases, testcase) From 5b91afec367cc4fcc04a7bc81b7bdff859cce4aa Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 14:45:04 +0800 Subject: [PATCH 7/9] Unexport junit xml structs and some further refactoring. --- output/junit_xml_format.go | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/output/junit_xml_format.go b/output/junit_xml_format.go index 290be81..1af54d3 100644 --- a/output/junit_xml_format.go +++ b/output/junit_xml_format.go @@ -7,30 +7,37 @@ import ( "github.com/GoASTScanner/gas" ) -type JUnitXMLReport struct { +type junitXMLReport struct { XMLName xml.Name `xml:"testsuites"` - Testsuites []Testsuite `xml:"testsuite"` + Testsuites []testsuite `xml:"testsuite"` } -type Testsuite struct { +type testsuite struct { XMLName xml.Name `xml:"testsuite"` Name string `xml:"name,attr"` Tests int `xml:"tests,attr"` - Testcases []Testcase `xml:"testcase"` + Testcases []testcase `xml:"testcase"` } -type Testcase struct { +type testcase struct { XMLName xml.Name `xml:"testcase"` Name string `xml:"name,attr"` - Failure Failure `xml:"failure"` + Failure failure `xml:"failure"` } -type Failure struct { +type failure struct { XMLName xml.Name `xml:"failure"` Message string `xml:"message,attr"` Text string `xml:",innerxml"` } +func genereratePlaintext(issue *gas.Issue) string { + return "Results:\n" + + "[" + issue.File + ":" + issue.Line + "] - " + + issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + + ", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + "> " + issue.Code +} + func groupDataByRules(data *reportInfo) map[string][]*gas.Issue { groupedData := make(map[string][]*gas.Issue) for _, issue := range data.Issues { @@ -43,25 +50,19 @@ func groupDataByRules(data *reportInfo) map[string][]*gas.Issue { return groupedData } -func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) JUnitXMLReport { - var xmlReport JUnitXMLReport +func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) junitXMLReport { + var xmlReport junitXMLReport for what, issues := range groupedData { - testsuite := Testsuite{ + testsuite := testsuite{ Name: what, Tests: len(issues), } for _, issue := range issues { - text := "Results:\n" - text += "[" + issue.File + ":" + issue.Line + "] - " + - issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + - ", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" - text += "> " + issue.Code - - testcase := Testcase{ + testcase := testcase{ Name: issue.File, - Failure: Failure{ + Failure: failure{ Message: "Found 1 vulnerability. See stacktrace for details.", - Text: text, + Text: genereratePlaintext(issue), }, } testsuite.Testcases = append(testsuite.Testcases, testcase) From 143df04ede480a1aa78d2f2d55166187d5a2b6ba Mon Sep 17 00:00:00 2001 From: Wong Her Laang Date: Sat, 27 Jan 2018 22:23:07 +0800 Subject: [PATCH 8/9] Fixed typo. --- output/junit_xml_format.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/output/junit_xml_format.go b/output/junit_xml_format.go index 1af54d3..9796079 100644 --- a/output/junit_xml_format.go +++ b/output/junit_xml_format.go @@ -31,7 +31,7 @@ type failure struct { Text string `xml:",innerxml"` } -func genereratePlaintext(issue *gas.Issue) string { +func generatePlaintext(issue *gas.Issue) string { return "Results:\n" + "[" + issue.File + ":" + issue.Line + "] - " + issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + @@ -62,7 +62,7 @@ func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) junitXMLReport { Name: issue.File, Failure: failure{ Message: "Found 1 vulnerability. See stacktrace for details.", - Text: genereratePlaintext(issue), + Text: generatePlaintext(issue), }, } testsuite.Testcases = append(testsuite.Testcases, testcase) From 862295cb7d907671c4c27abda5549c32e0a16cd6 Mon Sep 17 00:00:00 2001 From: Delon Wong Her Laang Date: Tue, 30 Jan 2018 09:54:30 +0800 Subject: [PATCH 9/9] Return err instead of panic. --- output/formatter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/output/formatter.go b/output/formatter.go index 572e807..d829c53 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -124,17 +124,17 @@ func reportJUnitXML(w io.Writer, data *reportInfo) error { raw, err := xml.MarshalIndent(junitXMLStruct, "", "\t") if err != nil { - panic(err) + return err } xmlHeader := []byte("\n") raw = append(xmlHeader, raw...) _, err = w.Write(raw) if err != nil { - panic(err) + return err } - return err + return nil } func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error {