mirror of
https://github.com/ysoftdevs/DependencyCheck.git
synced 2026-03-25 10:32:00 +01:00
implemented CSV reports per #675
This commit is contained in:
@@ -146,7 +146,7 @@ public class Check extends Update {
|
|||||||
private boolean updateOnly = false;
|
private boolean updateOnly = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The report format to be generated (HTML, XML, VULN, ALL). Default is
|
* The report format to be generated (HTML, XML, VULN, CSV, JSON, ALL). Default is
|
||||||
* HTML.
|
* HTML.
|
||||||
*/
|
*/
|
||||||
private String reportFormat = "HTML";
|
private String reportFormat = "HTML";
|
||||||
@@ -1102,7 +1102,7 @@ public class Check extends Update {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enumeration of supported report formats: "ALL", "HTML", "XML", "VULN",
|
* An enumeration of supported report formats: "ALL", "HTML", "XML", "CSV", "JSON", "VULN",
|
||||||
* etc..
|
* etc..
|
||||||
*/
|
*/
|
||||||
public static class ReportFormats extends EnumeratedAttribute {
|
public static class ReportFormats extends EnumeratedAttribute {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public final class CliParser {
|
|||||||
Format.valueOf(format);
|
Format.valueOf(format);
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
final String msg = String.format("An invalid 'format' of '%s' was specified. "
|
final String msg = String.format("An invalid 'format' of '%s' was specified. "
|
||||||
+ "Supported output formats are XML, JSON, HTML, VULN, or ALL", format);
|
+ "Supported output formats are HTML, XML, CSV, JSON, VULN, or ALL", format);
|
||||||
throw new ParseException(msg);
|
throw new ParseException(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package org.owasp.dependencycheck.reporting;
|
|||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.StringEscapeUtils;
|
import org.apache.commons.lang3.StringEscapeUtils;
|
||||||
|
import org.owasp.dependencycheck.dependency.Identifier;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -94,4 +96,71 @@ public class EscapeTool {
|
|||||||
}
|
}
|
||||||
return StringEscapeUtils.escapeJson(text);
|
return StringEscapeUtils.escapeJson(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats text for CSV format. This includes trimming whitespace, replace
|
||||||
|
* line breaks with spaces, and if necessary quotes the text and/or escapes
|
||||||
|
* contained quotes.
|
||||||
|
*
|
||||||
|
* @param text the text to escape and quote
|
||||||
|
* @return the escaped and quoted text
|
||||||
|
*/
|
||||||
|
public String csv(String text) {
|
||||||
|
if (text == null || text.isEmpty()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return StringEscapeUtils.escapeCsv(text.trim().replace("\n", " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a set of Identifiers, filters them to none CPE, and formats them
|
||||||
|
* for display in a CSV.
|
||||||
|
*
|
||||||
|
* @param ids the set of identifiers
|
||||||
|
* @return the formated list of none CPE identifiers
|
||||||
|
*/
|
||||||
|
public String csvIdentifiers(Set<Identifier> ids) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
boolean addComma = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Identifier id : ids) {
|
||||||
|
if (!"cpe".equals(id.getType())) {
|
||||||
|
if (addComma) {
|
||||||
|
sb.append(", ");
|
||||||
|
} else {
|
||||||
|
addComma = true;
|
||||||
|
}
|
||||||
|
sb.append(id.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringEscapeUtils.escapeCsv(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a set of Identifiers, filters them to just CPEs, and formats them
|
||||||
|
* for display in a CSV.
|
||||||
|
*
|
||||||
|
* @param ids the set of identifiers
|
||||||
|
* @return the formated list of CPE identifiers
|
||||||
|
*/
|
||||||
|
public String csvCpe(Set<Identifier> ids) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
boolean addComma = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Identifier id : ids) {
|
||||||
|
if ("cpe".equals(id.getType())) {
|
||||||
|
if (addComma) {
|
||||||
|
sb.append(", ");
|
||||||
|
} else {
|
||||||
|
addComma = true;
|
||||||
|
}
|
||||||
|
sb.append(id.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringEscapeUtils.escapeCsv(sb.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,11 @@ public class ReportGenerator {
|
|||||||
/**
|
/**
|
||||||
* Generate JSON report.
|
* Generate JSON report.
|
||||||
*/
|
*/
|
||||||
JSON
|
JSON,
|
||||||
|
/**
|
||||||
|
* Generate CSV report.
|
||||||
|
*/
|
||||||
|
CSV
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* The Velocity Engine.
|
* The Velocity Engine.
|
||||||
@@ -191,6 +195,9 @@ public class ReportGenerator {
|
|||||||
if (format == Format.JSON || format == Format.ALL) {
|
if (format == Format.JSON || format == Format.ALL) {
|
||||||
generateReport("JsonReport", outputStream);
|
generateReport("JsonReport", outputStream);
|
||||||
}
|
}
|
||||||
|
if (format == Format.CSV || format == Format.ALL) {
|
||||||
|
generateReport("CsvReport", outputStream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,6 +216,9 @@ public class ReportGenerator {
|
|||||||
generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json");
|
generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json");
|
||||||
pretifyJson(outputDir + File.separator + "dependency-check-report.json");
|
pretifyJson(outputDir + File.separator + "dependency-check-report.json");
|
||||||
}
|
}
|
||||||
|
if (format == Format.CSV || format == Format.ALL) {
|
||||||
|
generateReport("CsvReport", outputDir + File.separator + "dependency-check-report.csv");
|
||||||
|
}
|
||||||
if (format == Format.HTML || format == Format.ALL) {
|
if (format == Format.HTML || format == Format.ALL) {
|
||||||
generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html");
|
generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html");
|
||||||
}
|
}
|
||||||
@@ -344,6 +354,13 @@ public class ReportGenerator {
|
|||||||
generateReports(outputDir, Format.JSON);
|
generateReports(outputDir, Format.JSON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ("CSV".equalsIgnoreCase(format)) {
|
||||||
|
if (pathToCheck.endsWith(".csv")) {
|
||||||
|
generateReport("CsvReport", outputDir);
|
||||||
|
} else {
|
||||||
|
generateReports(outputDir, Format.JSON);
|
||||||
|
}
|
||||||
|
}
|
||||||
if ("ALL".equalsIgnoreCase(format)) {
|
if ("ALL".equalsIgnoreCase(format)) {
|
||||||
generateReports(outputDir, Format.ALL);
|
generateReports(outputDir, Format.ALL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#**
|
||||||
|
This file is part of Dependency-Check.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
Copyright (c) 2017 Jeremy Long. All Rights Reserved.
|
||||||
|
|
||||||
|
@author Jeremy Long <jeremy.long@owasp.org>
|
||||||
|
@version 1 *###
|
||||||
|
"Project","ScanDate","DependencyName","DependencyPath","Description","License","Md5","Sha1","Identifiers","CPE","CVE","CWE","Vulnerability","Severity","CVSSv2"
|
||||||
|
#macro(writeSev $score)#if($score<4.0)"Low"#elseif($score>=7.0)"High"#else"Medium"#end#end
|
||||||
|
#foreach($dependency in $dependencies)#if($dependency.getVulnerabilities().size()>0)
|
||||||
|
#foreach($vuln in $dependency.getVulnerabilities())
|
||||||
|
$enc.csv($applicationName),$enc.csv($scanDate),$enc.csv($dependency.DisplayFileName),#if($dependency.FilePath)$enc.csv($dependency.FilePath)#end,#if($dependency.description)$enc.csv($dependency.description)#end,#if($dependency.license)$enc.csv($dependency.license)#end,#if($dependency.Md5sum)$enc.csv($dependency.Md5sum)#end,#if($dependency.Sha1sum)$enc.csv($dependency.Sha1sum)#end,#if($dependency.identifiers)$enc.csvIdentifiers($dependency.identifiers)#end,#if($dependency.identifiers)$enc.csvCpe($dependency.identifiers)#end,#if($vuln.name)$enc.csv($vuln.name)#end,#if($dependency.cwe)$enc.csv($vuln.cwe)#end,#if($vuln.description)$enc.csv($vuln.description)#end,#writeSev($vuln.cvssScore),$vuln.cvssScore
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#end
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of dependency-check-core.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 Jeremy Long. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
package org.owasp.dependencycheck.reporting;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import org.owasp.dependencycheck.dependency.Identifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author jerem
|
||||||
|
*/
|
||||||
|
public class EscapeToolTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of url method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUrl() {
|
||||||
|
String text = null;
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
String expResult = null;
|
||||||
|
String result = instance.url(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "";
|
||||||
|
expResult = "";
|
||||||
|
result = instance.url(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = " ";
|
||||||
|
expResult = "+";
|
||||||
|
result = instance.url(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of html method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testHtml() {
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
String text = null;
|
||||||
|
String expResult = null;
|
||||||
|
String result = instance.html(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "";
|
||||||
|
expResult = "";
|
||||||
|
result = instance.html(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "<div>";
|
||||||
|
expResult = "<div>";
|
||||||
|
result = instance.html(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of xml method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testXml() {
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
String text = null;
|
||||||
|
String expResult = null;
|
||||||
|
String result = instance.xml(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "";
|
||||||
|
expResult = "";
|
||||||
|
result = instance.xml(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "<div>";
|
||||||
|
expResult = "<div>";
|
||||||
|
result = instance.xml(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of json method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testJson() {
|
||||||
|
String text = null;
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
String expResult = null;
|
||||||
|
String result = instance.json(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "";
|
||||||
|
expResult = "";
|
||||||
|
result = instance.json(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "test \"quote\"\"";
|
||||||
|
expResult = "test \\\"quote\\\"\\\"";
|
||||||
|
result = instance.json(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of csv method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCsv() {
|
||||||
|
String text = null;
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
String expResult = null;
|
||||||
|
String result = instance.csv(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "";
|
||||||
|
expResult = "";
|
||||||
|
result = instance.csv(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
text = "one, two";
|
||||||
|
expResult = "\"one, two\"";
|
||||||
|
result = instance.csv(text);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of csvIdentifiers method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCsvIdentifiers() {
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
Set<Identifier> ids = null;
|
||||||
|
String expResult = "";
|
||||||
|
String result = instance.csvIdentifiers(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
expResult = "";
|
||||||
|
result = instance.csvIdentifiers(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
|
||||||
|
expResult = "";
|
||||||
|
result = instance.csvIdentifiers(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
|
||||||
|
expResult = "somegroup:something:1.0";
|
||||||
|
result = instance.csvIdentifiers(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
|
||||||
|
ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
|
||||||
|
expResult = "somegroup:something:1.0";
|
||||||
|
result = instance.csvIdentifiers(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
|
||||||
|
ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
|
||||||
|
ids.add(new Identifier("gav", "somegroup2:something:1.2", ""));
|
||||||
|
expResult = "\"somegroup:something:1.0, somegroup2:something:1.2\"";
|
||||||
|
String expResult2 = "\"somegroup2:something:1.2, somegroup:something:1.0\"";
|
||||||
|
result = instance.csvIdentifiers(ids);
|
||||||
|
assertTrue(expResult.equals(result) || expResult2.equals(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of csvCpe method, of class EscapeTool.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCsvCpe() {
|
||||||
|
EscapeTool instance = new EscapeTool();
|
||||||
|
Set<Identifier> ids = null;
|
||||||
|
String expResult = "";
|
||||||
|
String result = instance.csvCpe(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
expResult = "";
|
||||||
|
result = instance.csvCpe(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
|
||||||
|
expResult = "";
|
||||||
|
result = instance.csvCpe(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
|
||||||
|
expResult = "cpe:/a:somegroup:something:1.0";
|
||||||
|
result = instance.csvCpe(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
|
||||||
|
ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
|
||||||
|
expResult = "cpe:/a:somegroup:something:1.0";
|
||||||
|
result = instance.csvCpe(ids);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
ids = new HashSet<>();
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
|
||||||
|
ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
|
||||||
|
ids.add(new Identifier("cpe", "cpe:/a:somegroup2:something:1.2", ""));
|
||||||
|
expResult = "\"cpe:/a:somegroup:something:1.0, cpe:/a:somegroup2:something:1.2\"";
|
||||||
|
String expResult2 = "\"cpe:/a:somegroup2:something:1.2, cpe:/a:somegroup:something:1.0\"";
|
||||||
|
result = instance.csvCpe(ids);
|
||||||
|
assertTrue(expResult.equals(result) || expResult2.equals(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -757,6 +757,10 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
|
|||||||
return "dependency-check-report.xml#";
|
return "dependency-check-report.xml#";
|
||||||
} else if ("VULN".equalsIgnoreCase(this.format)) {
|
} else if ("VULN".equalsIgnoreCase(this.format)) {
|
||||||
return "dependency-check-vulnerability";
|
return "dependency-check-vulnerability";
|
||||||
|
} else if ("JSON".equalsIgnoreCase(this.format)) {
|
||||||
|
return "dependency-check-report.json";
|
||||||
|
} else if ("CSV".equalsIgnoreCase(this.format)) {
|
||||||
|
return "dependency-check-report.csv";
|
||||||
} else {
|
} else {
|
||||||
getLog().warn("Unknown report format used during site generation.");
|
getLog().warn("Unknown report format used during site generation.");
|
||||||
return "dependency-check-report";
|
return "dependency-check-report";
|
||||||
|
|||||||
Reference in New Issue
Block a user