diff --git a/.gitignore b/.gitignore index 5fff7a394..b79573ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ # Eclipse project files .classpath .project +.settings +maven-eclipse.xml +.externalToolBuilders # Netbeans configuration nb-configuration.xml /target/ diff --git a/dependency-check-ant/pom.xml b/dependency-check-ant/pom.xml index 19cd62adb..85ba77aee 100644 --- a/dependency-check-ant/pom.xml +++ b/dependency-check-ant/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT dependency-check-ant @@ -68,7 +68,6 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-resources-plugin - 2.6 false @@ -191,10 +190,18 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-jar-plugin + org.apache.maven.plugins maven-shade-plugin - 2.1 + 2.3 @@ -218,29 +225,13 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - - - - **/checkstyle* - - - org.codehaus.mojo cobertura-maven-plugin - 2.6 - + 85 85 @@ -269,7 +260,6 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-surefire-plugin - 2.16 @@ -279,162 +269,139 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - false - 1.6 - 1.6 - - - - - org.apache.maven.plugins - maven-site-plugin - 3.3 - - - org.apache.maven.doxia - doxia-module-markdown - 1.5 - - - - true - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.7 - - - - index - summary - license - help - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - Copyright© 2012-14 Jeremy Long. All Rights Reserved. - - - - default - - javadoc - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.1 - - - - dependency-updates-report - plugin-updates-report - - - - - - org.apache.maven.plugins - maven-jxr-plugin - 2.4 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.16 - - - - report-only - - - - - - org.codehaus.mojo - taglist-maven-plugin - 2.4 - - - - - Todo Work - - - todo - ignoreCase - - - FIXME - exact - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - false - ${basedir}/../src/main/config/checkstyle-checks.xml - ${basedir}/../src/main/config/checkstyle-header.txt - ${basedir}/../src/main/config/checkstyle-suppressions.xml - checkstyle.suppressions.file - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.0.1 - - 1.6 - true - utf-8 - - **/generated/*.java - - - ../src/main/config/dcrules.xml - /rulesets/java/basic.xml - /rulesets/java/imports.xml - /rulesets/java/unusedcode.xml - - - - - org.codehaus.mojo - findbugs-maven-plugin - 2.5.3 - - - - + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.7 + + + + index + summary + license + help + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + Copyright© 2012-15 Jeremy Long. All Rights Reserved. + + + + default + + javadoc + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + + dependency-updates-report + plugin-updates-report + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.4 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.16 + + + + report-only + + + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Todo Work + + + todo + ignoreCase + + + FIXME + exact + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.11 + + false + false + ${basedir}/../src/main/config/checkstyle-checks.xml + ${basedir}/../src/main/config/checkstyle-header.txt + ${basedir}/../src/main/config/checkstyle-suppressions.xml + checkstyle.suppressions.file + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.0.1 + + 1.6 + true + utf-8 + + **/generated/*.java + + + ../src/main/config/dcrules.xml + /rulesets/java/basic.xml + /rulesets/java/imports.xml + /rulesets/java/unusedcode.xml + + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + + org.owasp @@ -456,12 +423,12 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. org.apache.ant ant - 1.9.3 + 1.9.4 org.apache.ant ant-testutil - 1.9.3 + 1.9.4 test diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTask.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTask.java index 47e83bc10..50582e3c0 100644 --- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTask.java +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTask.java @@ -98,8 +98,8 @@ public class DependencyCheckTask extends Task { } /** - * Returns the path. If the path has not been initialized yet, this class is synchronized, and will instantiate the - * path object. + * Returns the path. If the path has not been initialized yet, this class is synchronized, and will instantiate the path + * object. * * @return the path */ @@ -215,9 +215,9 @@ public class DependencyCheckTask extends Task { this.reportOutputDirectory = reportOutputDirectory; } /** - * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 - * which means since the CVSS scores are 0-10, by default the build will never fail and the CVSS score is set to 11. - * The valid range for the fail build on CVSS is 0 to 11, where anything above 10 will not cause the build to fail. + * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which + * means since the CVSS scores are 0-10, by default the build will never fail and the CVSS score is set to 11. The valid range + * for the fail build on CVSS is 0 to 11, where anything above 10 will not cause the build to fail. */ private float failBuildOnCVSS = 11; @@ -239,8 +239,8 @@ public class DependencyCheckTask extends Task { this.failBuildOnCVSS = failBuildOnCVSS; } /** - * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to - * false. Default is true. + * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. Default + * is true. */ private boolean autoUpdate = true; @@ -262,8 +262,8 @@ public class DependencyCheckTask extends Task { this.autoUpdate = autoUpdate; } /** - * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this - * within the Site plugin unless the externalReport is set to true. Default is HTML. + * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the + * Site plugin unless the externalReport is set to true. Default is HTML. */ private String reportFormat = "HTML"; @@ -322,8 +322,7 @@ public class DependencyCheckTask extends Task { * Set the value of proxyServer. * * @param proxyUrl new value of proxyServer - * @deprecated use {@link org.owasp.dependencycheck.taskdefs.DependencyCheckTask#setProxyServer(java.lang.String)} - * instead + * @deprecated use {@link org.owasp.dependencycheck.taskdefs.DependencyCheckTask#setProxyServer(java.lang.String)} instead */ @Deprecated public void setProxyUrl(String proxyUrl) { @@ -559,6 +558,28 @@ public class DependencyCheckTask extends Task { public void setNuspecAnalyzerEnabled(boolean nuspecAnalyzerEnabled) { this.nuspecAnalyzerEnabled = nuspecAnalyzerEnabled; } + /** + * Whether or not the central analyzer is enabled. + */ + private boolean centralAnalyzerEnabled = false; + + /** + * Get the value of centralAnalyzerEnabled. + * + * @return the value of centralAnalyzerEnabled + */ + public boolean isCentralAnalyzerEnabled() { + return centralAnalyzerEnabled; + } + + /** + * Set the value of centralAnalyzerEnabled. + * + * @param centralAnalyzerEnabled new value of centralAnalyzerEnabled + */ + public void setCentralAnalyzerEnabled(boolean centralAnalyzerEnabled) { + this.centralAnalyzerEnabled = centralAnalyzerEnabled; + } /** * Whether or not the nexus analyzer is enabled. @@ -742,8 +763,8 @@ public class DependencyCheckTask extends Task { } /** - * Additional ZIP File extensions to add analyze. This should be a comma-separated list of file extensions to treat - * like ZIP files. + * Additional ZIP File extensions to add analyze. This should be a comma-separated list of file extensions to treat like ZIP + * files. */ private String zipExtensions; @@ -958,8 +979,8 @@ public class DependencyCheckTask extends Task { } /** - * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system - * properties required to change the proxy server, port, and connection timeout. + * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties + * required to change the proxy server, port, and connection timeout. */ private void populateSettings() { Settings.initialize(); @@ -1015,6 +1036,8 @@ public class DependencyCheckTask extends Task { Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, jarAnalyzerEnabled); //NUSPEC ANALYZER Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, nuspecAnalyzerEnabled); + //CENTRAL ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled); //NEXUS ANALYZER Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled); if (nexusUrl != null && !nexusUrl.isEmpty()) { diff --git a/dependency-check-ant/src/site/markdown/configuration.md b/dependency-check-ant/src/site/markdown/configuration.md index d09ea5334..d67507378 100644 --- a/dependency-check-ant/src/site/markdown/configuration.md +++ b/dependency-check-ant/src/site/markdown/configuration.md @@ -46,17 +46,18 @@ Note, that specific analyzers will automatically disable themselves if no file types that they support are detected - so specifically disabling them may not be needed. -Property | Description | Default Value -------------------------|------------------------------------|------------------ -archiveAnalyzerEnabled | Sets whether the Archive Analyzer will be used. | true +Property | Description | Default Value +------------------------|---------------------------------------------------------------------------|------------------ +archiveAnalyzerEnabled | Sets whether the Archive Analyzer will be used. | true zipExtensions | A comma-separated list of additional file extensions to be treated like a ZIP file, the contents will be extracted and analyzed. |   -jarAnalyzer | Sets whether Jar Analyzer will be used. | true -nexusAnalyzerEnabled | Sets whether Nexus Analyzer will be used. | true -nexusUrl | Defines the Nexus URL. | https://repository.sonatype.org/service/local/ +jarAnalyzer | Sets whether the Jar Analyzer will be used. | true +centralAnalyzerEnabled | Sets whether the Central Analyzer will be used. **Disabling this analyzer is not recommended as it could lead to false negatives (e.g. libraries that have vulnerabilities may not be reported correctly).** If this analyzer is being disabled there is a good chance you also want to disable the Nexus Analyzer (see below). | true +nexusAnalyzerEnabled | Sets whether Nexus Analyzer will be used. This analyzer is superceded by the Central Analyzer; however, you can configure this to run against a Nexus Pro installation. | true +nexusUrl | Defines the Nexus Pro URL. If not set the Nexus Analyzer will be disabled. |   nexusUsesProxy | Whether or not the defined proxy should be used when connecting to Nexus. | true -nuspecAnalyzerEnabled | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | true -assemblyAnalyzerEnabled | Sets whether or not the .NET Assembly Analyzer should be used. | true -pathToMono | The path to Mono for .NET assembly analysis on non-windows systems |   +nuspecAnalyzerEnabled | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | true +assemblyAnalyzerEnabled | Sets whether or not the .NET Assembly Analyzer should be used. | true +pathToMono | The path to Mono for .NET assembly analysis on non-windows systems. |   Advanced Configuration ==================== diff --git a/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java b/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java index f13d93df2..758f4a549 100644 --- a/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java +++ b/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java @@ -30,6 +30,10 @@ import org.owasp.dependencycheck.utils.Settings; * @author Jeremy Long */ public class DependencyCheckTaskTest extends BuildFileTest { + //TODO: The use of deprecated class BuildFileTestcan possibly + //be replaced with BuildFileRule. However, it currently isn't included in the ant-testutil jar. + //This should be fixed in ant-testutil 1.9.5, so we can check back once that has been released. + //Reference: http://mail-archives.apache.org/mod_mbox/ant-user/201406.mbox/%3C000001cf87ba$8949b690$9bdd23b0$@de%3E @Before @Override diff --git a/dependency-check-cli/pom.xml b/dependency-check-cli/pom.xml index c89fbe155..99638765d 100644 --- a/dependency-check-cli/pom.xml +++ b/dependency-check-cli/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT dependency-check-cli @@ -60,27 +60,21 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-jar-plugin - 2.4 org.owasp.dependencycheck.App - true - - **/checkstyle* - org.codehaus.mojo cobertura-maven-plugin - 2.6 - + 85 85 @@ -114,7 +108,6 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-surefire-plugin - 2.16 @@ -133,165 +126,15 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-compiler-plugin - 3.1 - - false - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-site-plugin - 3.3 - - - org.apache.maven.doxia - doxia-module-markdown - 1.5 - - - - true - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.7 - - - - index - summary - license - help - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - Copyright© 2012-14 Jeremy Long. All Rights Reserved. - - - - default - - javadoc - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.1 - - - - dependency-updates-report - plugin-updates-report - - - - - - org.apache.maven.plugins - maven-jxr-plugin - 2.4 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.16 - - - - report-only - - - - - - org.codehaus.mojo - taglist-maven-plugin - 2.4 - - - - - Todo Work - - - todo - ignoreCase - - - FIXME - exact - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - false - ${basedir}/../src/main/config/checkstyle-checks.xml - ${basedir}/../src/main/config/checkstyle-header.txt - ${basedir}/../src/main/config/checkstyle-suppressions.xml - checkstyle.suppressions.file - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.1 - - 1.6 - true - utf-8 - - **/generated/*.java - - - ../src/main/config/dcrules.xml - /rulesets/java/basic.xml - /rulesets/java/imports.xml - /rulesets/java/unusedcode.xml - - - - - org.codehaus.mojo - findbugs-maven-plugin - 2.5.3 - - - org.codehaus.mojo appassembler-maven-plugin - 1.7 org.owasp.dependencycheck.App - dependency-check + dependency-check ${project.build.directory}/release @@ -332,6 +175,137 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved. + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.7 + + + + index + summary + license + help + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + Copyright© 2012-15 Jeremy Long. All Rights Reserved. + + + + default + + javadoc + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + + dependency-updates-report + plugin-updates-report + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.4 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.16 + + + + report-only + + + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Todo Work + + + todo + ignoreCase + + + FIXME + exact + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.11 + + false + false + ${basedir}/../src/main/config/checkstyle-checks.xml + ${basedir}/../src/main/config/checkstyle-header.txt + ${basedir}/../src/main/config/checkstyle-suppressions.xml + checkstyle.suppressions.file + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.1 + + 1.6 + true + utf-8 + + **/generated/*.java + + + ../src/main/config/dcrules.xml + /rulesets/java/basic.xml + /rulesets/java/imports.xml + /rulesets/java/unusedcode.xml + + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + + commons-cli diff --git a/dependency-check-cli/src/main/assembly/release.xml b/dependency-check-cli/src/main/assembly/release.xml index 7ebf60d62..a2dd05d87 100644 --- a/dependency-check-cli/src/main/assembly/release.xml +++ b/dependency-check-cli/src/main/assembly/release.xml @@ -2,10 +2,8 @@ release diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java index 25748e970..6ba1da344 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java @@ -21,15 +21,19 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.cli.ParseException; -import org.owasp.dependencycheck.cli.CliParser; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.org.apache.tools.ant.DirectoryScanner; import org.owasp.dependencycheck.reporting.ReportGenerator; import org.owasp.dependencycheck.utils.LogUtils; import org.owasp.dependencycheck.utils.Settings; @@ -93,7 +97,11 @@ public class App { cli.printVersionInfo(); } else if (cli.isRunScan()) { populateSettings(cli); - runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getApplicationName(), cli.getScanFiles()); + try { + runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getApplicationName(), cli.getScanFiles(), cli.getExcludeList()); + } catch (InvalidScanPathException ex) { + Logger.getLogger(App.class.getName()).log(Level.SEVERE, "An invalid scan path was detected; unable to scan '//*' paths"); + } } else { cli.printHelp(); } @@ -106,18 +114,71 @@ public class App { * @param outputFormat the output format of the report * @param applicationName the application name for the report * @param files the files/directories to scan + * @param excludes the patterns for files/directories to exclude + * + * @throws InvalidScanPathException thrown if the path to scan starts with "//" */ - private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files) { - Engine scanner = null; + private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files, + String[] excludes) throws InvalidScanPathException { + Engine engine = null; try { - scanner = new Engine(); - - for (String file : files) { - scanner.scan(file); + engine = new Engine(); + List antStylePaths = new ArrayList(); + if (excludes == null || excludes.length == 0) { + for (String file : files) { + if (file.contains("*") || file.contains("?")) { + antStylePaths.add(file); + } else { + engine.scan(file); + } + } + } else { + antStylePaths = Arrays.asList(files); } - scanner.analyzeDependencies(); - final List dependencies = scanner.getDependencies(); + final Set paths = new HashSet(); + for (String file : antStylePaths) { + final DirectoryScanner scanner = new DirectoryScanner(); + String include = file.replace('\\', '/'); + File baseDir; + + if (include.startsWith("//")) { + throw new InvalidScanPathException("Unable to scan paths specified by //"); + } else if (include.startsWith("./")) { + baseDir = new File("."); + include = include.substring(2); + } else if (include.startsWith("/")) { + baseDir = new File("/"); + include = include.substring(1); + } else if (include.contains("/")) { + final int pos = include.indexOf('/'); + final String tmp = include.substring(0, pos); + if (tmp.contains("*") || tmp.contains("?")) { + baseDir = new File("."); + } else { + baseDir = new File(tmp); + include = include.substring(pos + 1); + } + } else { //no path info - must just be a file in the working directory + baseDir = new File("."); + } + scanner.setBasedir(baseDir); + scanner.setIncludes(include); + if (excludes != null && excludes.length > 0) { + scanner.addExcludes(excludes); + } + scanner.scan(); + if (scanner.getIncludedFilesCount() > 0) { + for (String s : scanner.getIncludedFiles()) { + final File f = new File(baseDir, s); + paths.add(f); + } + } + } + engine.scan(paths); + + engine.analyzeDependencies(); + final List dependencies = engine.getDependencies(); DatabaseProperties prop = null; CveDB cve = null; try { @@ -131,7 +192,7 @@ public class App { cve.close(); } } - final ReportGenerator report = new ReportGenerator(applicationName, dependencies, scanner.getAnalyzers(), prop); + final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop); try { report.generateReports(reportDirectory, outputFormat); } catch (IOException ex) { @@ -145,8 +206,8 @@ public class App { LOGGER.log(Level.SEVERE, "Unable to connect to the dependency-check database; analysis has stopped"); LOGGER.log(Level.FINE, "", ex); } finally { - if (scanner != null) { - scanner.cleanup(); + if (engine != null) { + engine.cleanup(); } } } @@ -172,6 +233,7 @@ public class App { final boolean archiveDisabled = cli.isArchiveDisabled(); final boolean assemblyDisabled = cli.isAssemblyDisabled(); final boolean nuspecDisabled = cli.isNuspecDisabled(); + final boolean centralDisabled = cli.isCentralDisabled(); final boolean nexusDisabled = cli.isNexusDisabled(); final String nexusUrl = cli.getNexusUrl(); final String databaseDriverName = cli.getDatabaseDriverName(); @@ -237,6 +299,7 @@ public class App { Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, !nuspecDisabled); Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, !assemblyDisabled); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !centralDisabled); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !nexusDisabled); if (nexusUrl != null && !nexusUrl.isEmpty()) { Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl); diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/cli/CliParser.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java similarity index 87% rename from dependency-check-cli/src/main/java/org/owasp/dependencycheck/cli/CliParser.java rename to dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java index b3069ab6d..77e26dfe6 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/cli/CliParser.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -15,7 +15,7 @@ * * Copyright (c) 2012 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.cli; +package org.owasp.dependencycheck; import java.io.File; import java.io.FileNotFoundException; @@ -84,8 +84,8 @@ public final class CliParser { /** * Validates that the command line arguments are valid. * - * @throws FileNotFoundException if there is a file specified by either the SCAN or CPE command line arguments that - * does not exist. + * @throws FileNotFoundException if there is a file specified by either the SCAN or CPE command line arguments that does not + * exist. * @throws ParseException is thrown if there is an exception parsing the command line. */ private void validateArgs() throws FileNotFoundException, ParseException { @@ -112,8 +112,8 @@ public final class CliParser { } /** - * Validates whether or not the path(s) points at a file that exists; if the path(s) does not point to an existing - * file a FileNotFoundException is thrown. + * Validates whether or not the path(s) points at a file that exists; if the path(s) does not point to an existing file a + * FileNotFoundException is thrown. * * @param paths the paths to validate if they exists * @param optType the option being validated (e.g. scan, out, etc.) @@ -134,14 +134,36 @@ public final class CliParser { * @throws FileNotFoundException is thrown if the path being validated does not exist. */ private void validatePathExists(String path, String argumentName) throws FileNotFoundException { - if (!path.contains("*.")) { - final File f = new File(path); - if (!f.exists()) { - isValid = false; - final String msg = String.format("Invalid '%s' argument: '%s'", argumentName, path); - throw new FileNotFoundException(msg); + if (path == null) { + isValid = false; + final String msg = String.format("Invalid '%s' argument: null", argumentName); + throw new FileNotFoundException(msg); + } else if (!path.contains("*") && !path.contains("?")) { + File f = new File(path); + if ("o".equalsIgnoreCase(argumentName.substring(0, 1)) && !"ALL".equalsIgnoreCase(this.getReportFormat())) { + final String checkPath = path.toLowerCase(); + if (checkPath.endsWith(".html") || checkPath.endsWith(".xml") || checkPath.endsWith(".htm")) { + if (f.getParentFile() == null) { + f = new File(".", path); + } + if (!f.getParentFile().isDirectory()) { + isValid = false; + final String msg = String.format("Invalid '%s' argument: '%s'", argumentName, path); + throw new FileNotFoundException(msg); + } + } + } else { + if (!f.exists()) { + isValid = false; + final String msg = String.format("Invalid '%s' argument: '%s'", argumentName, path); + throw new FileNotFoundException(msg); + } } - } // else { // TODO add a validation for *.zip extensions rather then relying on the engine to validate it. + } else if (path.startsWith("//") || path.startsWith("\\\\")) { + isValid = false; + final String msg = String.format("Invalid '%s' argument: '%s'%nUnable to scan paths that start with '//'.", argumentName, path); + throw new FileNotFoundException(msg); + } } /** @@ -151,7 +173,6 @@ public final class CliParser { */ @SuppressWarnings("static-access") private Options createCommandLineOptions() { - final Options options = new Options(); addStandardOptions(options); addAdvancedOptions(options); @@ -184,16 +205,22 @@ public final class CliParser { .create(ARGUMENT.APP_NAME_SHORT); final Option path = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.SCAN) - .withDescription("The path to scan - this option can be specified multiple times. To limit the scan" - + " to specific file types *.[ext] can be added to the end of the path.") + .withDescription("The path to scan - this option can be specified multiple times. Ant style" + + " paths are supported (e.g. path/**/*.jar).") .create(ARGUMENT.SCAN_SHORT); + final Option excludes = OptionBuilder.withArgName("pattern").hasArg().withLongOpt(ARGUMENT.EXCLUDE) + .withDescription("Specify and exclusion pattern. This option can be specified multiple times" + + " and it accepts Ant style excludsions.") + .create(); + final Option props = OptionBuilder.withArgName("file").hasArg().withLongOpt(ARGUMENT.PROP) .withDescription("A property file to load.") .create(ARGUMENT.PROP_SHORT); - final Option out = OptionBuilder.withArgName("folder").hasArg().withLongOpt(ARGUMENT.OUT) - .withDescription("The folder to write reports to. This defaults to the current directory.") + final Option out = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.OUT) + .withDescription("The folder to write reports to. This defaults to the current directory. " + + "It is possible to set this to a specific file name if the format argument is not set to ALL.") .create(ARGUMENT.OUT_SHORT); final Option outputFormat = OptionBuilder.withArgName("format").hasArg().withLongOpt(ARGUMENT.OUTPUT_FORMAT) @@ -212,7 +239,11 @@ public final class CliParser { final OptionGroup og = new OptionGroup(); og.addOption(path); + final OptionGroup exog = new OptionGroup(); + exog.addOption(excludes); + options.addOptionGroup(og) + .addOptionGroup(exog) .addOption(out) .addOption(outputFormat) .addOption(appName) @@ -226,8 +257,8 @@ public final class CliParser { } /** - * Adds the advanced command line options to the given options collection. These are split out for purposes of being - * able to display two different help messages. + * Adds the advanced command line options to the given options collection. These are split out for purposes of being able to + * display two different help messages. * * @param options a collection of command line arguments * @throws IllegalArgumentException thrown if there is an exception @@ -292,12 +323,17 @@ public final class CliParser { .withDescription("Disable the .NET Assembly Analyzer.") .create(); + final Option disableCentralAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_CENTRAL) + .withDescription("Disable the Central Analyzer. If this analyzer is disabled it is likely you also want to disable " + + "the Nexus Analyzer.") + .create(); + final Option disableNexusAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_NEXUS) .withDescription("Disable the Nexus Analyzer.") .create(); final Option nexusUrl = OptionBuilder.withArgName("url").hasArg().withLongOpt(ARGUMENT.NEXUS_URL) - .withDescription("The url to the Nexus Server.") + .withDescription("The url to the Nexus Pro Server. If not set the Nexus Analyzer will be disabled.") .create(); final Option nexusUsesProxy = OptionBuilder.withArgName("true/false").hasArg().withLongOpt(ARGUMENT.NEXUS_USES_PROXY) @@ -329,6 +365,7 @@ public final class CliParser { .addOption(disableArchiveAnalyzer) .addOption(disableAssemblyAnalyzer) .addOption(disableNuspecAnalyzer) + .addOption(disableCentralAnalyzer) .addOption(disableNexusAnalyzer) .addOption(nexusUrl) .addOption(nexusUsesProxy) @@ -337,8 +374,8 @@ public final class CliParser { } /** - * Adds the deprecated command line options to the given options collection. These are split out for purposes of not - * including them in the help message. We need to add the deprecated options so as not to break existing scripts. + * Adds the deprecated command line options to the given options collection. These are split out for purposes of not including + * them in the help message. We need to add the deprecated options so as not to break existing scripts. * * @param options a collection of command line arguments * @throws IllegalArgumentException thrown if there is an exception @@ -425,6 +462,15 @@ public final class CliParser { return (line != null) && line.hasOption(ARGUMENT.DISABLE_NEXUS); } + /** + * Returns true if the disableCentral command line argument was specified. + * + * @return true if the disableCentral command line argument was specified; otherwise false + */ + public boolean isCentralDisabled() { + return (line != null) && line.hasOption(ARGUMENT.DISABLE_CENTRAL); + } + /** * Returns the url to the nexus server if one was specified. * @@ -439,8 +485,7 @@ public final class CliParser { } /** - * Returns true if the Nexus Analyzer should use the configured proxy to connect to Nexus; otherwise false is - * returned. + * Returns true if the Nexus Analyzer should use the configured proxy to connect to Nexus; otherwise false is returned. * * @return true if the Nexus Analyzer should use the configured proxy to connect to Nexus; otherwise false */ @@ -479,7 +524,6 @@ public final class CliParser { options, "", true); - } /** @@ -491,6 +535,15 @@ public final class CliParser { return line.getOptionValues(ARGUMENT.SCAN); } + /** + * Retrieves the list of excluded file patterns specified by the 'exclude' argument. + * + * @return the excluded file patterns + */ + public String[] getExcludeList() { + return line.getOptionValues(ARGUMENT.EXCLUDE); + } + /** * Returns the directory to write the reports to specified on the command line. * @@ -628,14 +681,13 @@ public final class CliParser { */ public void printVersionInfo() { final String version = String.format("%s version %s", - Settings.getString("application.name", "DependencyCheck"), - Settings.getString("application.version", "Unknown")); + Settings.getString(Settings.KEYS.APPLICATION_VAME, "dependency-check"), + Settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown")); System.out.println(version); } /** - * Checks if the auto update feature has been disabled. If it has been disabled via the command line this will - * return false. + * Checks if the auto update feature has been disabled. If it has been disabled via the command line this will return false. * * @return if auto-update is allowed. */ @@ -837,6 +889,10 @@ public final class CliParser { * Disables the Nuspec Analyzer. */ public static final String DISABLE_NUSPEC = "disableNuspec"; + /** + * Disables the Central Analyzer. + */ + public static final String DISABLE_CENTRAL = "disableCentral"; /** * Disables the Nexus Analyzer. */ @@ -877,5 +933,9 @@ public final class CliParser { * The CLI argument name for setting extra extensions. */ public static final String ADDITIONAL_ZIP_EXTENSIONS = "zipExtensions"; + /** + * Exclude path argument. + */ + public static final String EXCLUDE = "exclude"; } } diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/InvalidScanPathException.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/InvalidScanPathException.java new file mode 100644 index 000000000..e8a9611d4 --- /dev/null +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/InvalidScanPathException.java @@ -0,0 +1,61 @@ +/* + * This file is part of dependency-check-cli. + * + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck; + +/** + * Thrown if an invalid path is encountered. + * + * @author Jeremy Long + */ +class InvalidScanPathException extends Exception { + + /** + * Creates a new InvalidScanPathException. + */ + public InvalidScanPathException() { + super(); + } + + /** + * Creates a new InvalidScanPathException. + * + * @param msg a message for the exception + */ + public InvalidScanPathException(String msg) { + super(msg); + } + + /** + * Creates a new InvalidScanPathException. + * + * @param ex the cause of the exception + */ + public InvalidScanPathException(Throwable ex) { + super(ex); + } + + /** + * Creates a new InvalidScanPathException. + * + * @param msg a message for the exception + * @param ex the cause of the exception + */ + public InvalidScanPathException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/cli/package-info.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/cli/package-info.java deleted file mode 100644 index 1d5265535..000000000 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/cli/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * - * - * org.owasp.dependencycheck.cli - * - * - * Includes utility classes such as the CLI Parser, - * - * -*/ - -package org.owasp.dependencycheck.cli; diff --git a/dependency-check-cli/src/site/markdown/arguments.md b/dependency-check-cli/src/site/markdown/arguments.md index 5ffba77cd..a22f54786 100644 --- a/dependency-check-cli/src/site/markdown/arguments.md +++ b/dependency-check-cli/src/site/markdown/arguments.md @@ -6,8 +6,9 @@ The following table lists the command line arguments: Short | Argument Name   | Parameter | Description | Requirement -------|-----------------------|-----------------|-------------|------------ \-a | \-\-app | \ | The name of the application being scanned. This is a required argument. | Required - \-s | \-\-scan | \ | The path to scan \- this option can be specified multiple times. It is also possible to specify specific file types that should be scanned by supplying a scan path of '[path]/[to]/[scan]/*.zip'. The wild card can only be used to denote any file-name with a specific extension. | Required - \-o | \-\-out | \ | The folder to write reports to. This defaults to the current directory. | Optional + \-s | \-\-scan | \ | The path to scan \- this option can be specified multiple times. It is also possible to specify Ant style paths (e.g. directory/**/*.jar). | Required + | \-\-exclude | \ | The path patterns to exclude from the scan \- this option can be specified multiple times. This accepts Ant style path patterns (e.g. **/exclude/**) . | Optional + \-o | \-\-out | \ | The folder to write reports to. This defaults to the current directory. If the format is not set to ALL one could specify a specific file name. | Optional \-f | \-\-format | \ | The output format to write to (XML, HTML, VULN, ALL). The default is HTML. | Required \-l | \-\-log | \ | The file path to write verbose logging information. | Optional \-n | \-\-noupdate | | Disables the automatic updating of the CPE data. | Optional @@ -18,25 +19,27 @@ Short | Argument Name   | Parameter | Description | Requir Advanced Options ================ -Short | Argument Name        | Parameter | Description | Default Value --------|-----------------------|-----------------|-------------|--------------- - | \-\-disableArchive | | Sets whether the Archive Analyzer will be used. | false +Short | Argument Name        | Parameter | Description | Default Value +-------|-----------------------|-----------------|-----------------------------------------------------------------------------|--------------- + \-P | \-\-propertyfile | \ | Specifies a file that contains properties to use instead of applicaion defaults. |   + | \-\-disableArchive | | Sets whether the Archive Analyzer will be used. | false | \-\-zipExtensions | \ | A comma-separated list of additional file extensions to be treated like a ZIP file, the contents will be extracted and analyzed. |   - | \-\-disableJar | | Sets whether Jar Analyzer will be used. | false - | \-\-disableNexus | | Sets whether Nexus Analyzer will be used. | false - | \-\-nexus | \ | The url to the Nexus Server. | https://repository.sonatype.org/service/local/ - | \-\-nexusUsesProxy | \ | Whether or not the defined proxy should be used when connecting to Nexus. | true - | \-\-disableNuspec | | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | false - | \-\-disableAssembly | | Sets whether or not the .NET Assembly Analyzer should be used. | false - | \-\-pathToMono | \ | The path to Mono for .NET Assembly analysis on non-windows systems. |   - | \-\-proxyserver | \ | The proxy server to use when downloading resources. |   - | \-\-proxyport | \ | The proxy port to use when downloading resources. |   + | \-\-disableJar | | Sets whether the Jar Analyzer will be used. | false + | \-\-disableCentral | | Sets whether the Central Analyzer will be used. **Disabling this analyzer is not recommended as it could lead to false negatives (e.g. libraries that have vulnerabilities may not be reported correctly).** If this analyzer is being disabled there is a good chance you also want to disable the Nexus Analyzer. | false + | \-\-disableNexus | | Sets whether the Nexus Analyzer will be used. Note, this has been superceded by the Central Analyzer. However, you can configure the Nexus URL to utilize an internally hosted Nexus Pro server. | false + | \-\-nexus | \ | The url to the Nexus Pro Server. If not set the Nexus Analyzer will be disabled. |   + | \-\-nexusUsesProxy | \ | Whether or not the defined proxy should be used when connecting to Nexus. | true + | \-\-disableNuspec | | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | false + | \-\-disableAssembly | | Sets whether or not the .NET Assembly Analyzer should be used. | false + | \-\-pathToMono | \ | The path to Mono for .NET Assembly analysis on non-windows systems. |   + | \-\-proxyserver | \ | The proxy server to use when downloading resources. |   + | \-\-proxyport | \ | The proxy port to use when downloading resources. |   | \-\-connectiontimeout | \ | The connection timeout (in milliseconds) to use when downloading resources. |   - | \-\-proxypass | \ | The proxy password to use when downloading resources. |   - | \-\-proxyuser | \ | The proxy username to use when downloading resources. |   - | \-\-connectionString | \ | The connection string to the database. |   - | \-\-dbDriverName | \ | The database driver name. |   + | \-\-proxypass | \ | The proxy password to use when downloading resources. |   + | \-\-proxyuser | \ | The proxy username to use when downloading resources. |   + | \-\-connectionString | \ | The connection string to the database. |   + | \-\-dbDriverName | \ | The database driver name. |   | \-\-dbDriverPath | \ | The path to the database driver; note, this does not need to be set unless the JAR is outside of the class path. |   - | \-\-dbPassword | \ | The password for connecting to the database. |   - | \-\-dbUser | \ | The username used to connect to the database. |   + | \-\-dbPassword | \ | The password for connecting to the database. |   + | \-\-dbUser | \ | The username used to connect to the database. |   \-d | \-\-data | \ | The location of the data directory used to store persistent data. This option should generally not be set. |   diff --git a/dependency-check-cli/src/test/java/org/owasp/dependencycheck/cli/CliParserTest.java b/dependency-check-cli/src/test/java/org/owasp/dependencycheck/CliParserTest.java similarity index 99% rename from dependency-check-cli/src/test/java/org/owasp/dependencycheck/cli/CliParserTest.java rename to dependency-check-cli/src/test/java/org/owasp/dependencycheck/CliParserTest.java index 1c62ed81e..119397278 100644 --- a/dependency-check-cli/src/test/java/org/owasp/dependencycheck/cli/CliParserTest.java +++ b/dependency-check-cli/src/test/java/org/owasp/dependencycheck/CliParserTest.java @@ -15,8 +15,9 @@ * * Copyright (c) 2012 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.cli; +package org.owasp.dependencycheck; +import org.owasp.dependencycheck.CliParser; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; diff --git a/dependency-check-core/pom.xml b/dependency-check-core/pom.xml index fc649713b..c57fe865c 100644 --- a/dependency-check-core/pom.xml +++ b/dependency-check-core/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT dependency-check-core @@ -93,7 +93,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-dependency-plugin - 2.8 generate-resources @@ -110,7 +109,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-jar-plugin - 2.4 jar @@ -127,24 +125,13 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. - - - - true - - - - **/checkstyle* - - org.codehaus.mojo cobertura-maven-plugin - 2.6 - true + .*\$KEYS\.class .*\$Element\.class @@ -192,7 +179,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-surefire-plugin - 2.16 @@ -213,200 +199,149 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-failsafe-plugin - 2.16 data.directory ${project.build.directory}/data - - temp.directory - ${project.build.directory}/temp - - - - **/*IntegrationTest.java - - - - - - integration-test - verify - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.3 - - - org.apache.maven.doxia - doxia-module-markdown - 1.5 - - - - true - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.7 - - - - index - summary - license - help - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - Copyright© 2012-14 Jeremy Long. All Rights Reserved. - - - - default - - javadoc - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.1 - - - - dependency-updates-report - plugin-updates-report - - - - - - org.apache.maven.plugins - maven-jxr-plugin - 2.4 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.16 - - - - report-only - - - - integration-tests - - report-only - failsafe-report-only - - - - - - org.codehaus.mojo - taglist-maven-plugin - 2.4 - - - - - Todo Work - - - todo - ignoreCase - - - FIXME - exact - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - false - ${basedir}/../src/main/config/checkstyle-checks.xml - ${basedir}/../src/main/config/checkstyle-header.txt - ${basedir}/../src/main/config/checkstyle-suppressions.xml - checkstyle.suppressions.file - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.1 - - 1.6 - true - utf-8 - - **/generated/*.java - - - ../src/main/config/dcrules.xml - /rulesets/java/basic.xml - /rulesets/java/imports.xml - /rulesets/java/unusedcode.xml - - - - - org.codehaus.mojo - findbugs-maven-plugin - 2.5.3 - - - org.codehaus.mojo - javancss-maven-plugin - 2.0 - - org.apache.maven.plugins maven-compiler-plugin - 3.1 - false - 1.6 - 1.6 + -Xlint:unchecked + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + Copyright© 2012-15 Jeremy Long. All Rights Reserved. + + + + default + + javadoc + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + + dependency-updates-report + plugin-updates-report + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.4 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.16 + + + + report-only + + + + integration-tests + + report-only + failsafe-report-only + + + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Todo Work + + + todo + ignoreCase + + + FIXME + exact + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.11 + + false + false + ${basedir}/../src/main/config/checkstyle-checks.xml + ${basedir}/../src/main/config/checkstyle-header.txt + ${basedir}/../src/main/config/checkstyle-suppressions.xml + checkstyle.suppressions.file + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.1 + + 1.6 + true + utf-8 + + **/generated/*.java + + + ../src/main/config/dcrules.xml + /rulesets/java/basic.xml + /rulesets/java/imports.xml + /rulesets/java/unusedcode.xml + + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + + + org.owasp dependency-check-utils @@ -415,24 +350,24 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.lucene lucene-test-framework - 4.3.1 + ${apache.lucene.version} + test + + + org.jmockit + jmockit test com.google.code.findbugs annotations - 2.0.1 + 3.0.0 true - - commons-cli - commons-cli - 1.2 - org.apache.commons commons-compress - 1.8.1 + 1.9 commons-io @@ -442,22 +377,22 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. commons-lang commons-lang - 2.5 + 2.6 org.apache.lucene lucene-core - 4.5.1 + ${apache.lucene.version} org.apache.lucene lucene-analyzers-common - 4.5.1 + ${apache.lucene.version} org.apache.lucene lucene-queryparser - 4.5.1 + ${apache.lucene.version} org.apache.velocity @@ -467,7 +402,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. com.h2database h2 - 1.3.172 + 1.3.176 org.jsoup @@ -599,7 +534,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-surefire-plugin - 2.16 + 2.18.1 true @@ -607,7 +542,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-failsafe-plugin - 2.16 + 2.18.1 @@ -707,7 +642,47 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. provided true + + com.sun.jersey + jersey-client + 1.11.1 + provided + true + + + com.sun.faces + jsf-impl + 2.2.8-02 + provided + true + + + com.google.inject + guice + 3.0 + provided + true + + + org.opensaml + xmltooling + 1.4.1 + provided + true + + + org.springframework + spring-webmvc + 3.2.12.RELEASE + provided + true + + + + 4.7.2 + diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java index 3dfb16489..e2eeb9bc3 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java @@ -18,7 +18,6 @@ package org.owasp.dependencycheck; import java.io.File; -import java.io.Serializable; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashSet; @@ -32,8 +31,6 @@ import org.owasp.dependencycheck.analyzer.Analyzer; import org.owasp.dependencycheck.analyzer.AnalyzerService; import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; -import org.owasp.dependencycheck.data.cpe.CpeMemoryIndex; -import org.owasp.dependencycheck.data.cpe.IndexException; import org.owasp.dependencycheck.data.nvdcve.ConnectionFactory; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; @@ -53,28 +50,30 @@ import org.owasp.dependencycheck.utils.Settings; * * @author Jeremy Long */ -public class Engine implements Serializable { +public class Engine { /** * The list of dependencies. */ - private List dependencies; + private List dependencies = new ArrayList(); /** * A Map of analyzers grouped by Analysis phase. */ - private transient final EnumMap> analyzers; + private EnumMap> analyzers = new EnumMap>(AnalysisPhase.class); + /** * A Map of analyzers grouped by Analysis phase. */ - private transient final Set fileTypeAnalyzers; + private Set fileTypeAnalyzers = new HashSet(); + /** * The ClassLoader to use when dynamically loading Analyzer and Update services. */ - private transient ClassLoader serviceClassLoader; + private ClassLoader serviceClassLoader = Thread.currentThread().getContextClassLoader(); /** * The Logger for use throughout the class. */ - private transient static final Logger LOGGER = Logger.getLogger(Engine.class.getName()); + private static final Logger LOGGER = Logger.getLogger(Engine.class.getName()); /** * Creates a new Engine. @@ -82,32 +81,27 @@ public class Engine implements Serializable { * @throws DatabaseException thrown if there is an error connecting to the database */ public Engine() throws DatabaseException { - this(Thread.currentThread().getContextClassLoader()); + initializeEngine(); + } + + /** + * Creates a new Engine. + * + * @param serviceClassLoader a reference the class loader being used + * @throws DatabaseException thrown if there is an error connecting to the database + */ + public Engine(ClassLoader serviceClassLoader) throws DatabaseException { + this.serviceClassLoader = serviceClassLoader; + initializeEngine(); } /** * Creates a new Engine using the specified classloader to dynamically load Analyzer and Update services. * - * @param serviceClassLoader the ClassLoader to use when dynamically loading Analyzer and Update services * @throws DatabaseException thrown if there is an error connecting to the database */ - public Engine(ClassLoader serviceClassLoader) throws DatabaseException { - this.dependencies = new ArrayList(); - this.analyzers = new EnumMap>(AnalysisPhase.class); - this.fileTypeAnalyzers = new HashSet(); - this.serviceClassLoader = serviceClassLoader; - + protected final void initializeEngine() throws DatabaseException { ConnectionFactory.initialize(); - - boolean autoUpdate = true; - try { - autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); - } catch (InvalidSettingException ex) { - LOGGER.log(Level.FINE, "Invalid setting for auto-update; using true."); - } - if (autoUpdate) { - doUpdates(); - } loadAnalyzers(); } @@ -122,7 +116,9 @@ public class Engine implements Serializable { * Loads the analyzers specified in the configuration file (or system properties). */ private void loadAnalyzers() { - + if (!analyzers.isEmpty()) { + return; + } for (AnalysisPhase phase : AnalysisPhase.values()) { analyzers.put(phase, new ArrayList()); } @@ -157,159 +153,200 @@ public class Engine implements Serializable { return dependencies; } + /** + * Sets the dependencies. + * + * @param dependencies the dependencies + */ public void setDependencies(List dependencies) { this.dependencies = dependencies; - //for (Dependency dependency: dependencies) { - // dependencies.add(dependency); - //} } /** * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any * dependencies identified are added to the dependency collection. * - * @since v0.3.2.5 + * @param paths an array of paths to files or directories to be analyzed + * @return the list of dependencies scanned * - * @param paths an array of paths to files or directories to be analyzed. + * @since v0.3.2.5 */ - public void scan(String[] paths) { + public List scan(String[] paths) { + final List deps = new ArrayList(); for (String path : paths) { final File file = new File(path); - scan(file); + final List d = scan(file); + if (d != null) { + deps.addAll(d); + } } + return deps; } /** * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies * identified are added to the dependency collection. * - * @param path the path to a file or directory to be analyzed. + * @param path the path to a file or directory to be analyzed + * @return the list of dependencies scanned */ - public void scan(String path) { - if (path.matches("^.*[\\/]\\*\\.[^\\/:*|?<>\"]+$")) { - final String[] parts = path.split("\\*\\."); - final String[] ext = new String[]{parts[parts.length - 1]}; - final File dir = new File(path.substring(0, path.length() - ext[0].length() - 2)); - if (dir.isDirectory()) { - final List files = (List) org.apache.commons.io.FileUtils.listFiles(dir, ext, true); - scan(files); - } else { - final String msg = String.format("Invalid file path provided to scan '%s'", path); - LOGGER.log(Level.SEVERE, msg); - } - } else { - final File file = new File(path); - scan(file); - } + public List scan(String path) { + final File file = new File(path); + return scan(file); } /** * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any * dependencies identified are added to the dependency collection. * - * @since v0.3.2.5 - * * @param files an array of paths to files or directories to be analyzed. + * @return the list of dependencies + * + * @since v0.3.2.5 */ - public void scan(File[] files) { + public List scan(File[] files) { + final List deps = new ArrayList(); for (File file : files) { - scan(file); + final List d = scan(file); + if (d != null) { + deps.addAll(d); + } } + return deps; } /** * Scans a list of files or directories. If a directory is specified, it will be scanned recursively. Any * dependencies identified are added to the dependency collection. * - * @since v0.3.2.5 + * @param files a set of paths to files or directories to be analyzed + * @return the list of dependencies scanned * - * @param files a set of paths to files or directories to be analyzed. + * @since v0.3.2.5 */ - public void scan(Set files) { + public List scan(Set files) { + final List deps = new ArrayList(); for (File file : files) { - scan(file); + final List d = scan(file); + if (d != null) { + deps.addAll(d); + } } + return deps; } /** * Scans a list of files or directories. If a directory is specified, it will be scanned recursively. Any * dependencies identified are added to the dependency collection. * - * @since v0.3.2.5 + * @param files a set of paths to files or directories to be analyzed + * @return the list of dependencies scanned * - * @param files a set of paths to files or directories to be analyzed. + * @since v0.3.2.5 */ - public void scan(List files) { + public List scan(List files) { + final List deps = new ArrayList(); for (File file : files) { - scan(file); + final List d = scan(file); + if (d != null) { + deps.addAll(d); + } } + return deps; } /** * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies * identified are added to the dependency collection. * + * @param file the path to a file or directory to be analyzed + * @return the list of dependencies scanned + * * @since v0.3.2.4 * - * @param file the path to a file or directory to be analyzed. */ - public void scan(File file) { + public List scan(File file) { if (file.exists()) { if (file.isDirectory()) { - scanDirectory(file); + return scanDirectory(file); } else { - scanFile(file); + final Dependency d = scanFile(file); + if (d != null) { + final List deps = new ArrayList(); + deps.add(d); + return deps; + } } } + return null; } /** * Recursively scans files and directories. Any dependencies identified are added to the dependency collection. * - * @param dir the directory to scan. + * @param dir the directory to scan + * @return the list of Dependency objects scanned */ - protected void scanDirectory(File dir) { + protected List scanDirectory(File dir) { final File[] files = dir.listFiles(); + final List deps = new ArrayList(); if (files != null) { for (File f : files) { if (f.isDirectory()) { - scanDirectory(f); + final List d = scanDirectory(f); + if (d != null) { + deps.addAll(d); + } } else { - scanFile(f); + final Dependency d = scanFile(f); + deps.add(d); } } } + return deps; } /** * Scans a specified file. If a dependency is identified it is added to the dependency collection. * - * @param file The file to scan. + * @param file The file to scan + * @return the scanned dependency */ - protected void scanFile(File file) { + protected Dependency scanFile(File file) { if (!file.isFile()) { final String msg = String.format("Path passed to scanFile(File) is not a file: %s. Skipping the file.", file.toString()); LOGGER.log(Level.FINE, msg); - return; + return null; } final String fileName = file.getName(); final String extension = FileUtils.getFileExtension(fileName); + Dependency dependency = null; if (extension != null) { if (supportsExtension(extension)) { - final Dependency dependency = new Dependency(file); + dependency = new Dependency(file); dependencies.add(dependency); } } else { - final String msg = String.format("No file extension found on file '%s'. The file was not analyzed.", - file.toString()); - LOGGER.log(Level.FINEST, msg); + final String msg = String.format("No file extension found on file '%s'. The file was not analyzed.", file.toString()); + LOGGER.log(Level.FINE, msg); } + return dependency; } /** * Runs the analyzers against all of the dependencies. */ public void analyzeDependencies() { + boolean autoUpdate = true; + try { + autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); + } catch (InvalidSettingException ex) { + LOGGER.log(Level.FINE, "Invalid setting for auto-update; using true."); + } + if (autoUpdate) { + doUpdates(); + } + //need to ensure that data exists try { ensureDataExists(); @@ -338,7 +375,7 @@ public class Engine implements Serializable { final List analyzerList = analyzers.get(phase); for (Analyzer a : analyzerList) { - initializeAnalyzer(a); + a = initializeAnalyzer(a); /* need to create a copy of the collection because some of the * analyzers may modify it. This prevents ConcurrentModificationExceptions. @@ -393,8 +430,9 @@ public class Engine implements Serializable { * Initializes the given analyzer. * * @param analyzer the analyzer to initialize + * @return the initialized analyzer */ - private void initializeAnalyzer(Analyzer analyzer) { + protected Analyzer initializeAnalyzer(Analyzer analyzer) { try { final String msg = String.format("Initializing %s", analyzer.getName()); LOGGER.log(Level.FINE, msg); @@ -409,6 +447,7 @@ public class Engine implements Serializable { LOGGER.log(Level.FINEST, null, ex1); } } + return analyzer; } /** @@ -416,7 +455,7 @@ public class Engine implements Serializable { * * @param analyzer the analyzer to close */ - private void closeAnalyzer(Analyzer analyzer) { + protected void closeAnalyzer(Analyzer analyzer) { final String msg = String.format("Closing Analyzer '%s'", analyzer.getName()); LOGGER.log(Level.FINE, msg); try { @@ -430,6 +469,7 @@ public class Engine implements Serializable { * Cycles through the cached web data sources and calls update on all of them. */ private void doUpdates() { + LOGGER.info("Checking for updates"); final UpdateService service = new UpdateService(serviceClassLoader); final Iterator iterator = service.getDataSources(); while (iterator.hasNext()) { @@ -439,10 +479,10 @@ public class Engine implements Serializable { } catch (UpdateException ex) { LOGGER.log(Level.WARNING, "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities."); - LOGGER.log(Level.FINE, - String.format("Unable to update details for %s", source.getClass().getName()), ex); + LOGGER.log(Level.FINE, String.format("Unable to update details for %s", source.getClass().getName()), ex); } } + LOGGER.info("Check for updates complete"); } /** @@ -478,6 +518,15 @@ public class Engine implements Serializable { return scan; } + /** + * Returns the set of file type analyzers. + * + * @return the set of file type analyzers + */ + public Set getFileTypeAnalyzers() { + return this.fileTypeAnalyzers; + } + /** * Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown. * @@ -485,22 +534,16 @@ public class Engine implements Serializable { * @throws DatabaseException thrown if there is an exception opening the database */ private void ensureDataExists() throws NoDataException, DatabaseException { - final CpeMemoryIndex cpe = CpeMemoryIndex.getInstance(); final CveDB cve = new CveDB(); - try { cve.open(); - cpe.open(cve); - } catch (IndexException ex) { - throw new NoDataException(ex.getMessage(), ex); + if (!cve.dataExists()) { + throw new NoDataException("No documents exist"); + } } catch (DatabaseException ex) { throw new NoDataException(ex.getMessage(), ex); } finally { cve.close(); } - if (cpe.numDocs() <= 0) { - cpe.close(); - throw new NoDataException("No documents exist"); - } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java index 6fa41e556..f88483487 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java @@ -440,6 +440,52 @@ public class DependencyCheckScanAgent { this.showSummary = showSummary; } + /** + * Whether or not the Maven Central analyzer is enabled. + */ + private boolean centralAnalyzerEnabled = true; + + /** + * Get the value of centralAnalyzerEnabled. + * + * @return the value of centralAnalyzerEnabled + */ + public boolean isCentralAnalyzerEnabled() { + return centralAnalyzerEnabled; + } + + /** + * Set the value of centralAnalyzerEnabled. + * + * @param centralAnalyzerEnabled new value of centralAnalyzerEnabled + */ + public void setCentralAnalyzerEnabled(boolean centralAnalyzerEnabled) { + this.centralAnalyzerEnabled = centralAnalyzerEnabled; + } + + /** + * The URL of Maven Central. + */ + private String centralUrl; + + /** + * Get the value of centralUrl. + * + * @return the value of centralUrl + */ + public String getCentralUrl() { + return centralUrl; + } + + /** + * Set the value of centralUrl. + * + * @param centralUrl new value of centralUrl + */ + public void setCentralUrl(String centralUrl) { + this.centralUrl = centralUrl; + } + /** * Whether or not the nexus analyzer is enabled. */ @@ -849,6 +895,10 @@ public class DependencyCheckScanAgent { if (suppressionFile != null && !suppressionFile.isEmpty()) { Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile); } + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled); + if (centralUrl != null && !centralUrl.isEmpty()) { + Settings.setString(Settings.KEYS.ANALYZER_CENTRAL_URL, centralUrl); + } Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled); if (nexusUrl != null && !nexusUrl.isEmpty()) { Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java index 935a820b7..21c8aabd7 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java @@ -41,16 +41,7 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen * enabled. */ public AbstractFileTypeAnalyzer() { - final String key = getAnalyzerEnabledSettingKey(); - try { - enabled = Settings.getBoolean(key, true); - } catch (InvalidSettingException ex) { - String msg = String.format("Invalid setting for property '%s'", key); - LOGGER.log(Level.WARNING, msg); - LOGGER.log(Level.FINE, "", ex); - msg = String.format("%s has been disabled", getName()); - LOGGER.log(Level.WARNING, msg); - } + reset(); } // @@ -164,6 +155,23 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen } } + /** + * Resets the enabled flag on the analyzer. + */ + @Override + public final void reset() { + final String key = getAnalyzerEnabledSettingKey(); + try { + enabled = Settings.getBoolean(key, true); + } catch (InvalidSettingException ex) { + String msg = String.format("Invalid setting for property '%s'", key); + LOGGER.log(Level.WARNING, msg); + LOGGER.log(Level.FINE, "", ex); + msg = String.format("%s has been disabled", getName()); + LOGGER.log(Level.WARNING, msg); + } + } + /** * Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, * scanned, and added to the list of dependencies within the engine. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java index 9cab329ee..dc662b2c6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java @@ -110,7 +110,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { static { final String additionalZipExt = Settings.getString(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS); if (additionalZipExt != null) { - final HashSet ext = new HashSet(Arrays.asList(additionalZipExt)); + final Set ext = new HashSet(Arrays.asList(additionalZipExt)); ZIPPABLES.addAll(ext); } EXTENSIONS.addAll(ZIPPABLES); @@ -186,7 +186,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { if (tempFileLocation != null && tempFileLocation.exists()) { LOGGER.log(Level.FINE, "Attempting to delete temporary files"); final boolean success = FileUtils.delete(tempFileLocation); - if (!success && tempFileLocation != null & tempFileLocation.exists()) { + if (!success && tempFileLocation != null && tempFileLocation.exists() && tempFileLocation.list().length > 0) { LOGGER.log(Level.WARNING, "Failed to delete some temporary files, see the log for more details"); } } @@ -221,9 +221,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { final String displayPath = String.format("%s%s", dependency.getFilePath(), d.getActualFilePath().substring(tmpDir.getAbsolutePath().length())); - final String displayName = String.format("%s%s%s", + final String displayName = String.format("%s: %s", dependency.getFileName(), - File.separator, d.getFileName()); d.setFilePath(displayPath); d.setFileName(displayName); @@ -339,7 +338,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { try { fis.close(); } catch (IOException ex) { - LOGGER.log(Level.FINEST, null, ex); + LOGGER.log(Level.FINE, null, ex); } } } @@ -368,8 +367,10 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { final File file = new File(destination, entry.getName()); final String ext = FileUtils.getFileExtension(file.getName()); if (engine.supportsExtension(ext)) { + final String extracting = String.format("Extracting '%s'", file.getPath()); + LOGGER.fine(extracting); BufferedOutputStream bos = null; - FileOutputStream fos; + FileOutputStream fos = null; try { final File parent = file.getParentFile(); if (!parent.isDirectory()) { @@ -381,7 +382,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos, BUFFER_SIZE); int count; - final byte data[] = new byte[BUFFER_SIZE]; + final byte[] data = new byte[BUFFER_SIZE]; while ((count = input.read(data, 0, BUFFER_SIZE)) != -1) { bos.write(data, 0, count); } @@ -402,6 +403,13 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.log(Level.FINEST, null, ex); } } + if (fos != null) { + try { + fos.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, null, ex); + } + } } } } @@ -429,6 +437,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { * @throws ArchiveExtractionException thrown if there is an exception decompressing the file */ private void decompressFile(CompressorInputStream inputStream, File outputFile) throws ArchiveExtractionException { + final String msg = String.format("Decompressing '%s'", outputFile.getPath()); + LOGGER.fine(msg); FileOutputStream out = null; try { out = new FileOutputStream(outputFile); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java index ac18ec90a..f8417d0d7 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java @@ -120,9 +120,11 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { // Try evacuating the error stream rdr = new BufferedReader(new InputStreamReader(proc.getErrorStream(), "UTF-8")); String line = null; + // CHECKSTYLE:OFF while (rdr.ready() && (line = rdr.readLine()) != null) { LOGGER.log(Level.WARNING, "analyzer.AssemblyAnalyzer.grokassembly.stderr", line); } + // CHECKSTYLE:ON int rc = 0; doc = builder.parse(proc.getInputStream()); @@ -233,9 +235,11 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { final Process p = pb.start(); // Try evacuating the error stream rdr = new BufferedReader(new InputStreamReader(p.getErrorStream(), "UTF-8")); + // CHECKSTYLE:OFF while (rdr.ready() && rdr.readLine() != null) { // We expect this to complain } + // CHECKSTYLE:ON final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(p.getInputStream()); final XPath xpath = XPathFactory.newInstance().newXPath(); final String error = xpath.evaluate("/assembly/error", doc); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java index 97779a515..19d9c890d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java @@ -51,8 +51,8 @@ import org.owasp.dependencycheck.utils.DependencyVersion; import org.owasp.dependencycheck.utils.DependencyVersionUtil; /** - * CPEAnalyzer is a utility class that takes a project dependency and attempts to discern if there is an associated CPE. - * It uses the evidence contained within the dependency to search the Lucene index. + * CPEAnalyzer is a utility class that takes a project dependency and attempts to discern if there is an associated CPE. It uses + * the evidence contained within the dependency to search the Lucene index. * * @author Jeremy Long */ @@ -130,8 +130,8 @@ public class CPEAnalyzer implements Analyzer { * Opens the data source. * * @throws IOException when the Lucene directory to be queried does not exist or is corrupt. - * @throws DatabaseException when the database throws an exception. This usually occurs when the database is in use - * by another process. + * @throws DatabaseException when the database throws an exception. This usually occurs when the database is in use by another + * process. */ public void open() throws IOException, DatabaseException { LOGGER.log(Level.FINE, "Opening the CVE Database"); @@ -161,8 +161,8 @@ public class CPEAnalyzer implements Analyzer { } /** - * Searches the data store of CPE entries, trying to identify the CPE for the given dependency based on the evidence - * contained within. The dependency passed in is updated with any identified CPE values. + * Searches the data store of CPE entries, trying to identify the CPE for the given dependency based on the evidence contained + * within. The dependency passed in is updated with any identified CPE values. * * @param dependency the dependency to search for CPE entries on. * @throws CorruptIndexException is thrown when the Lucene index is corrupt. @@ -176,24 +176,25 @@ public class CPEAnalyzer implements Analyzer { for (Confidence confidence : Confidence.values()) { if (dependency.getVendorEvidence().contains(confidence)) { vendors = addEvidenceWithoutDuplicateTerms(vendors, dependency.getVendorEvidence(), confidence); + LOGGER.fine(String.format("vendor search: %s", vendors)); } if (dependency.getProductEvidence().contains(confidence)) { products = addEvidenceWithoutDuplicateTerms(products, dependency.getProductEvidence(), confidence); + LOGGER.fine(String.format("product search: %s", products)); } - /* bug fix for #40 - version evidence is not showing up as "used" in the reports if there is no - * CPE identified. As such, we are "using" the evidence and ignoring the results. */ -// if (dependency.getVersionEvidence().contains(confidence)) { -// addEvidenceWithoutDuplicateTerms("", dependency.getVersionEvidence(), confidence); -// } if (!vendors.isEmpty() && !products.isEmpty()) { final List entries = searchCPE(vendors, products, dependency.getProductEvidence().getWeighting(), dependency.getVendorEvidence().getWeighting()); - + if (entries == null) { + continue; + } boolean identifierAdded = false; for (IndexEntry e : entries) { + LOGGER.fine(String.format("Verifying entry: %s", e.toString())); if (verifyEntry(e, dependency)) { final String vendor = e.getVendor(); final String product = e.getProduct(); + LOGGER.fine(String.format("identified vendor/product: %s/%s", vendor, product)); identifierAdded |= determineIdentifiers(dependency, vendor, product, confidence); } } @@ -205,9 +206,9 @@ public class CPEAnalyzer implements Analyzer { } /** - * Returns the text created by concatenating the text and the values from the EvidenceCollection (filtered for a - * specific confidence). This attempts to prevent duplicate terms from being added.
Note, if the evidence - * is longer then 200 characters it will be truncated. + * Returns the text created by concatenating the text and the values from the EvidenceCollection (filtered for a specific + * confidence). This attempts to prevent duplicate terms from being added.
Note, if the evidence is longer then 200 + * characters it will be truncated. * * @param text the base text. * @param ec an EvidenceCollection @@ -242,49 +243,49 @@ public class CPEAnalyzer implements Analyzer { * version.

* *

- * If either the vendorWeightings or productWeightings lists have been populated this data is used to add weighting - * factors to the search.

+ * If either the vendorWeightings or productWeightings lists have been populated this data is used to add weighting factors to + * the search.

* * @param vendor the text used to search the vendor field * @param product the text used to search the product field * @param vendorWeightings a list of strings to use to add weighting factors to the vendor field * @param productWeightings Adds a list of strings that will be used to add weighting factors to the product search * @return a list of possible CPE values - * @throws CorruptIndexException when the Lucene index is corrupt - * @throws IOException when the Lucene index is not found - * @throws ParseException when the generated query is not valid */ protected List searchCPE(String vendor, String product, - Set vendorWeightings, Set productWeightings) - throws CorruptIndexException, IOException, ParseException { - final ArrayList ret = new ArrayList(MAX_QUERY_RESULTS); + Set vendorWeightings, Set productWeightings) { + + final List ret = new ArrayList(MAX_QUERY_RESULTS); final String searchString = buildSearch(vendor, product, vendorWeightings, productWeightings); if (searchString == null) { return ret; } - - final TopDocs docs = cpe.search(searchString, MAX_QUERY_RESULTS); - for (ScoreDoc d : docs.scoreDocs) { - if (d.score >= 0.08) { - final Document doc = cpe.getDocument(d.doc); - final IndexEntry entry = new IndexEntry(); - entry.setVendor(doc.get(Fields.VENDOR)); - entry.setProduct(doc.get(Fields.PRODUCT)); -// if (d.score < 0.08) { -// System.out.print(entry.getVendor()); -// System.out.print(":"); -// System.out.print(entry.getProduct()); -// System.out.print(":"); -// System.out.println(d.score); -// } - entry.setSearchScore(d.score); - if (!ret.contains(entry)) { - ret.add(entry); + try { + final TopDocs docs = cpe.search(searchString, MAX_QUERY_RESULTS); + for (ScoreDoc d : docs.scoreDocs) { + if (d.score >= 0.08) { + final Document doc = cpe.getDocument(d.doc); + final IndexEntry entry = new IndexEntry(); + entry.setVendor(doc.get(Fields.VENDOR)); + entry.setProduct(doc.get(Fields.PRODUCT)); + entry.setSearchScore(d.score); + if (!ret.contains(entry)) { + ret.add(entry); + } } } + return ret; + } catch (ParseException ex) { + final String msg = String.format("Unable to parse: %s", searchString); + LOGGER.log(Level.WARNING, "An error occured querying the CPE data. See the log for more details."); + LOGGER.log(Level.INFO, msg, ex); + } catch (IOException ex) { + final String msg = String.format("IO Error with search string: %s", searchString); + LOGGER.log(Level.WARNING, "An error occured reading CPE data. See the log for more details."); + LOGGER.log(Level.INFO, msg, ex); } - return ret; + return null; } /** @@ -292,8 +293,8 @@ public class CPEAnalyzer implements Analyzer { * Builds a Lucene search string by properly escaping data and constructing a valid search query.

* *

- * If either the possibleVendor or possibleProducts lists have been populated this data is used to add weighting - * factors to the search string generated.

+ * If either the possibleVendor or possibleProducts lists have been populated this data is used to add weighting factors to + * the search string generated.

* * @param vendor text to search the vendor field * @param product text to search the product field @@ -319,9 +320,8 @@ public class CPEAnalyzer implements Analyzer { } /** - * This method constructs a Lucene query for a given field. The searchText is split into separate words and if the - * word is within the list of weighted words then an additional weighting is applied to the term as it is appended - * into the query. + * This method constructs a Lucene query for a given field. The searchText is split into separate words and if the word is + * within the list of weighted words then an additional weighting is applied to the term as it is appended into the query. * * @param sb a StringBuilder that the query text will be appended to. * @param field the field within the Lucene index that the query is searching. @@ -392,8 +392,8 @@ public class CPEAnalyzer implements Analyzer { } /** - * Ensures that the CPE Identified matches the dependency. This validates that the product, vendor, and version - * information for the CPE are contained within the dependencies evidence. + * Ensures that the CPE Identified matches the dependency. This validates that the product, vendor, and version information + * for the CPE are contained within the dependencies evidence. * * @param entry a CPE entry. * @param dependency the dependency that the CPE entries could be for. @@ -482,17 +482,19 @@ public class CPEAnalyzer implements Analyzer { } /** - * Retrieves a list of CPE values from the CveDB based on the vendor and product passed in. The list is then - * validated to find only CPEs that are valid for the given dependency. It is possible that the CPE identified is a - * best effort "guess" based on the vendor, product, and version information. + * Retrieves a list of CPE values from the CveDB based on the vendor and product passed in. The list is then validated to find + * only CPEs that are valid for the given dependency. It is possible that the CPE identified is a best effort "guess" based on + * the vendor, product, and version information. * * @param dependency the Dependency being analyzed * @param vendor the vendor for the CPE being analyzed * @param product the product for the CPE being analyzed + * @param currentConfidence the current confidence being used during analysis * @return true if an identifier was added to the dependency; otherwise false * @throws UnsupportedEncodingException is thrown if UTF-8 is not supported */ - private boolean determineIdentifiers(Dependency dependency, String vendor, String product, Confidence currentConfidence) throws UnsupportedEncodingException { + protected boolean determineIdentifiers(Dependency dependency, String vendor, String product, + Confidence currentConfidence) throws UnsupportedEncodingException { final Set cpes = cve.getCPEs(vendor, product); DependencyVersion bestGuess = new DependencyVersion("-"); Confidence bestGuessConf = null; @@ -590,8 +592,8 @@ public class CPEAnalyzer implements Analyzer { */ BEST_GUESS, /** - * The entire vendor/product group must be added (without a guess at version) because there is a CVE with a VS - * that only specifies vendor/product. + * The entire vendor/product group must be added (without a guess at version) because there is a CVE with a VS that only + * specifies vendor/product. */ BROAD_MATCH } @@ -739,8 +741,7 @@ public class CPEAnalyzer implements Analyzer { // /** - * Standard implementation of compareTo that compares identifier confidence, evidence confidence, and then the - * identifier. + * Standard implementation of compareTo that compares identifier confidence, evidence confidence, and then the identifier. * * @param o the IdentifierMatch to compare to * @return the natural ordering of IdentifierMatch diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java new file mode 100644 index 000000000..244358fa1 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java @@ -0,0 +1,243 @@ +/* + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.analyzer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.commons.io.FileUtils; +import org.owasp.dependencycheck.Engine; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.data.central.CentralSearch; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.dependency.Evidence; +import org.owasp.dependencycheck.jaxb.pom.PomUtils; +import org.owasp.dependencycheck.utils.DownloadFailedException; +import org.owasp.dependencycheck.utils.Downloader; +import org.owasp.dependencycheck.utils.InvalidSettingException; +import org.owasp.dependencycheck.utils.Settings; + +/** + * Analyzer which will attempt to locate a dependency, and the GAV information, by querying Central for the dependency's SHA-1 + * digest. + * + * @author colezlaw + */ +public class CentralAnalyzer extends AbstractFileTypeAnalyzer { + + /** + * The logger. + */ + private static final Logger LOGGER = Logger.getLogger(CentralAnalyzer.class.getName()); + + /** + * The name of the analyzer. + */ + private static final String ANALYZER_NAME = "Central Analyzer"; + + /** + * The phase in which this analyzer runs. + */ + private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION; + + /** + * The types of files on which this will work. + */ + private static final Set SUPPORTED_EXTENSIONS = newHashSet("jar"); + + /** + * The analyzer should be disabled if there are errors, so this is a flag to determine if such an error has occurred. + */ + private boolean errorFlag = false; + + /** + * The searcher itself. + */ + private CentralSearch searcher; + /** + * Utility to read POM files. + */ + private PomUtils pomUtil = new PomUtils(); + /** + * Field indicating if the analyzer is enabled. + */ + private final boolean enabled = checkEnabled(); + + /** + * Determine whether to enable this analyzer or not. + * + * @return whether the analyzer should be enabled + */ + @Override + public boolean isEnabled() { + return enabled; + } + + /** + * Determines if this analyzer is enabled. + * + * @return true if the analyzer is enabled; otherwise false + */ + private boolean checkEnabled() { + boolean retval = false; + + try { + if (Settings.getBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED)) { + if (!Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED) + || NexusAnalyzer.DEFAULT_URL.equals(Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL))) { + LOGGER.fine("Enabling the Central analyzer"); + retval = true; + } else { + LOGGER.info("Nexus analyzer is enabled, disabling the Central Analyzer"); + } + } else { + LOGGER.info("Central analyzer disabled"); + } + } catch (InvalidSettingException ise) { + LOGGER.warning("Invalid setting. Disabling the Central analyzer"); + } + return retval; + } + + /** + * Initializes the analyzer once before any analysis is performed. + * + * @throws Exception if there's an error during initialization + */ + @Override + public void initializeFileTypeAnalyzer() throws Exception { + LOGGER.fine("Initializing Central analyzer"); + LOGGER.fine(String.format("Central analyzer enabled: %s", isEnabled())); + if (isEnabled()) { + final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_CENTRAL_URL); + LOGGER.fine(String.format("Central Analyzer URL: %s", searchUrl)); + searcher = new CentralSearch(new URL(searchUrl)); + } + } + + /** + * Returns the analyzer's name. + * + * @return the name of the analyzer + */ + @Override + public String getName() { + return ANALYZER_NAME; + } + + /** + * Returns the key used in the properties file to to reference the analyzer's enabled property. + * + * @return the analyzer's enabled property setting key. + */ + @Override + protected String getAnalyzerEnabledSettingKey() { + return Settings.KEYS.ANALYZER_CENTRAL_ENABLED; + } + + /** + * Returns the analysis phase under which the analyzer runs. + * + * @return the phase under which the analyzer runs + */ + @Override + public AnalysisPhase getAnalysisPhase() { + return ANALYSIS_PHASE; + } + + /** + * Returns the extensions for which this Analyzer runs. + * + * @return the extensions for which this Analyzer runs + */ + @Override + public Set getSupportedExtensions() { + return SUPPORTED_EXTENSIONS; + } + + /** + * Performs the analysis. + * + * @param dependency the dependency to analyze + * @param engine the engine + * @throws AnalysisException when there's an exception during analysis + */ + @Override + public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException { + if (errorFlag || !isEnabled()) { + return; + } + + try { + final List mas = searcher.searchSha1(dependency.getSha1sum()); + final Confidence confidence = mas.size() > 1 ? Confidence.HIGH : Confidence.HIGHEST; + for (MavenArtifact ma : mas) { + LOGGER.fine(String.format("Central analyzer found artifact (%s) for dependency (%s)", ma.toString(), dependency.getFileName())); + dependency.addAsEvidence("central", ma, confidence); + boolean pomAnalyzed = false; + for (Evidence e : dependency.getVendorEvidence()) { + if ("pom".equals(e.getSource())) { + pomAnalyzed = true; + break; + } + } + if (!pomAnalyzed && ma.getPomUrl() != null) { + File pomFile = null; + try { + final File baseDir = Settings.getTempDirectory(); + pomFile = File.createTempFile("pom", ".xml", baseDir); + if (!pomFile.delete()) { + final String msg = String.format("Unable to fetch pom.xml for %s from Central; " + + "this could result in undetected CPE/CVEs.", dependency.getFileName()); + LOGGER.warning(msg); + LOGGER.fine("Unable to delete temp file"); + } + LOGGER.fine(String.format("Downloading %s", ma.getPomUrl())); + Downloader.fetchFile(new URL(ma.getPomUrl()), pomFile); + pomUtil.analyzePOM(dependency, pomFile); + + } catch (DownloadFailedException ex) { + final String msg = String.format("Unable to download pom.xml for %s from Central; " + + "this could result in undetected CPE/CVEs.", dependency.getFileName()); + LOGGER.warning(msg); + } finally { + if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) { + pomFile.deleteOnExit(); + } + } + } + + } + } catch (IllegalArgumentException iae) { + LOGGER.info(String.format("invalid sha1-hash on %s", dependency.getFileName())); + } catch (FileNotFoundException fnfe) { + LOGGER.fine(String.format("Artifact not found in repository: '%s", dependency.getFileName())); + } catch (IOException ioe) { + LOGGER.log(Level.FINE, "Could not connect to Central search", ioe); + errorFlag = true; + } + } + +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java index 3c8a67471..fe1c653e4 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java @@ -36,9 +36,9 @@ import org.owasp.dependencycheck.utils.LogUtils; /** *

- * This analyzer ensures dependencies that should be grouped together, to remove excess noise from the report, are - * grouped. An example would be Spring, Spring Beans, Spring MVC, etc. If they are all for the same version and have the - * same relative path then these should be grouped into a single dependency under the core/main library.

+ * This analyzer ensures dependencies that should be grouped together, to remove excess noise from the report, are grouped. An + * example would be Spring, Spring Beans, Spring MVC, etc. If they are all for the same version and have the same relative path + * then these should be grouped into a single dependency under the core/main library.

*

* Note, this grouping only works on dependencies with identified CVE entries

* @@ -55,7 +55,7 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal /** * A pattern for obtaining the first part of a filename. */ - private static final Pattern STARTING_TEXT_PATTERN = Pattern.compile("^[a-zA-Z]*"); + private static final Pattern STARTING_TEXT_PATTERN = Pattern.compile("^[a-zA-Z0-9]*"); /** * a flag indicating if this analyzer has run. This analyzer only runs once. */ @@ -91,8 +91,8 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal // /** - * Analyzes a set of dependencies. If they have been found to have the same base path and the same set of - * identifiers they are likely related. The related dependencies are bundled into a single reportable item. + * Analyzes a set of dependencies. If they have been found to have the same base path and the same set of identifiers they are + * likely related. The related dependencies are bundled into a single reportable item. * * @param ignore this analyzer ignores the dependency being analyzed * @param engine the engine that is scanning the dependencies @@ -107,30 +107,34 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal //for (Dependency nextDependency : engine.getDependencies()) { while (mainIterator.hasNext()) { final Dependency dependency = mainIterator.next(); - if (mainIterator.hasNext()) { + if (mainIterator.hasNext() && !dependenciesToRemove.contains(dependency)) { final ListIterator subIterator = engine.getDependencies().listIterator(mainIterator.nextIndex()); while (subIterator.hasNext()) { final Dependency nextDependency = subIterator.next(); if (hashesMatch(dependency, nextDependency)) { - if (isCore(dependency, nextDependency)) { + if (firstPathIsShortest(dependency.getFilePath(), nextDependency.getFilePath())) { mergeDependencies(dependency, nextDependency, dependenciesToRemove); } else { mergeDependencies(nextDependency, dependency, dependenciesToRemove); + break; //since we merged into the next dependency - skip forward to the next in mainIterator } } else if (isShadedJar(dependency, nextDependency)) { if (dependency.getFileName().toLowerCase().endsWith("pom.xml")) { - dependenciesToRemove.add(dependency); + mergeDependencies(nextDependency, dependency, dependenciesToRemove); + nextDependency.getRelatedDependencies().remove(dependency); + break; } else { - dependenciesToRemove.add(nextDependency); + mergeDependencies(dependency, nextDependency, dependenciesToRemove); + nextDependency.getRelatedDependencies().remove(nextDependency); } } else if (cpeIdentifiersMatch(dependency, nextDependency) && hasSameBasePath(dependency, nextDependency) && fileNameMatch(dependency, nextDependency)) { - if (isCore(dependency, nextDependency)) { mergeDependencies(dependency, nextDependency, dependenciesToRemove); } else { mergeDependencies(nextDependency, dependency, dependenciesToRemove); + break; //since we merged into the next dependency - skip forward to the next in mainIterator } } } @@ -138,9 +142,7 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal } //removing dependencies here as ensuring correctness and avoiding ConcurrentUpdateExceptions // was difficult because of the inner iterator. - for (Dependency d : dependenciesToRemove) { - engine.getDependencies().remove(d); - } + engine.getDependencies().removeAll(dependenciesToRemove); } } @@ -148,10 +150,10 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal * Adds the relatedDependency to the dependency's related dependencies. * * @param dependency the main dependency - * @param relatedDependency a collection of dependencies to be removed from the main analysis loop, this is the - * source of dependencies to remove - * @param dependenciesToRemove a collection of dependencies that will be removed from the main analysis loop, this - * function adds to this collection + * @param relatedDependency a collection of dependencies to be removed from the main analysis loop, this is the source of + * dependencies to remove + * @param dependenciesToRemove a collection of dependencies that will be removed from the main analysis loop, this function + * adds to this collection */ private void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set dependenciesToRemove) { dependency.addRelatedDependency(relatedDependency); @@ -160,12 +162,14 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal dependency.addRelatedDependency(i.next()); i.remove(); } + if (dependency.getSha1sum().equals(relatedDependency.getSha1sum())) { + dependency.addAllProjectReferences(relatedDependency.getProjectReferences()); + } dependenciesToRemove.add(relatedDependency); } /** - * Attempts to trim a maven repo to a common base path. This is typically - * [drive]\[repo_location]\repository\[path1]\[path2]. + * Attempts to trim a maven repo to a common base path. This is typically [drive]\[repo_location]\repository\[path1]\[path2]. * * @param path the path to trim * @return a string representing the base path. @@ -201,25 +205,8 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal || dependency2 == null || dependency2.getFileName() == null) { return false; } - String fileName1 = dependency1.getFileName(); - String fileName2 = dependency2.getFileName(); - - //update to deal with archive analyzer, the starting name maybe the same - // as this is incorrectly looking at the starting path - final File one = new File(fileName1); - final File two = new File(fileName2); - final String oneParent = one.getParent(); - final String twoParent = two.getParent(); - if (oneParent != null) { - if (oneParent.equals(twoParent)) { - fileName1 = one.getName(); - fileName2 = two.getName(); - } else { - return false; - } - } else if (twoParent != null) { - return false; - } + final String fileName1 = dependency1.getActualFile().getName(); + final String fileName2 = dependency2.getActualFile().getName(); //version check final DependencyVersion version1 = DependencyVersionUtil.parseVersion(fileName1); @@ -267,9 +254,11 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal } if (cpeCount1 > 0 && cpeCount1 == cpeCount2) { for (Identifier i : dependency1.getIdentifiers()) { - matches |= dependency2.getIdentifiers().contains(i); - if (!matches) { - break; + if ("cpe".equals(i.getType())) { + matches |= dependency2.getIdentifiers().contains(i); + if (!matches) { + break; + } } } } @@ -318,8 +307,8 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal } /** - * This is likely a very broken attempt at determining if the 'left' dependency is the 'core' library in comparison - * to the 'right' library. + * This is likely a very broken attempt at determining if the 'left' dependency is the 'core' library in comparison to the + * 'right' library. * * @param left the dependency to test * @param right the dependency to test against @@ -338,6 +327,10 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal || !rightName.contains("core") && leftName.contains("core") || !rightName.contains("kernel") && leftName.contains("kernel")) { returnVal = true; +// } else if (leftName.matches(".*struts2\\-core.*") && rightName.matches(".*xwork\\-core.*")) { +// returnVal = true; +// } else if (rightName.matches(".*struts2\\-core.*") && leftName.matches(".*xwork\\-core.*")) { +// returnVal = false; } else { /* * considered splitting the names up and comparing the components, @@ -372,13 +365,12 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal } /** - * Determines if the jar is shaded and the created pom.xml identified the same CPE as the jar - if so, the pom.xml - * dependency should be removed. + * Determines if the jar is shaded and the created pom.xml identified the same CPE as the jar - if so, the pom.xml dependency + * should be removed. * * @param dependency a dependency to check * @param nextDependency another dependency to check - * @return true if on of the dependencies is a pom.xml and the identifiers between the two collections match; - * otherwise false + * @return true if on of the dependencies is a pom.xml and the identifiers between the two collections match; otherwise false */ private boolean isShadedJar(Dependency dependency, Dependency nextDependency) { final String mainName = dependency.getFileName().toLowerCase(); @@ -390,4 +382,43 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal } return false; } + + /** + * Determines which path is shortest; if path lengths are equal then we use compareTo of the string method to determine if the + * first path is smaller. + * + * @param left the first path to compare + * @param right the second path to compare + * @return true if the leftPath is the shortest; otherwise false + */ + protected boolean firstPathIsShortest(String left, String right) { + final String leftPath = left.replace('\\', '/'); + final String rightPath = right.replace('\\', '/'); + + final int leftCount = countChar(leftPath, '/'); + final int rightCount = countChar(rightPath, '/'); + if (leftCount == rightCount) { + return leftPath.compareTo(rightPath) <= 0; + } else { + return leftCount < rightCount; + } + } + + /** + * Counts the number of times the character is present in the string. + * + * @param string the string to count the characters in + * @param c the character to count + * @return the number of times the character is present in the string + */ + private int countChar(String string, char c) { + int count = 0; + final int max = string.length(); + for (int i = 0; i < max; i++) { + if (c == string.charAt(i)) { + count++; + } + } + return count; + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java index 3eb5d46c3..725e32ede 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java @@ -93,12 +93,17 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer { addFalseNegativeCPEs(dependency); } + /** + * Removes inaccurate matches on springframework CPEs. + * + * @param dependency the dependency to test for and remove known inaccurate CPE matches + */ private void removeBadSpringMatches(Dependency dependency) { String mustContain = null; for (Identifier i : dependency.getIdentifiers()) { if ("maven".contains(i.getType())) { if (i.getValue() != null && i.getValue().startsWith("org.springframework.")) { - int endPoint = i.getValue().indexOf(":", 19); + final int endPoint = i.getValue().indexOf(":", 19); if (endPoint >= 0) { mustContain = i.getValue().substring(19, endPoint).toLowerCase(); break; @@ -107,9 +112,9 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer { } } if (mustContain != null) { - Iterator itr = dependency.getIdentifiers().iterator(); + final Iterator itr = dependency.getIdentifiers().iterator(); while (itr.hasNext()) { - Identifier i = itr.next(); + final Identifier i = itr.next(); if ("cpe".contains(i.getType()) && i.getValue() != null && i.getValue().startsWith("cpe:/a:springsource:") @@ -117,7 +122,6 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer { itr.remove(); //dependency.getIdentifiers().remove(i); } - } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java index e4bc0a6b1..8910f704d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java @@ -73,7 +73,7 @@ public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer { public void analyze(Dependency dependency, Engine engine) throws AnalysisException { //strip any path information that may get added by ArchiveAnalyzer, etc. - final File f = new File(dependency.getFileName()); + final File f = dependency.getActualFile(); String fileName = f.getName(); //remove file extension diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileTypeAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileTypeAnalyzer.java index d22aad210..55f3c2f7b 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileTypeAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileTypeAnalyzer.java @@ -31,4 +31,9 @@ public interface FileTypeAnalyzer extends Analyzer { * @return whether or not the specified file extension is supported by this analyzer. */ boolean supportsExtension(String extension); + + /** + * Resets the analyzers state. + */ + void reset(); } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java index 123f51f83..8d6f866e4 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java @@ -19,6 +19,7 @@ package org.owasp.dependencycheck.analyzer; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Set; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; @@ -64,8 +65,8 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { // /** - * The HintAnalyzer uses knowledge about a dependency to add additional information to help in identification of - * identifiers or vulnerabilities. + * The HintAnalyzer uses knowledge about a dependency to add additional information to help in identification of identifiers + * or vulnerabilities. * * @param dependency The dependency being analyzed * @param engine The scanning engine @@ -84,24 +85,39 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { Confidence.HIGH); final Evidence springTest3 = new Evidence("Manifest", + "Implementation-Title", + "spring-core", + Confidence.HIGH); + + final Evidence springTest4 = new Evidence("Manifest", "Bundle-Vendor", "SpringSource", Confidence.HIGH); - Set evidence = dependency.getProductEvidence().getEvidence(); - if (evidence.contains(springTest1) || evidence.contains(springTest2)) { - dependency.getProductEvidence().addEvidence("hint analyzer", "product", "springsource_spring_framework", Confidence.HIGH); + final Evidence springTest5 = new Evidence("jar", + "package name", + "springframework", + Confidence.LOW); + + //springsource/vware problem + final Set product = dependency.getProductEvidence().getEvidence(); + final Set vendor = dependency.getVendorEvidence().getEvidence(); + + if (product.contains(springTest1) || product.contains(springTest2) || product.contains(springTest3) + || (dependency.getFileName().contains("spring") && (product.contains(springTest5) || vendor.contains(springTest5)))) { + dependency.getProductEvidence().addEvidence("hint analyzer", "product", "springsource spring framework", Confidence.HIGH); dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "SpringSource", Confidence.HIGH); dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "vmware", Confidence.HIGH); } - evidence = dependency.getVendorEvidence().getEvidence(); - if (evidence.contains(springTest3)) { + if (vendor.contains(springTest4)) { dependency.getProductEvidence().addEvidence("hint analyzer", "product", "springsource_spring_framework", Confidence.HIGH); dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "vmware", Confidence.HIGH); } + + //sun/oracle problem final Iterator itr = dependency.getVendorEvidence().iterator(); - final ArrayList newEntries = new ArrayList(); + final List newEntries = new ArrayList(); while (itr.hasNext()) { final Evidence e = itr.next(); if ("sun".equalsIgnoreCase(e.getValue(false))) { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java index 97e934840..5adf7968f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java @@ -46,13 +46,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipEntry; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.sax.SAXSource; import org.jsoup.Jsoup; import org.owasp.dependencycheck.Engine; @@ -60,7 +53,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.EvidenceCollection; -import org.owasp.dependencycheck.jaxb.pom.MavenNamespaceFilter; +import org.owasp.dependencycheck.jaxb.pom.PomUtils; import org.owasp.dependencycheck.jaxb.pom.generated.License; import org.owasp.dependencycheck.jaxb.pom.generated.Model; import org.owasp.dependencycheck.jaxb.pom.generated.Organization; @@ -68,9 +61,6 @@ import org.owasp.dependencycheck.utils.FileUtils; import org.owasp.dependencycheck.utils.NonClosingStream; import org.owasp.dependencycheck.utils.Settings; import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLFilter; -import org.xml.sax.XMLReader; /** * Used to load a JAR file and collect information that can be used to determine the associated CPE. @@ -158,24 +148,18 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { * A pattern to detect HTML within text. */ private static final Pattern HTML_DETECTION_PATTERN = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE); + /** - * The unmarshaller used to parse the pom.xml from a JAR file. + * The POM Utility for parsing POM files. */ - private Unmarshaller pomUnmarshaller; + private PomUtils pomUtils = null; // /** * Constructs a new JarAnalyzer. */ public JarAnalyzer() { - try { - //final JAXBContext jaxbContext = JAXBContext.newInstance("org.owasp.dependencycheck.jaxb.pom.generated"); - final JAXBContext jaxbContext = JAXBContext.newInstance(Model.class); - pomUnmarshaller = jaxbContext.createUnmarshaller(); - } catch (JAXBException ex) { //guess we will just have a null pointer exception later... - LOGGER.log(Level.SEVERE, "Unable to load parser. See the log for more details."); - LOGGER.log(Level.FINE, null, ex); - } + pomUtils = new PomUtils(); } // @@ -243,7 +227,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { @Override public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException { try { - final ArrayList classNames = collectClassNames(dependency); + final List classNames = collectClassNames(dependency); final String fileName = dependency.getFileName().toLowerCase(); if (classNames.isEmpty() && (fileName.endsWith("-sources.jar") @@ -262,8 +246,8 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Attempts to find a pom.xml within the JAR file. If found it extracts information and adds it to the evidence. - * This will attempt to interpolate the strings contained within the pom.properties if one exists. + * Attempts to find a pom.xml within the JAR file. If found it extracts information and adds it to the evidence. This will + * attempt to interpolate the strings contained within the pom.properties if one exists. * * @param dependency the dependency being analyzed * @param classes a collection of class name information @@ -271,7 +255,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { * @throws AnalysisException is thrown if there is an exception parsing the pom * @return whether or not evidence was added to the dependency */ - protected boolean analyzePOM(Dependency dependency, ArrayList classes, Engine engine) throws AnalysisException { + protected boolean analyzePOM(Dependency dependency, List classes, Engine engine) throws AnalysisException { boolean foundSomething = false; final JarFile jar; try { @@ -293,13 +277,23 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.log(Level.FINE, msg, ex); return false; } + File externalPom = null; if (pomEntries.isEmpty()) { - return false; + String pomPath = dependency.getActualFilePath(); + pomPath = pomPath.substring(0, pomPath.lastIndexOf('.')) + ".pom"; + externalPom = new File(pomPath); + if (externalPom.isFile()) { + pomEntries.add(pomPath); + } else { + return false; + } } for (String path : pomEntries) { Properties pomProperties = null; try { - pomProperties = retrievePomProperties(path, jar); + if (externalPom == null) { + pomProperties = retrievePomProperties(path, jar); + } } catch (IOException ex) { LOGGER.log(Level.FINEST, "ignore this, failed reading a non-existent pom.properties", ex); } @@ -313,11 +307,11 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { final String displayPath = String.format("%s%s%s", dependency.getFilePath(), File.separator, - path); //.replaceAll("[\\/]", File.separator)); + path); final String displayName = String.format("%s%s%s", dependency.getFileName(), File.separator, - path); //.replaceAll("[\\/]", File.separator)); + path); newDependency.setFileName(displayName); newDependency.setFilePath(displayPath); @@ -325,7 +319,11 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { engine.getDependencies().add(newDependency); Collections.sort(engine.getDependencies()); } else { - pom = retrievePom(path, jar); + if (externalPom == null) { + pom = retrievePom(path, jar); + } else { + pom = pomUtils.readPom(externalPom); + } foundSomething |= setPomEvidence(dependency, pom, pomProperties, classes); } } catch (AnalysisException ex) { @@ -410,7 +408,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos, BUFFER_SIZE); int count; - final byte data[] = new byte[BUFFER_SIZE]; + final byte[] data = new byte[BUFFER_SIZE]; while ((count = input.read(data, 0, BUFFER_SIZE)) != -1) { bos.write(data, 0, count); } @@ -432,7 +430,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { final InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); final InputSource xml = new InputSource(reader); final SAXSource source = new SAXSource(xml); - model = readPom(source); + model = pomUtils.readPom(source); } catch (FileNotFoundException ex) { final String msg = String.format("Unable to parse pom '%s' in jar '%s' (File Not Found)", path, jar.getName()); LOGGER.log(Level.WARNING, msg); @@ -502,7 +500,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { final InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); final InputSource xml = new InputSource(reader); final SAXSource source = new SAXSource(xml); - model = readPom(source); + model = pomUtils.readPom(source); } catch (SecurityException ex) { final String msg = String.format("Unable to parse pom '%s' in jar '%s'; invalid signature", path, jar.getName()); LOGGER.log(Level.WARNING, msg); @@ -523,49 +521,17 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { return model; } - /** - * Retrieves the specified POM from a jar file and converts it to a Model. - * - * @param source the SAXSource input stream to read the POM from - * @return returns the POM object - * @throws AnalysisException is thrown if there is an exception extracting or parsing the POM - * {@link org.owasp.dependencycheck.jaxb.pom.generated.Model} object - */ - private Model readPom(SAXSource source) throws AnalysisException { - Model model = null; - try { - final XMLFilter filter = new MavenNamespaceFilter(); - final SAXParserFactory spf = SAXParserFactory.newInstance(); - final SAXParser sp = spf.newSAXParser(); - final XMLReader xr = sp.getXMLReader(); - filter.setParent(xr); - final JAXBElement el = pomUnmarshaller.unmarshal(source, Model.class); - model = el.getValue(); - } catch (SecurityException ex) { - throw new AnalysisException(ex); - } catch (ParserConfigurationException ex) { - throw new AnalysisException(ex); - } catch (SAXException ex) { - throw new AnalysisException(ex); - } catch (JAXBException ex) { - throw new AnalysisException(ex); - } catch (Throwable ex) { - throw new AnalysisException(ex); - } - return model; - } - /** * Sets evidence from the pom on the supplied dependency. * * @param dependency the dependency to set data on * @param pom the information from the pom * @param pomProperties the pom properties file (null if none exists) - * @param classes a collection of ClassNameInformation - containing data about the fully qualified class names - * within the JAR file being analyzed + * @param classes a collection of ClassNameInformation - containing data about the fully qualified class names within the JAR + * file being analyzed * @return true if there was evidence within the pom that we could use; otherwise false */ - private boolean setPomEvidence(Dependency dependency, Model pom, Properties pomProperties, ArrayList classes) { + private boolean setPomEvidence(Dependency dependency, Model pom, Properties pomProperties, List classes) { boolean foundSomething = false; boolean addAsIdentifier = true; if (pom == null) { @@ -583,9 +549,6 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { final String originalGroupID = groupid; if (groupid != null && !groupid.isEmpty()) { - if (groupid.startsWith("org.") || groupid.startsWith("com.")) { - groupid = groupid.substring(4); - } foundSomething = true; dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGHEST); dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Confidence.LOW); @@ -689,17 +652,17 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Analyzes the path information of the classes contained within the JarAnalyzer to try and determine possible - * vendor or product names. If any are found they are stored in the packageVendor and packageProduct hashSets. + * Analyzes the path information of the classes contained within the JarAnalyzer to try and determine possible vendor or + * product names. If any are found they are stored in the packageVendor and packageProduct hashSets. * * @param classNames a list of class names * @param dependency a dependency to analyze * @param addPackagesAsEvidence a flag indicating whether or not package names should be added as evidence. */ - protected void analyzePackageNames(ArrayList classNames, + protected void analyzePackageNames(List classNames, Dependency dependency, boolean addPackagesAsEvidence) { - final HashMap vendorIdentifiers = new HashMap(); - final HashMap productIdentifiers = new HashMap(); + final Map vendorIdentifiers = new HashMap(); + final Map productIdentifiers = new HashMap(); analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers); final int classCount = classNames.size(); @@ -741,7 +704,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { * @return whether evidence was identified parsing the manifest * @throws IOException if there is an issue reading the JAR file */ - protected boolean parseManifest(Dependency dependency, ArrayList classInformation) throws IOException { + protected boolean parseManifest(Dependency dependency, List classInformation) throws IOException { boolean foundSomething = false; JarFile jar = null; try { @@ -898,18 +861,17 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Adds a description to the given dependency. If the description contains one of the following strings beyond 100 - * characters, then the description used will be trimmed to that position: + * Adds a description to the given dependency. If the description contains one of the following strings beyond 100 characters, + * then the description used will be trimmed to that position: *
  • "such as"
  • "like "
  • "will use "
  • "* uses "
* * @param dependency a dependency * @param description the description * @param source the source of the evidence * @param key the "name" of the evidence - * @return if the description is trimmed, the trimmed version is returned; otherwise the original description is - * returned + * @return if the description is trimmed, the trimmed version is returned; otherwise the original description is returned */ - private String addDescription(Dependency dependency, String description, String source, String key) { + public static String addDescription(Dependency dependency, String description, String source, String key) { if (dependency.getDescription() == null) { dependency.setDescription(description); } @@ -1014,12 +976,11 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { /** *

- * A utility function that will interpolate strings based on values given in the properties file. It will also - * interpolate the strings contained within the properties file so that properties can reference other - * properties.

+ * A utility function that will interpolate strings based on values given in the properties file. It will also interpolate the + * strings contained within the properties file so that properties can reference other properties.

*

- * Note: if there is no property found the reference will be removed. In other words, if the interpolated - * string will be replaced with an empty string. + * Note: if there is no property found the reference will be removed. In other words, if the interpolated string will + * be replaced with an empty string. *

*

* Example:

@@ -1039,13 +1000,13 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { * @param properties a collection of properties that may be referenced within the text. * @return the interpolated text. */ - protected String interpolateString(String text, Properties properties) { - Properties props = properties; + public static String interpolateString(String text, Properties properties) { + final Properties props = properties; if (text == null) { return text; } if (props == null) { - props = new Properties(); + return text; } final int pos = text.indexOf("${"); @@ -1083,14 +1044,14 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Cycles through an enumeration of JarEntries, contained within the dependency, and returns a list of the class - * names. This does not include core Java package names (i.e. java.* or javax.*). + * Cycles through an enumeration of JarEntries, contained within the dependency, and returns a list of the class names. This + * does not include core Java package names (i.e. java.* or javax.*). * * @param dependency the dependency being analyzed * @return an list of fully qualified class names */ - private ArrayList collectClassNames(Dependency dependency) { - final ArrayList classNames = new ArrayList(); + private List collectClassNames(Dependency dependency) { + final List classNames = new ArrayList(); JarFile jar = null; try { jar = new JarFile(dependency.getActualFilePath()); @@ -1121,17 +1082,17 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Cycles through the list of class names and places the package levels 0-3 into the provided maps for vendor and - * product. This is helpful when analyzing vendor/product as many times this is included in the package name. + * Cycles through the list of class names and places the package levels 0-3 into the provided maps for vendor and product. + * This is helpful when analyzing vendor/product as many times this is included in the package name. * * @param classNames a list of class names * @param vendor HashMap of possible vendor names from package names (e.g. owasp) * @param product HashMap of possible product names from package names (e.g. dependencycheck) */ - private void analyzeFullyQualifiedClassNames(ArrayList classNames, - HashMap vendor, HashMap product) { + private void analyzeFullyQualifiedClassNames(List classNames, + Map vendor, Map product) { for (ClassNameInformation entry : classNames) { - final ArrayList list = entry.getPackageStructure(); + final List list = entry.getPackageStructure(); addEntry(vendor, list.get(0)); if (list.size() == 2) { @@ -1153,13 +1114,13 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Adds an entry to the specified collection and sets the Integer (e.g. the count) to 1. If the entry already exists - * in the collection then the Integer is incremented by 1. + * Adds an entry to the specified collection and sets the Integer (e.g. the count) to 1. If the entry already exists in the + * collection then the Integer is incremented by 1. * * @param collection a collection of strings and their occurrence count * @param key the key to add to the collection */ - private void addEntry(HashMap collection, String key) { + private void addEntry(Map collection, String key) { if (collection.containsKey(key)) { collection.put(key, collection.get(key) + 1); } else { @@ -1168,15 +1129,15 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Cycles through the collection of class name information to see if parts of the package names are contained in the - * provided value. If found, it will be added as the HIGHEST confidence evidence because we have more then one - * source corroborating the value. + * Cycles through the collection of class name information to see if parts of the package names are contained in the provided + * value. If found, it will be added as the HIGHEST confidence evidence because we have more then one source corroborating the + * value. * * @param classes a collection of class name information * @param value the value to check to see if it contains a package name * @param evidence the evidence collection to add new entries too */ - private void addMatchingValues(ArrayList classes, String value, EvidenceCollection evidence) { + private void addMatchingValues(List classes, String value, EvidenceCollection evidence) { if (value == null || value.isEmpty() || classes == null || classes.isEmpty()) { return; } @@ -1211,7 +1172,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { * @param pomProperties the properties, used for string interpolation * @param dependency the dependency to add license information too */ - private void extractLicense(Model pom, Properties pomProperties, Dependency dependency) { + public static void extractLicense(Model pom, Properties pomProperties, Dependency dependency) { //license if (pom.getLicenses() != null) { String license = null; @@ -1252,9 +1213,9 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { /** *

- * Stores information about a given class name. This class will keep the fully qualified class name and a list - * of the important parts of the package structure. Up to the first four levels of the package structure are - * stored, excluding a leading "org" or "com". Example:

+ * Stores information about a given class name. This class will keep the fully qualified class name and a list of the + * important parts of the package structure. Up to the first four levels of the package structure are stored, excluding a + * leading "org" or "com". Example:

* ClassNameInformation obj = new ClassNameInformation("org.owasp.dependencycheck.analyzer.JarAnalyzer"); * System.out.println(obj.getName()); * for (String p : obj.getPackageStructure()) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java index dbee6a5d7..7d5650db6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java @@ -17,6 +17,7 @@ */ package org.owasp.dependencycheck.analyzer; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; @@ -24,13 +25,18 @@ import java.net.URL; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import org.apache.commons.io.FileUtils; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.data.nexus.MavenArtifact; import org.owasp.dependencycheck.data.nexus.NexusSearch; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; -import org.owasp.dependencycheck.dependency.Identifier; +import org.owasp.dependencycheck.dependency.Evidence; +import org.owasp.dependencycheck.jaxb.pom.PomUtils; +import org.owasp.dependencycheck.utils.InvalidSettingException; +import org.owasp.dependencycheck.utils.DownloadFailedException; +import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.Settings; /** @@ -49,6 +55,11 @@ import org.owasp.dependencycheck.utils.Settings; */ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { + /** + * The default URL - this will be used by the CentralAnalyzer to determine whether to enable this. + */ + public static final String DEFAULT_URL = "https://repository.sonatype.org/service/local/"; + /** * The logger. */ @@ -74,6 +85,51 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { */ private NexusSearch searcher; + /** + * Field indicating if the analyzer is enabled. + */ + private final boolean enabled = checkEnabled(); + /** + * Field for doing POM work + */ + private final PomUtils pomUtil = new PomUtils(); + + /** + * Determines if this analyzer is enabled + * + * @return true if the analyzer is enabled; otherwise false + */ + private boolean checkEnabled() { + /* Enable this analyzer ONLY if the Nexus URL has been set to something + other than the default one (if it's the default one, we'll use the + central one) and it's enabled by the user. + */ + boolean retval = false; + try { + if ((!DEFAULT_URL.equals(Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL))) + && Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED)) { + LOGGER.info("Enabling Nexus analyzer"); + retval = true; + } else { + LOGGER.fine("Nexus analyzer disabled, using Central instead"); + } + } catch (InvalidSettingException ise) { + LOGGER.warning("Invalid setting. Disabling Nexus analyzer"); + } + + return retval; + } + + /** + * Determine whether to enable this analyzer or not. + * + * @return whether the analyzer should be enabled + */ + @Override + public boolean isEnabled() { + return enabled; + } + /** * Initializes the analyzer once before any analysis is performed. * @@ -150,29 +206,42 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { */ @Override public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException { + if (!isEnabled()) { + return; + } try { final MavenArtifact ma = searcher.searchSha1(dependency.getSha1sum()); - if (ma.getGroupId() != null && !"".equals(ma.getGroupId())) { - dependency.getVendorEvidence().addEvidence("nexus", "groupid", ma.getGroupId(), Confidence.HIGH); - } - if (ma.getArtifactId() != null && !"".equals(ma.getArtifactId())) { - dependency.getProductEvidence().addEvidence("nexus", "artifactid", ma.getArtifactId(), Confidence.HIGH); - } - if (ma.getVersion() != null && !"".equals(ma.getVersion())) { - dependency.getVersionEvidence().addEvidence("nexus", "version", ma.getVersion(), Confidence.HIGH); - } - if (ma.getArtifactUrl() != null && !"".equals(ma.getArtifactUrl())) { - boolean found = false; - for (Identifier i : dependency.getIdentifiers()) { - if ("maven".equals(i.getType()) && i.getValue().equals(ma.toString())) { - found = true; - i.setConfidence(Confidence.HIGHEST); - i.setUrl(ma.getArtifactUrl()); - break; - } + dependency.addAsEvidence("nexus", ma, Confidence.HIGH); + boolean pomAnalyzed = false; + LOGGER.fine("POM URL " + ma.getPomUrl()); + for (Evidence e : dependency.getVendorEvidence()) { + if ("pom".equals(e.getSource())) { + pomAnalyzed = true; + break; } - if (!found) { - dependency.addIdentifier("maven", ma.toString(), ma.getArtifactUrl(), Confidence.HIGHEST); + } + if (!pomAnalyzed && ma.getPomUrl() != null) { + File pomFile = null; + try { + final File baseDir = Settings.getTempDirectory(); + pomFile = File.createTempFile("pom", ".xml", baseDir); + if (!pomFile.delete()) { + final String msg = String.format("Unable to fetch pom.xml for %s from Nexus repository; " + + "this could result in undetected CPE/CVEs.", dependency.getFileName()); + LOGGER.warning(msg); + LOGGER.fine("Unable to delete temp file"); + } + LOGGER.fine(String.format("Downloading %s", ma.getPomUrl())); + Downloader.fetchFile(new URL(ma.getPomUrl()), pomFile); + pomUtil.analyzePOM(dependency, pomFile); + } catch (DownloadFailedException ex) { + final String msg = String.format("Unable to download pom.xml for %s from Nexus repository; " + + "this could result in undetected CPE/CVEs.", dependency.getFileName()); + LOGGER.warning(msg); + } finally { + if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) { + pomFile.deleteOnExit(); + } } } } catch (IllegalArgumentException iae) { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java new file mode 100644 index 000000000..5699cbeb2 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java @@ -0,0 +1,161 @@ +/* + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.data.central; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; +import org.owasp.dependencycheck.utils.Settings; +import org.owasp.dependencycheck.utils.URLConnectionFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +/** + * Class of methods to search Maven Central via Central. + * + * @author colezlaw + */ +public class CentralSearch { + + /** + * The URL for the Central service + */ + private final URL rootURL; + + /** + * Whether to use the Proxy when making requests + */ + private boolean useProxy; + + /** + * Used for logging. + */ + private static final Logger LOGGER = Logger.getLogger(CentralSearch.class.getName()); + + /** + * Creates a NexusSearch for the given repository URL. + * + * @param rootURL the URL of the repository on which searches should execute. Only parameters are added to this (so it should + * end in /select) + */ + public CentralSearch(URL rootURL) { + this.rootURL = rootURL; + if (null != Settings.getString(Settings.KEYS.PROXY_SERVER)) { + useProxy = true; + LOGGER.fine("Using proxy"); + } else { + useProxy = false; + LOGGER.fine("Not using proxy"); + } + } + + /** + * Searches the configured Central URL for the given sha1 hash. If the artifact is found, a MavenArtifact is + * populated with the GAV. + * + * @param sha1 the SHA-1 hash string for which to search + * @return the populated Maven GAV. + * @throws IOException if it's unable to connect to the specified repository or if the specified artifact is not found. + */ + public List searchSha1(String sha1) throws IOException { + if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) { + throw new IllegalArgumentException("Invalid SHA1 format"); + } + + final URL url = new URL(rootURL + String.format("?q=1:\"%s\"&wt=xml", sha1)); + + LOGGER.fine(String.format("Searching Central url %s", url.toString())); + + // Determine if we need to use a proxy. The rules: + // 1) If the proxy is set, AND the setting is set to true, use the proxy + // 2) Otherwise, don't use the proxy (either the proxy isn't configured, + // or proxy is specifically set to false) + final HttpURLConnection conn = URLConnectionFactory.createHttpURLConnection(url, useProxy); + + conn.setDoOutput(true); + + // JSON would be more elegant, but there's not currently a dependency + // on JSON, so don't want to add one just for this + conn.addRequestProperty("Accept", "application/xml"); + conn.connect(); + + if (conn.getResponseCode() == 200) { + boolean missing = false; + try { + final DocumentBuilder builder = DocumentBuilderFactory + .newInstance().newDocumentBuilder(); + final Document doc = builder.parse(conn.getInputStream()); + final XPath xpath = XPathFactory.newInstance().newXPath(); + final String numFound = xpath.evaluate("/response/result/@numFound", doc); + if ("0".equals(numFound)) { + missing = true; + } else { + final ArrayList result = new ArrayList(); + final NodeList docs = (NodeList) xpath.evaluate("/response/result/doc", doc, XPathConstants.NODESET); + for (int i = 0; i < docs.getLength(); i++) { + final String g = xpath.evaluate("./str[@name='g']", docs.item(i)); + LOGGER.finest(String.format("GroupId: %s", g)); + final String a = xpath.evaluate("./str[@name='a']", docs.item(i)); + LOGGER.finest(String.format("ArtifactId: %s", a)); + final String v = xpath.evaluate("./str[@name='v']", docs.item(i)); + final NodeList atts = (NodeList) xpath.evaluate("./arr[@name='ec']/str", docs.item(i), XPathConstants.NODESET); + boolean pomAvailable = false; + boolean jarAvailable = false; + for (int x = 0; x < atts.getLength(); x++) { + final String tmp = xpath.evaluate(".", atts.item(x)); + if (".pom".equals(tmp)) { + pomAvailable = true; + } else if (".jar".equals(tmp)) { + jarAvailable = true; + } + } + LOGGER.finest(String.format("Version: %s", v)); + result.add(new MavenArtifact(g, a, v, jarAvailable, pomAvailable)); + } + + return result; + } + } catch (Throwable e) { + // Anything else is jacked up XML stuff that we really can't recover + // from well + throw new IOException(e.getMessage(), e); + } + + if (missing) { + throw new FileNotFoundException("Artifact not found in Central"); + } + } else { + final String msg = String.format("Could not connect to Central received response code: %d %s", + conn.getResponseCode(), conn.getResponseMessage()); + LOGGER.fine(msg); + throw new IOException(msg); + } + + return null; + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/package-info.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/package-info.java new file mode 100644 index 000000000..9b51647d6 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.data.central + * + * + *

+ * Contains classes related to searching Maven Central.

+ *

+ * These are used to abstract Maven Central searching away from OWASP Dependency Check so they can be reused elsewhere.

+ * + * + */ +package org.owasp.dependencycheck.data.central; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java index fa35d5eb3..355d82506 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java @@ -48,12 +48,13 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.utils.Pair; /** - * An in memory lucene index that contains the vendor/product combinations from the CPE (application) identifiers within - * the NVD CVE data. + * An in memory lucene index that contains the vendor/product combinations from the CPE (application) identifiers within the NVD + * CVE data. * * @author Jeremy Long */ public final class CpeMemoryIndex { + /** * The logger. */ @@ -61,7 +62,7 @@ public final class CpeMemoryIndex { /** * singleton instance. */ - private static CpeMemoryIndex instance = new CpeMemoryIndex(); + private static final CpeMemoryIndex INSTANCE = new CpeMemoryIndex(); /** * private constructor for singleton. @@ -75,7 +76,7 @@ public final class CpeMemoryIndex { * @return the instance of the CpeMemoryIndex */ public static CpeMemoryIndex getInstance() { - return instance; + return INSTANCE; } /** * The in memory Lucene index. @@ -113,18 +114,20 @@ public final class CpeMemoryIndex { * @throws IndexException thrown if there is an error creating the index */ public void open(CveDB cve) throws IndexException { - if (!openState) { - index = new RAMDirectory(); - buildIndex(cve); - try { - indexReader = DirectoryReader.open(index); - } catch (IOException ex) { - throw new IndexException(ex); + synchronized (INSTANCE) { + if (!openState) { + index = new RAMDirectory(); + buildIndex(cve); + try { + indexReader = DirectoryReader.open(index); + } catch (IOException ex) { + throw new IndexException(ex); + } + indexSearcher = new IndexSearcher(indexReader); + searchingAnalyzer = createSearchingAnalyzer(); + queryParser = new QueryParser(LuceneUtils.CURRENT_VERSION, Fields.DOCUMENT_KEY, searchingAnalyzer); + openState = true; } - indexSearcher = new IndexSearcher(indexReader); - searchingAnalyzer = createSearchingAnalyzer(); - queryParser = new QueryParser(LuceneUtils.CURRENT_VERSION, Fields.DOCUMENT_KEY, searchingAnalyzer); - openState = true; } } /** @@ -160,7 +163,7 @@ public final class CpeMemoryIndex { */ @SuppressWarnings("unchecked") private Analyzer createSearchingAnalyzer() { - final Map fieldAnalyzers = new HashMap(); + final Map fieldAnalyzers = new HashMap(); fieldAnalyzers.put(Fields.DOCUMENT_KEY, new KeywordAnalyzer()); productSearchFieldAnalyzer = new SearchFieldAnalyzer(LuceneUtils.CURRENT_VERSION); vendorSearchFieldAnalyzer = new SearchFieldAnalyzer(LuceneUtils.CURRENT_VERSION); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cwe/CweDB.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cwe/CweDB.java index ce2410ec5..ca67107f3 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cwe/CweDB.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cwe/CweDB.java @@ -29,10 +29,12 @@ import java.util.logging.Logger; * @author Jeremy Long */ public final class CweDB { + /** * The Logger. */ private static final Logger LOGGER = Logger.getLogger(CweDB.class.getName()); + /** * Empty private constructor as this is a utility class. */ @@ -55,7 +57,9 @@ public final class CweDB { final String filePath = "data/cwe.hashmap.serialized"; final InputStream input = CweDB.class.getClassLoader().getResourceAsStream(filePath); oin = new ObjectInputStream(input); - return (HashMap) oin.readObject(); + @SuppressWarnings("unchecked") + final HashMap ret = (HashMap) oin.readObject(); + return ret; } catch (ClassNotFoundException ex) { LOGGER.log(Level.WARNING, "Unable to load CWE data. This should not be an issue."); LOGGER.log(Level.FINE, null, ex); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/AbstractTokenizingFilter.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/AbstractTokenizingFilter.java index a45b653fe..6d06d74c6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/AbstractTokenizingFilter.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/AbstractTokenizingFilter.java @@ -72,7 +72,7 @@ public abstract class AbstractTokenizingFilter extends TokenFilter { * @return whether or not a new term was added */ protected boolean addTerm() { - final boolean termAdded = tokens.size() > 0; + final boolean termAdded = !tokens.isEmpty(); if (termAdded) { final String term = tokens.pop(); clearAttributes(); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java index e1810e38f..700034c9d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java @@ -29,8 +29,8 @@ import org.apache.lucene.util.Version; /** *

- * A Lucene Analyzer that utilizes the WhitespaceTokenizer, WordDelimiterFilter, LowerCaseFilter, and StopFilter. The - * intended purpose of this Analyzer is to index the CPE fields vendor and product.

+ * A Lucene Analyzer that utilizes the WhitespaceTokenizer, WordDelimiterFilter, LowerCaseFilter, and StopFilter. The intended + * purpose of this Analyzer is to index the CPE fields vendor and product.

* * @author Jeremy Long */ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/LuceneUtils.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/LuceneUtils.java index 1b59283d8..cbe6f1d32 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/LuceneUtils.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/LuceneUtils.java @@ -17,6 +17,7 @@ */ package org.owasp.dependencycheck.data.lucene; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.lucene.util.Version; /** @@ -28,10 +29,10 @@ import org.apache.lucene.util.Version; public final class LuceneUtils { /** - * The current version of Lucene being used. Declaring this one place so an upgrade doesn't require hunting through - * the code base. + * The current version of Lucene being used. Declaring this one place so an upgrade doesn't require hunting through the code + * base. */ - public static final Version CURRENT_VERSION = Version.LUCENE_45; + public static final Version CURRENT_VERSION = Version.LUCENE_47; /** * Private constructor as this is a utility class. @@ -46,7 +47,7 @@ public final class LuceneUtils { * @param text the data to be escaped */ @SuppressWarnings("fallthrough") - @edu.umd.cs.findbugs.annotations.SuppressWarnings( + @SuppressFBWarnings( value = "SF_SWITCH_NO_DEFAULT", justification = "The switch below does have a default.") public static void appendEscapedLuceneQuery(StringBuilder buf, diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchFieldAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchFieldAnalyzer.java index 7b40e08ed..634287f5f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchFieldAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchFieldAnalyzer.java @@ -39,8 +39,7 @@ public class SearchFieldAnalyzer extends Analyzer { */ private final Version version; /** - * A local reference to the TokenPairConcatenatingFilter so that we can clear any left over state if this analyzer - * is re-used. + * A local reference to the TokenPairConcatenatingFilter so that we can clear any left over state if this analyzer is re-used. */ private TokenPairConcatenatingFilter concatenatingFilter; @@ -85,8 +84,7 @@ public class SearchFieldAnalyzer extends Analyzer { /** *

- * Resets the analyzer and clears any internal state data that may have been left-over from previous uses of the - * analyzer.

+ * Resets the analyzer and clears any internal state data that may have been left-over from previous uses of the analyzer.

*

* If this analyzer is re-used this method must be called between uses.

*/ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchVersionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchVersionAnalyzer.java deleted file mode 100644 index 995a6d751..000000000 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/SearchVersionAnalyzer.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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) 2012 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.data.lucene; - -import java.io.Reader; -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.Tokenizer; -import org.apache.lucene.analysis.core.LowerCaseFilter; -import org.apache.lucene.analysis.core.WhitespaceTokenizer; -import org.apache.lucene.util.Version; - -/** - * SearchVersionAnalyzer is a Lucene Analyzer used to analyze version information. - * - * @author Jeremy Long - * @deprecated version information is no longer stored in lucene - */ -@Deprecated -public class SearchVersionAnalyzer extends Analyzer { - //TODO consider implementing payloads/custom attributes... - // use custom attributes for major, minor, x, x, x, rcx - // these can then be used to weight the score for searches on the version. - // see http://lucene.apache.org/core/3_6_1/api/core/org/apache/lucene/analysis/package-summary.html#package_description - // look at this article to implement - // http://www.codewrecks.com/blog/index.php/2012/08/25/index-your-blog-using-tags-and-lucene-net/ - - /** - * The Lucene Version used. - */ - private final Version version; - - /** - * Creates a new SearchVersionAnalyzer. - * - * @param version the Lucene version - */ - public SearchVersionAnalyzer(Version version) { - this.version = version; - } - - /** - * Creates the TokenStreamComponents - * - * @param fieldName the field name being analyzed - * @param reader the reader containing the input - * @return the TokenStreamComponents - */ - @Override - protected TokenStreamComponents createComponents(String fieldName, Reader reader) { - final Tokenizer source = new WhitespaceTokenizer(version, reader); - TokenStream stream = source; - stream = new LowerCaseFilter(version, stream); - stream = new VersionTokenizingFilter(stream); - return new TokenStreamComponents(source, stream); - } -} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/TokenPairConcatenatingFilter.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/TokenPairConcatenatingFilter.java index 3a5c52a8a..69c9c0769 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/TokenPairConcatenatingFilter.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/TokenPairConcatenatingFilter.java @@ -92,7 +92,7 @@ public final class TokenPairConcatenatingFilter extends TokenFilter { //if we have a previousTerm - write it out as its own token concatenated // with the current word (if one is available). - if (previousWord != null && words.size() > 0) { + if (previousWord != null && !words.isEmpty()) { final String word = words.getFirst(); clearAttributes(); termAtt.append(previousWord).append(word); @@ -100,7 +100,7 @@ public final class TokenPairConcatenatingFilter extends TokenFilter { return true; } //if we have words, write it out as a single token - if (words.size() > 0) { + if (!words.isEmpty()) { final String word = words.removeFirst(); clearAttributes(); termAtt.append(word); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilter.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilter.java index e5f47221a..a02253123 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilter.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilter.java @@ -60,7 +60,7 @@ public final class UrlTokenizingFilter extends AbstractTokenizingFilter { public boolean incrementToken() throws IOException { final LinkedList tokens = getTokens(); final CharTermAttribute termAtt = getTermAtt(); - if (tokens.size() == 0 && input.incrementToken()) { + if (tokens.isEmpty() && input.incrementToken()) { final String text = new String(termAtt.buffer(), 0, termAtt.length()); if (UrlStringUtils.containsUrl(text)) { final String[] parts = text.split("\\s"); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/VersionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/VersionAnalyzer.java deleted file mode 100644 index b5510f480..000000000 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/VersionAnalyzer.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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) 2012 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.data.lucene; - -import java.io.Reader; -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.Tokenizer; -import org.apache.lucene.analysis.core.LowerCaseFilter; -import org.apache.lucene.analysis.core.WhitespaceTokenizer; -import org.apache.lucene.util.Version; - -/** - * VersionAnalyzer is a Lucene Analyzer used to analyze version information. - * - * @author Jeremy Long - * @deprecated version information is no longer stored in lucene - */ -@Deprecated -public class VersionAnalyzer extends Analyzer { - //TODO consider implementing payloads/custom attributes... - // use custom attributes for major, minor, x, x, x, rcx - // these can then be used to weight the score for searches on the version. - // see http://lucene.apache.org/core/3_6_1/api/core/org/apache/lucene/analysis/package-summary.html#package_description - // look at this article to implement - // http://www.codewrecks.com/blog/index.php/2012/08/25/index-your-blog-using-tags-and-lucene-net/ - - /** - * The Lucene Version used. - */ - private final Version version; - - /** - * Creates a new VersionAnalyzer. - * - * @param version the Lucene version - */ - public VersionAnalyzer(Version version) { - this.version = version; - } - - /** - * Creates the TokenStreamComponents - * - * @param fieldName the field name being analyzed - * @param reader the reader containing the input - * @return the TokenStreamComponents - */ - @Override - protected TokenStreamComponents createComponents(String fieldName, Reader reader) { - final Tokenizer source = new WhitespaceTokenizer(version, reader); - TokenStream stream = source; - stream = new LowerCaseFilter(version, stream); - return new TokenStreamComponents(source, stream); - } -} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/VersionTokenizingFilter.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/VersionTokenizingFilter.java deleted file mode 100644 index 3b4d8bf48..000000000 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/VersionTokenizingFilter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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) 2012 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.data.lucene; - -import java.io.IOException; -import java.util.LinkedList; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; - -/** - *

- * Takes a TokenStream and splits or adds tokens to correctly index version numbers.

- *

- * Example: "3.0.0.RELEASE" -> "3 3.0 3.0.0 RELEASE 3.0.0.RELEASE".

- * - * @author Jeremy Long - * @deprecated version information is no longer stored in lucene - */ -@Deprecated -public final class VersionTokenizingFilter extends AbstractTokenizingFilter { - - /** - * Constructs a new VersionTokenizingFilter. - * - * @param stream the TokenStream that this filter will process - */ - public VersionTokenizingFilter(TokenStream stream) { - super(stream); - } - - /** - * Increments the underlying TokenStream and sets CharTermAttributes to construct an expanded set of tokens by - * concatenating tokens with the previous token. - * - * @return whether or not we have hit the end of the TokenStream - * @throws IOException is thrown when an IOException occurs - */ - @Override - public boolean incrementToken() throws IOException { - final LinkedList tokens = getTokens(); - final CharTermAttribute termAtt = getTermAtt(); - if (tokens.size() == 0 && input.incrementToken()) { - final String version = new String(termAtt.buffer(), 0, termAtt.length()); - final String[] toAnalyze = version.split("[_-]"); - //ensure we analyze the whole string as one too - analyzeVersion(version); - for (String str : toAnalyze) { - analyzeVersion(str); - } - } - return addTerm(); - } - - /** - *

- * Analyzes the version and adds several copies of the version as different tokens. For example, the version 1.2.7 - * would create the tokens 1 1.2 1.2.7. This is useful in discovering the correct version - sometimes a maintenance - * or build number will throw off the version identification.

- * - *

- * expected format:&nbps;major.minor[.maintenance[.build]]

- * - * @param version the version to analyze - */ - private void analyzeVersion(String version) { - //todo should we also be splitting on dash or underscore? we would need - // to incorporate the dash or underscore back in... - final LinkedList tokens = getTokens(); - final String[] versionParts = version.split("\\."); - String dottedVersion = null; - for (String current : versionParts) { - if (!current.matches("^/d+$")) { - tokens.add(current); - } - if (dottedVersion == null) { - dottedVersion = current; - } else { - dottedVersion = dottedVersion + "." + current; - } - tokens.add(dottedVersion); - } - } -} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/MavenArtifact.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/MavenArtifact.java index 559f4f8bf..ad020c1f3 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/MavenArtifact.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/MavenArtifact.java @@ -24,6 +24,11 @@ package org.owasp.dependencycheck.data.nexus; */ public class MavenArtifact { + /** + * The base URL for download artifacts from Central. + */ + private static final String CENTRAL_CONTENT_URL = "http://search.maven.org/remotecontent?filepath="; + /** * The groupId */ @@ -43,6 +48,10 @@ public class MavenArtifact { * The artifact url. This may change depending on which Nexus server the search took place. */ private String artifactUrl; + /** + * The url to download the POM from. + */ + private String pomUrl; /** * Creates an empty MavenArtifact. @@ -58,9 +67,34 @@ public class MavenArtifact { * @param version the version */ public MavenArtifact(String groupId, String artifactId, String version) { - setGroupId(groupId); - setArtifactId(artifactId); - setVersion(version); + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + } + + /** + * Creates a MavenArtifact with the given attributes. + * + * @param groupId the groupId + * @param artifactId the artifactId + * @param version the version + * @param jarAvailable if the jar file is available from central + * @param pomAvailable if the pom file is available from central + */ + public MavenArtifact(String groupId, String artifactId, String version, boolean jarAvailable, boolean pomAvailable) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + if (jarAvailable) { + //org/springframework/spring-core/3.2.0.RELEASE/spring-core-3.2.0.RELEASE.pom + this.artifactUrl = this.CENTRAL_CONTENT_URL + groupId.replace('.', '/') + "/" + artifactId.replace('.', '/') + "/" + + version + "/" + artifactId + "-" + version + ".jar"; + } + if (pomAvailable) { + //org/springframework/spring-core/3.2.0.RELEASE/spring-core-3.2.0.RELEASE.pom + this.pomUrl = this.CENTRAL_CONTENT_URL + groupId.replace('.', '/') + "/" + artifactId.replace('.', '/') + "/" + + version + "/" + artifactId + "-" + version + ".pom"; + } } /** @@ -72,10 +106,10 @@ public class MavenArtifact { * @param url the artifactLink url */ public MavenArtifact(String groupId, String artifactId, String version, String url) { - setGroupId(groupId); - setArtifactId(artifactId); - setVersion(version); - setArtifactUrl(url); + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + this.artifactUrl = url; } /** @@ -159,6 +193,25 @@ public class MavenArtifact { public String getArtifactUrl() { return artifactUrl; } + + /** + * Get the value of pomUrl. + * + * @return the value of pomUrl + */ + public String getPomUrl() { + return pomUrl; + } + + /** + * Set the value of pomUrl. + * + * @param pomUrl new value of pomUrl + */ + public void setPomUrl(String pomUrl) { + this.pomUrl = pomUrl; + } + } // vim: cc=120:sw=4:ts=4:sts=4 diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java index ec406a916..a1a66ab25 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java @@ -40,26 +40,32 @@ import org.w3c.dom.Document; public class NexusSearch { /** - * The root URL for the Nexus repository service + * The root URL for the Nexus repository service. */ private final URL rootURL; /** - * Whether to use the Proxy when making requests + * Whether to use the Proxy when making requests. */ private boolean useProxy; - + /** + * The username to use if the Nexus requires authentication. + */ + private String userName = null; + /** + * The password to use if the Nexus requires authentication. + */ + private char[] password; /** * Used for logging. */ - private static final Logger LOGGER = Logger.getLogger(NexusSearch.class - .getName()); + private static final Logger LOGGER = Logger.getLogger(NexusSearch.class.getName()); /** * Creates a NexusSearch for the given repository URL. * - * @param rootURL the root URL of the repository on which searches should execute. full URL's are calculated - * relative to this URL, so it should end with a / + * @param rootURL the root URL of the repository on which searches should execute. full URL's are calculated relative to this + * URL, so it should end with a / */ public NexusSearch(URL rootURL) { this.rootURL = rootURL; @@ -78,13 +84,12 @@ public class NexusSearch { } /** - * Searches the configured Nexus repository for the given sha1 hash. If the artifact is found, a - * MavenArtifact is populated with the coordinate information. + * Searches the configured Nexus repository for the given sha1 hash. If the artifact is found, a MavenArtifact is + * populated with the coordinate information. * * @param sha1 The SHA-1 hash string for which to search * @return the populated Maven coordinates - * @throws IOException if it's unable to connect to the specified repository or if the specified artifact is not - * found. + * @throws IOException if it's unable to connect to the specified repository or if the specified artifact is not found. */ public MavenArtifact searchSha1(String sha1) throws IOException { if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) { @@ -99,10 +104,9 @@ public class NexusSearch { // Determine if we need to use a proxy. The rules: // 1) If the proxy is set, AND the setting is set to true, use the proxy // 2) Otherwise, don't use the proxy (either the proxy isn't configured, - // or proxy is specifically - // set to false - final HttpURLConnection conn = URLConnectionFactory.createHttpURLConnection(url, useProxy); - + // or proxy is specifically set to false + HttpURLConnection conn; + conn = URLConnectionFactory.createHttpURLConnection(url, useProxy); conn.setDoOutput(true); // JSON would be more elegant, but there's not currently a dependency @@ -131,7 +135,18 @@ public class NexusSearch { .evaluate( "/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink", doc); - return new MavenArtifact(groupId, artifactId, version, link); + final String pomLink = xpath + .evaluate( + "/org.sonatype.nexus.rest.model.NexusArtifact/pomLink", + doc); + final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version); + if (link != null && !"".equals(link)) { + ma.setArtifactUrl(link); + } + if (pomLink != null && !"".equals(pomLink)) { + ma.setPomUrl(pomLink); + } + return ma; } catch (Throwable e) { // Anything else is jacked-up XML stuff that we really can't recover // from well @@ -153,8 +168,10 @@ public class NexusSearch { * @return whether the repository is listening and returns the /status URL correctly */ public boolean preflightRequest() { + HttpURLConnection conn; try { - final HttpURLConnection conn = URLConnectionFactory.createHttpURLConnection(new URL(rootURL, "status"), useProxy); + URL url = new URL(rootURL, "status"); + conn = URLConnectionFactory.createHttpURLConnection(url, useProxy); conn.addRequestProperty("Accept", "application/xml"); conn.connect(); if (conn.getResponseCode() != 200) { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java index da6b2ae58..44998262f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java @@ -17,6 +17,7 @@ */ package org.owasp.dependencycheck.data.nvdcve; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.sql.Connection; import java.sql.PreparedStatement; @@ -24,8 +25,10 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; @@ -39,6 +42,7 @@ import org.owasp.dependencycheck.utils.DBUtils; import org.owasp.dependencycheck.utils.DependencyVersion; import org.owasp.dependencycheck.utils.DependencyVersionUtil; import org.owasp.dependencycheck.utils.Pair; +import org.owasp.dependencycheck.utils.Settings; /** * The database holding information about the NVD CVE data. @@ -57,8 +61,8 @@ public class CveDB { private Connection conn; /** - * Creates a new CveDB object and opens the database connection. Note, the connection must be closed by the caller - * by calling the close method. + * Creates a new CveDB object and opens the database connection. Note, the connection must be closed by the caller by calling + * the close method. * * @throws DatabaseException thrown if there is an exception opening the database. */ @@ -87,7 +91,9 @@ public class CveDB { * @throws DatabaseException thrown if there is an error opening the database connection */ public final void open() throws DatabaseException { - conn = ConnectionFactory.getConnection(); + if (!isOpen()) { + conn = ConnectionFactory.getConnection(); + } } /** @@ -170,8 +176,8 @@ public class CveDB { */ private static final String DELETE_VULNERABILITY = "DELETE FROM vulnerability WHERE id = ?"; /** - * SQL Statement to cleanup orphan entries. Yes, the db schema could be a little tighter, but what we have works - * well to keep the data file size down a bit. + * SQL Statement to cleanup orphan entries. Yes, the db schema could be a little tighter, but what we have works well to keep + * the data file size down a bit. */ private static final String CLEANUP_ORPHANS = "DELETE FROM CpeEntry WHERE id not in (SELECT CPEEntryId FROM Software); "; /** @@ -208,7 +214,8 @@ public class CveDB { private static final String SELECT_CVE_FROM_SOFTWARE = "SELECT cve, cpe, previousVersion " + "FROM software INNER JOIN vulnerability ON vulnerability.id = software.cveId " + "INNER JOIN cpeEntry ON cpeEntry.id = software.cpeEntryId " - + "WHERE vendor = ? AND product = ?"; + + "WHERE vendor = ? AND product = ? " + + "ORDER BY cve, cpe"; //, previousVersion //unfortunately, the version info is too complicated to do in a select. Need to filter this afterwards // + " AND (version = '-' OR previousVersion IS NOT NULL OR version=?)"; // @@ -266,8 +273,8 @@ public class CveDB { //
/** - * Searches the CPE entries in the database and retrieves all entries for a given vendor and product combination. - * The returned list will include all versions of the product that are registered in the NVD CVE data. + * Searches the CPE entries in the database and retrieves all entries for a given vendor and product combination. The returned + * list will include all versions of the product that are registered in the NVD CVE data. * * @param vendor the identified vendor name of the dependency being analyzed * @param product the identified name of the product of the dependency being analyzed @@ -306,14 +313,14 @@ public class CveDB { * @throws DatabaseException thrown when there is an error retrieving the data from the DB */ public Set> getVendorProductList() throws DatabaseException { - final HashSet data = new HashSet>(); + final Set> data = new HashSet>(); ResultSet rs = null; PreparedStatement ps = null; try { ps = getConnection().prepareStatement(SELECT_VENDOR_PRODUCT_LIST); rs = ps.executeQuery(); while (rs.next()) { - data.add(new Pair(rs.getString(1), rs.getString(2))); + data.add(new Pair(rs.getString(1), rs.getString(2))); } } catch (SQLException ex) { final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details."; @@ -452,30 +459,41 @@ public class CveDB { final List vulnerabilities = new ArrayList(); PreparedStatement ps; - final HashSet cveEntries = new HashSet(); try { ps = getConnection().prepareStatement(SELECT_CVE_FROM_SOFTWARE); ps.setString(1, cpe.getVendor()); ps.setString(2, cpe.getProduct()); rs = ps.executeQuery(); + String currentCVE = ""; + + final Map vulnSoftware = new HashMap(); while (rs.next()) { final String cveId = rs.getString(1); + if (!currentCVE.equals(cveId)) { //check for match and add + final Entry matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion); + if (matchedCPE != null) { + final Vulnerability v = getVulnerability(currentCVE); + v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null); + vulnerabilities.add(v); + } + vulnSoftware.clear(); + currentCVE = cveId; + } + final String cpeId = rs.getString(2); final String previous = rs.getString(3); - if (!cveEntries.contains(cveId) && isAffected(cpe.getVendor(), cpe.getProduct(), detectedVersion, cpeId, previous)) { - cveEntries.add(cveId); - final Vulnerability v = getVulnerability(cveId); - v.setMatchedCPE(cpeId, previous); - vulnerabilities.add(v); - } + final Boolean p = previous != null && !previous.isEmpty(); + vulnSoftware.put(cpeId, p); + } + //remember to process the last set of CVE/CPE entries + final Entry matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion); + if (matchedCPE != null) { + final Vulnerability v = getVulnerability(currentCVE); + v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null); + vulnerabilities.add(v); } DBUtils.closeResultSet(rs); DBUtils.closeStatement(ps); -// for (String cve : cveEntries) { -// final Vulnerability v = getVulnerability(cve); -// vulnerabilities.add(v); -// } - } catch (SQLException ex) { throw new DatabaseException("Exception retrieving vulnerability for " + cpeStr, ex); } finally { @@ -701,8 +719,45 @@ public class CveDB { } /** - * It is possible that orphaned rows may be generated during database updates. This should be called after all - * updates have been completed to ensure orphan entries are removed. + * Checks to see if data exists so that analysis can be performed. + * + * @return true if data exists; otherwise false + */ + public boolean dataExists() { + Statement cs = null; + ResultSet rs = null; + try { + cs = conn.createStatement(); + rs = cs.executeQuery("SELECT COUNT(*) records FROM cpeEntry"); + if (rs.next()) { + if (rs.getInt(1) > 0) { + return true; + } + } + } catch (SQLException ex) { + String dd; + try { + dd = Settings.getDataDirectory().getAbsolutePath(); + } catch (IOException ex1) { + dd = Settings.getString(Settings.KEYS.DATA_DIRECTORY); + } + final String msg = String.format("Unable to access the local database.%n%nEnsure that '%s' is a writable directory. " + + "If the problem persist try deleting the files in '%s' and running %s again. If the problem continues, please " + + "create a log file (see documentation at http://jeremylong.github.io/DependencyCheck/) and open a ticket at " + + "https://github.com/jeremylong/DependencyCheck/issues and include the log file.%n%n", + dd, dd, Settings.getString(Settings.KEYS.APPLICATION_VAME)); + LOGGER.log(Level.SEVERE, msg); + LOGGER.log(Level.FINE, "", ex); + } finally { + DBUtils.closeResultSet(rs); + DBUtils.closeStatement(cs); + } + return false; + } + + /** + * It is possible that orphaned rows may be generated during database updates. This should be called after all updates have + * been completed to ensure orphan entries are removed. */ public void cleanupDatabase() { PreparedStatement ps = null; @@ -721,46 +776,80 @@ public class CveDB { } /** - * Determines if the given identifiedVersion is affected by the given cpeId and previous version flag. A non-null, - * non-empty string passed to the previous version argument indicates that all previous versions are affected. + * Determines if the given identifiedVersion is affected by the given cpeId and previous version flag. A non-null, non-empty + * string passed to the previous version argument indicates that all previous versions are affected. * * @param vendor the vendor of the dependency being analyzed * @param product the product name of the dependency being analyzed + * @param vulnerableSoftware a map of the vulnerable software with a boolean indicating if all previous versions are affected * @param identifiedVersion the identified version of the dependency being analyzed - * @param cpeId the cpe identifier of software that has a known vulnerability - * @param previous a flag indicating if previous versions of the product are vulnerable * @return true if the identified version is affected, otherwise false */ - private boolean isAffected(String vendor, String product, DependencyVersion identifiedVersion, String cpeId, String previous) { - boolean affected = false; - final boolean isStruts = "apache".equals(vendor) && "struts".equals(product); - final DependencyVersion v = parseDependencyVersion(cpeId); - final boolean prevAffected = previous != null && !previous.isEmpty(); - if (v == null || "-".equals(v.toString())) { //all versions - affected = true; - } else if (identifiedVersion == null || "-".equals(identifiedVersion.toString())) { - if (prevAffected) { - affected = true; + Entry getMatchingSoftware(Map vulnerableSoftware, String vendor, String product, + DependencyVersion identifiedVersion) { + + final boolean isVersionTwoADifferentProduct = "apache".equals(vendor) && "struts".equals(product); + + final Set majorVersionsAffectingAllPrevious = new HashSet(); + final boolean matchesAnyPrevious = identifiedVersion == null || "-".equals(identifiedVersion.toString()); + String majorVersionMatch = null; + for (Entry entry : vulnerableSoftware.entrySet()) { + final DependencyVersion v = parseDependencyVersion(entry.getKey()); + if (v == null || "-".equals(v.toString())) { //all versions + return entry; } - } else if (identifiedVersion.equals(v) || (prevAffected && identifiedVersion.compareTo(v) < 0)) { - if (isStruts) { //struts 2 vulns don't affect struts 1 - if (identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0))) { - affected = true; + if (entry.getValue()) { + if (matchesAnyPrevious) { + return entry; } - } else { - affected = true; + if (identifiedVersion != null && identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0))) { + majorVersionMatch = v.getVersionParts().get(0); + } + majorVersionsAffectingAllPrevious.add(v.getVersionParts().get(0)); } } - /* - * TODO consider utilizing the matchThreeVersion method to get additional results. However, this - * might also introduce false positives. - */ - return affected; + if (matchesAnyPrevious) { + return null; + } + + final boolean canSkipVersions = majorVersionMatch != null && majorVersionsAffectingAllPrevious.size() > 1; + //yes, we are iterating over this twice. The first time we are skipping versions those that affect all versions + //then later we process those that affect all versions. This could be done with sorting... + for (Entry entry : vulnerableSoftware.entrySet()) { + if (!entry.getValue()) { + final DependencyVersion v = parseDependencyVersion(entry.getKey()); + //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this. + if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) { + continue; + } + //this can't dereference a null 'identifiedVersion' because if it was null we would have exited + //in the above loop or just after loop (if matchesAnyPrevious return null). + if (identifiedVersion.equals(v)) { + return entry; + } + } + } + for (Entry entry : vulnerableSoftware.entrySet()) { + if (entry.getValue()) { + final DependencyVersion v = parseDependencyVersion(entry.getKey()); + //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this. + if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) { + continue; + } + //this can't dereference a null 'identifiedVersion' because if it was null we would have exited + //in the above loop or just after loop (if matchesAnyPrevious return null). + if (entry.getValue() && identifiedVersion.compareTo(v) <= 0) { + if (!(isVersionTwoADifferentProduct && !identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0)))) { + return entry; + } + } + } + } + return null; } /** - * Parses the version (including revision) from a CPE identifier. If no version is identified then a '-' is - * returned. + * Parses the version (including revision) from a CPE identifier. If no version is identified then a '-' is returned. * * @param cpeStr a cpe identifier * @return a dependency version @@ -784,9 +873,9 @@ public class CveDB { */ private DependencyVersion parseDependencyVersion(VulnerableSoftware cpe) { DependencyVersion cpeVersion; - if (cpe.getVersion() != null && cpe.getVersion().length() > 0) { + if (cpe.getVersion() != null && !cpe.getVersion().isEmpty()) { String versionText; - if (cpe.getRevision() != null && cpe.getRevision().length() > 0) { + if (cpe.getRevision() != null && !cpe.getRevision().isEmpty()) { versionText = String.format("%s.%s", cpe.getVersion(), cpe.getRevision()); } else { versionText = cpe.getVersion(); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java index cadcb2ae6..8b90dd0fa 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java @@ -91,7 +91,7 @@ public class DatabaseProperties { } /** - * Writes a properties file containing the last updated date to the VULNERABLE_CPE directory. + * Saves the last updated information to the properties file. * * @param updatedValue the updated NVD CVE entry * @throws UpdateException is thrown if there is an update exception @@ -100,8 +100,19 @@ public class DatabaseProperties { if (updatedValue == null) { return; } - properties.put(LAST_UPDATED_BASE + updatedValue.getId(), String.valueOf(updatedValue.getTimestamp())); - cveDB.saveProperty(LAST_UPDATED_BASE + updatedValue.getId(), String.valueOf(updatedValue.getTimestamp())); + save(LAST_UPDATED_BASE + updatedValue.getId(), String.valueOf(updatedValue.getTimestamp())); + } + + /** + * Saves the key value pair to the properties store. + * + * @param key the property key + * @param value the property value + * @throws UpdateException is thrown if there is an update exception + */ + public void save(String key, String value) throws UpdateException { + properties.put(key, value); + cveDB.saveProperty(key, value); } /** @@ -142,8 +153,8 @@ public class DatabaseProperties { * * @return a map of the database meta data */ - public Map getMetaData() { - final TreeMap map = new TreeMap(); + public Map getMetaData() { + final Map map = new TreeMap(); for (Entry entry : properties.entrySet()) { final String key = (String) entry.getKey(); if (!"version".equals(key)) { @@ -156,10 +167,10 @@ public class DatabaseProperties { map.put(key, formatted); } catch (Throwable ex) { //deliberately being broad in this catch clause LOGGER.log(Level.FINE, "Unable to parse timestamp from DB", ex); - map.put(key, entry.getValue()); + map.put(key, (String) entry.getValue()); } } else { - map.put(key, entry.getValue()); + map.put(key, (String) entry.getValue()); } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DriverLoader.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DriverLoader.java index fbce2e8a8..127b43673 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DriverLoader.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DriverLoader.java @@ -27,6 +27,7 @@ import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -75,7 +76,7 @@ public final class DriverLoader { */ public static Driver load(String className, String pathToDriver) throws DriverLoadException { final URLClassLoader parent = (URLClassLoader) ClassLoader.getSystemClassLoader(); - final ArrayList urls = new ArrayList(); + final List urls = new ArrayList(); final String[] paths = pathToDriver.split(File.pathSeparator); for (String path : paths) { final File file = new File(path); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java new file mode 100644 index 000000000..035d4e49b --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java @@ -0,0 +1,214 @@ +/* + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.data.update; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.commons.io.IOUtils; +import org.owasp.dependencycheck.data.nvdcve.CveDB; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; +import org.owasp.dependencycheck.data.update.exception.UpdateException; +import org.owasp.dependencycheck.utils.DateUtil; +import org.owasp.dependencycheck.utils.DependencyVersion; +import org.owasp.dependencycheck.utils.Settings; +import org.owasp.dependencycheck.utils.URLConnectionFactory; +import org.owasp.dependencycheck.utils.URLConnectionFailureException; + +/** + * + * @author Jeremy Long + */ +public class EngineVersionCheck implements CachedWebDataSource { + + /** + * Static logger. + */ + private static final Logger LOGGER = Logger.getLogger(EngineVersionCheck.class.getName()); + /** + * The property key indicating when the last version check occurred. + */ + public static final String ENGINE_VERSION_CHECKED_ON = "VersionCheckOn"; + /** + * The property key indicating when the last version check occurred. + */ + public static final String CURRENT_ENGINE_RELEASE = "CurrentEngineRelease"; + /** + * Reference to the Cve Database. + */ + private CveDB cveDB = null; + + /** + * The version retrieved from the database properties or web to check against. + */ + private String updateToVersion; + + /** + * Getter for updateToVersion - only used for testing. Represents the version retrieved from the database. + * + * @return the version to test + */ + protected String getUpdateToVersion() { + return updateToVersion; + } + + /** + * Setter for updateToVersion - only used for testing. Represents the version retrieved from the database. + * + * @param version the version to test + */ + protected void setUpdateToVersion(String version) { + updateToVersion = version; + } + + @Override + public void update() throws UpdateException { + try { + openDatabase(); + LOGGER.fine("Begin Engine Version Check"); + final DatabaseProperties properties = cveDB.getDatabaseProperties(); + final long lastChecked = Long.parseLong(properties.getProperty(ENGINE_VERSION_CHECKED_ON, "0")); + final long now = (new Date()).getTime(); + updateToVersion = properties.getProperty(CURRENT_ENGINE_RELEASE, ""); + final String currentVersion = Settings.getString(Settings.KEYS.APPLICATION_VERSION, "0.0.0"); + LOGGER.fine("Last checked: " + lastChecked); + LOGGER.fine("Now: " + now); + LOGGER.fine("Current version: " + currentVersion); + final boolean updateNeeded = shouldUpdate(lastChecked, now, properties, currentVersion); + if (updateNeeded) { + final String msg = String.format("A new version of dependency-check is available. Consider updating to version %s.", + updateToVersion); + LOGGER.warning(msg); + } + } catch (DatabaseException ex) { + LOGGER.log(Level.FINE, "Database Exception opening databases to retrieve properties", ex); + throw new UpdateException("Error occured updating database properties."); + } finally { + closeDatabase(); + } + } + + /** + * Determines if a new version of the dependency-check engine has been released. + * + * @param lastChecked the epoch time of the last version check + * @param now the current epoch time + * @param properties the database properties object + * @param currentVersion the current version of dependency-check + * @return true if a newer version of the database has been released; otherwise false + * @throws UpdateException thrown if there is an error connecting to the github documentation site or accessing the + * local database. + */ + protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties, + String currentVersion) throws UpdateException { + //check every 30 days if we know there is an update, otherwise check every 7 days + int checkRange = 30; + if (updateToVersion.isEmpty()) { + checkRange = 7; + } + if (!DateUtil.withinDateRange(lastChecked, now, checkRange)) { + LOGGER.fine("Checking web for new version."); + final String currentRelease = getCurrentReleaseVersion(); + if (currentRelease != null) { + final DependencyVersion v = new DependencyVersion(currentRelease); + if (v.getVersionParts() != null && v.getVersionParts().size() >= 3) { + updateToVersion = v.toString(); + if (!currentRelease.equals(updateToVersion)) { + properties.save(CURRENT_ENGINE_RELEASE, updateToVersion); + } else { + properties.save(CURRENT_ENGINE_RELEASE, ""); + } + properties.save(ENGINE_VERSION_CHECKED_ON, Long.toString(now)); + } + } + LOGGER.log(Level.FINE, "Current Release: {0}", updateToVersion); + } + final DependencyVersion running = new DependencyVersion(currentVersion); + final DependencyVersion released = new DependencyVersion(updateToVersion); + if (running.compareTo(released) < 0) { + LOGGER.fine("Upgrade recommended"); + return true; + } + LOGGER.fine("Upgrade not needed"); + return false; + } + + /** + * Opens the CVE and CPE data stores. + * + * @throws DatabaseException thrown if a data store cannot be opened + */ + protected final void openDatabase() throws DatabaseException { + if (cveDB != null) { + return; + } + cveDB = new CveDB(); + cveDB.open(); + } + + /** + * Closes the CVE and CPE data stores. + */ + protected void closeDatabase() { + if (cveDB != null) { + try { + cveDB.close(); + } catch (Throwable ignore) { + LOGGER.log(Level.FINEST, "Error closing the cveDB", ignore); + } + } + } + + /** + * Retrieves the current released version number from the github documentation site. + * + * @return the current released version number + */ + protected String getCurrentReleaseVersion() { + HttpURLConnection conn = null; + try { + final String str = Settings.getString(Settings.KEYS.ENGINE_VERSION_CHECK_URL, "http://jeremylong.github.io/DependencyCheck/current.txt"); + final URL url = new URL(str); + conn = URLConnectionFactory.createHttpURLConnection(url); + conn.connect(); + if (conn.getResponseCode() != 200) { + return null; + } + final String releaseVersion = IOUtils.toString(conn.getInputStream(), "UTF-8"); + if (releaseVersion != null) { + return releaseVersion.trim(); + } + } catch (MalformedURLException ex) { + LOGGER.log(Level.FINE, "unable to retrieve current release version of dependency-check", ex); + } catch (URLConnectionFailureException ex) { + LOGGER.log(Level.FINE, "unable to retrieve current release version of dependency-check", ex); + } catch (IOException ex) { + LOGGER.log(Level.FINE, "unable to retrieve current release version of dependency-check", ex); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return null; + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/StandardUpdate.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/StandardUpdate.java index 89a7fc6bd..169f039ca 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/StandardUpdate.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/StandardUpdate.java @@ -36,6 +36,7 @@ import org.owasp.dependencycheck.data.update.exception.InvalidDataException; import org.owasp.dependencycheck.data.update.exception.UpdateException; import org.owasp.dependencycheck.data.update.task.DownloadTask; import org.owasp.dependencycheck.data.update.task.ProcessTask; +import org.owasp.dependencycheck.utils.DateUtil; import org.owasp.dependencycheck.utils.DownloadFailedException; import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; @@ -220,7 +221,7 @@ public class StandardUpdate { final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7); if (lastUpdated == updates.getTimeStamp(MODIFIED)) { updates.clear(); //we don't need to update anything. - } else if (withinRange(lastUpdated, now.getTime(), days)) { + } else if (DateUtil.withinDateRange(lastUpdated, now.getTime(), days)) { for (NvdCveInfo entry : updates) { if (MODIFIED.equals(entry.getId())) { entry.setNeedsUpdate(true); @@ -317,19 +318,4 @@ public class StandardUpdate { throw new UpdateException("Error updating the CPE/CVE data, please see the log file for more details."); } } - - /** - * Determines if the epoch date is within the range specified of the compareTo epoch time. This takes the - * (compareTo-date)/1000/60/60/24 to get the number of days. If the calculated days is less then the range the date - * is considered valid. - * - * @param date the date to be checked. - * @param compareTo the date to compare to. - * @param range the range in days to be considered valid. - * @return whether or not the date is within the range. - */ - protected boolean withinRange(long date, long compareTo, int range) { - final double differenceInDays = (compareTo - date) / 1000.0 / 60.0 / 60.0 / 24.0; - return differenceInDays < range; - } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/task/DownloadTask.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/task/DownloadTask.java index 5c3fba161..e15541312 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/task/DownloadTask.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/task/DownloadTask.java @@ -18,6 +18,9 @@ package org.owasp.dependencycheck.data.update.task; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.util.concurrent.Callable; @@ -25,6 +28,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; +import org.apache.commons.io.FileUtils; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.update.NvdCveInfo; import org.owasp.dependencycheck.data.update.exception.UpdateException; @@ -195,10 +200,18 @@ public class DownloadTask implements Callable> { LOGGER.log(Level.FINE, null, ex); return null; } + if (url1.toExternalForm().endsWith(".xml.gz")) { + extractGzip(first); + } + if (url2.toExternalForm().endsWith(".xml.gz")) { + extractGzip(second); + } msg = String.format("Download Complete for NVD CVE - %s", nvdCveInfo.getId()); LOGGER.log(Level.INFO, msg); - + if (this.processorService == null) { + return null; + } final ProcessTask task = new ProcessTask(cveDB, this, settings); return this.processorService.submit(task); @@ -237,4 +250,56 @@ public class DownloadTask implements Callable> { } } } + + /** + * Extracts the file contained in a gzip archive. The extracted file is placed in the exact same path as the file + * specified. + * + * @param file the archive file + * @throws FileNotFoundException thrown if the file does not exist + * @throws IOException thrown if there is an error extracting the file. + */ + private void extractGzip(File file) throws FileNotFoundException, IOException { + final String originalPath = file.getPath(); + final File gzip = new File(originalPath + ".gz"); + if (gzip.isFile() && !gzip.delete()) { + gzip.deleteOnExit(); + } + if (!file.renameTo(gzip)) { + throw new IOException("Unable to rename '" + file.getPath() + "'"); + } + final File newfile = new File(originalPath); + + final byte[] buffer = new byte[4096]; + + GZIPInputStream cin = null; + FileOutputStream out = null; + try { + cin = new GZIPInputStream(new FileInputStream(gzip)); + out = new FileOutputStream(newfile); + + int len; + while ((len = cin.read(buffer)) > 0) { + out.write(buffer, 0, len); + } + } finally { + if (cin != null) { + try { + cin.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "ignore", ex); + } + } + if (out != null) { + try { + out.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "ignore", ex); + } + } + if (gzip.isFile()) { + FileUtils.deleteQuietly(gzip); + } + } + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java index 9ce32d38d..bad622829 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java @@ -21,18 +21,22 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; import org.owasp.dependencycheck.utils.Checksum; import org.owasp.dependencycheck.utils.FileUtils; /** - * A program dependency. This object is one of the core components within DependencyCheck. It is used to collect - * information about the dependency in the form of evidence. The Evidence is then used to determine if there are any - * known, published, vulnerabilities associated with the program dependency. + * A program dependency. This object is one of the core components within DependencyCheck. It is used to collect information about + * the dependency in the form of evidence. The Evidence is then used to determine if there are any known, published, + * vulnerabilities associated with the program dependency. * * @author Jeremy Long */ @@ -120,8 +124,8 @@ public class Dependency implements Serializable, Comparable { } /** - * Returns the file name of the dependency with the backslash escaped for use in JavaScript. This is a complete hack - * as I could not get the replace to work in the template itself. + * Returns the file name of the dependency with the backslash escaped for use in JavaScript. This is a complete hack as I + * could not get the replace to work in the template itself. * * @return the file name of the dependency with the backslash escaped for use in JavaScript */ @@ -193,8 +197,7 @@ public class Dependency implements Serializable, Comparable { } /** - * Returns the file name to display in reports; if no display file name has been set it will default to the actual - * file name. + * Returns the file name to display in reports; if no display file name has been set it will default to the actual file name. * * @return the file name to display */ @@ -209,8 +212,8 @@ public class Dependency implements Serializable, Comparable { *

* Gets the file path of the dependency.

*

- * NOTE: This may not be the actual path of the file on disk. The actual path of the file on disk can be - * obtained via the getActualFilePath().

+ * NOTE: This may not be the actual path of the file on disk. The actual path of the file on disk can be obtained via + * the getActualFilePath().

* * @return the file path of the dependency */ @@ -316,6 +319,43 @@ public class Dependency implements Serializable, Comparable { this.identifiers.add(i); } + /** + * Adds the maven artifact as evidence. + * + * @param source The source of the evidence + * @param mavenArtifact The maven artifact + * @param confidence The confidence level of this evidence + */ + public void addAsEvidence(String source, MavenArtifact mavenArtifact, Confidence confidence) { + if (mavenArtifact.getGroupId() != null && !mavenArtifact.getGroupId().isEmpty()) { + this.getVendorEvidence().addEvidence(source, "groupid", mavenArtifact.getGroupId(), confidence); + } + if (mavenArtifact.getArtifactId() != null && !mavenArtifact.getArtifactId().isEmpty()) { + this.getProductEvidence().addEvidence(source, "artifactid", mavenArtifact.getArtifactId(), confidence); + } + if (mavenArtifact.getVersion() != null && !mavenArtifact.getVersion().isEmpty()) { + this.getVersionEvidence().addEvidence(source, "version", mavenArtifact.getVersion(), confidence); + } + if (mavenArtifact.getArtifactUrl() != null && !mavenArtifact.getArtifactUrl().isEmpty()) { + boolean found = false; + for (Identifier i : this.getIdentifiers()) { + if ("maven".equals(i.getType()) && i.getValue().equals(mavenArtifact.toString())) { + found = true; + i.setConfidence(Confidence.HIGHEST); + final String url = "http://search.maven.org/#search|ga|1|1%3A%22" + this.getSha1sum() + "%22"; + i.setUrl(url); + //i.setUrl(mavenArtifact.getArtifactUrl()); + LOGGER.fine(String.format("Already found identifier %s. Confidence set to highest", i.getValue())); + break; + } + } + if (!found) { + LOGGER.fine(String.format("Adding new maven identifier %s", mavenArtifact.toString())); + this.addIdentifier("maven", mavenArtifact.toString(), mavenArtifact.getArtifactUrl(), Confidence.HIGHEST); + } + } + } + /** * Adds an entry to the list of detected Identifiers for the dependency file. * @@ -324,6 +364,7 @@ public class Dependency implements Serializable, Comparable { public void addIdentifier(Identifier identifier) { this.identifiers.add(identifier); } + /** * A set of identifiers that have been suppressed. */ @@ -441,6 +482,7 @@ public class Dependency implements Serializable, Comparable { public EvidenceCollection getVersionEvidence() { return this.versionEvidence; } + /** * The description of the JAR file. */ @@ -463,6 +505,7 @@ public class Dependency implements Serializable, Comparable { public void setDescription(String description) { this.description = description; } + /** * The license that this dependency uses. */ @@ -485,6 +528,7 @@ public class Dependency implements Serializable, Comparable { public void setLicense(String license) { this.license = license; } + /** * A list of vulnerabilities for this dependency. */ @@ -540,6 +584,7 @@ public class Dependency implements Serializable, Comparable { public void addVulnerability(Vulnerability vulnerability) { this.vulnerabilities.add(vulnerability); } + /** * A collection of related dependencies. */ @@ -554,6 +599,47 @@ public class Dependency implements Serializable, Comparable { return relatedDependencies; } + /** + * A list of projects that reference this dependency. + */ + private Set projectReferences = new HashSet(); + + /** + * Get the value of projectReferences. + * + * @return the value of projectReferences + */ + public Set getProjectReferences() { + return projectReferences; + } + + /** + * Set the value of projectReferences. + * + * @param projectReferences new value of projectReferences + */ + public void setProjectReferences(Set projectReferences) { + this.projectReferences = projectReferences; + } + + /** + * Adds a project reference. + * + * @param projectReference a project reference + */ + public void addProjectReference(String projectReference) { + this.projectReferences.add(projectReference); + } + + /** + * Add a collection of project reference. + * + * @param projectReferences a set of project references + */ + public void addAllProjectReferences(Set projectReferences) { + this.projectReferences.addAll(projectReferences); + } + /** * Set the value of relatedDependencies. * @@ -569,7 +655,46 @@ public class Dependency implements Serializable, Comparable { * @param dependency a reference to the related dependency */ public void addRelatedDependency(Dependency dependency) { - relatedDependencies.add(dependency); + if (this == dependency) { + LOGGER.warning("Attempted to add a circular reference - please post the log file to issue #172 here " + + "https://github.com/jeremylong/DependencyCheck/issues/172 "); + LOGGER.log(Level.FINE, "this: {0}", this.toString()); + LOGGER.log(Level.FINE, "dependency: {0}", dependency.toString()); + } else { + relatedDependencies.add(dependency); + } + } + + /** + * A list of available versions. + */ + private List availableVersions = new ArrayList(); + + /** + * Get the value of availableVersions. + * + * @return the value of availableVersions + */ + public List getAvailableVersions() { + return availableVersions; + } + + /** + * Set the value of availableVersions. + * + * @param availableVersions new value of availableVersions + */ + public void setAvailableVersions(List availableVersions) { + this.availableVersions = availableVersions; + } + + /** + * Adds a version to the available version list. + * + * @param version the version to add to the list + */ + public void addAvailableVersion(String version) { + this.availableVersions.add(version); } /** @@ -579,7 +704,7 @@ public class Dependency implements Serializable, Comparable { * @return an integer representing the natural ordering */ public int compareTo(Dependency o) { - return this.getFileName().compareToIgnoreCase(o.getFileName()); + return this.getFilePath().compareToIgnoreCase(o.getFilePath()); } /** @@ -640,6 +765,15 @@ public class Dependency implements Serializable, Comparable { && (this.relatedDependencies == null || !this.relatedDependencies.equals(other.relatedDependencies))) { return false; } + if (this.projectReferences != other.projectReferences + && (this.projectReferences == null || !this.projectReferences.equals(other.projectReferences))) { + return false; + } + if (this.availableVersions != other.availableVersions + && (this.availableVersions == null || !this.availableVersions.equals(other.availableVersions))) { + return false; + } + return true; } @@ -665,6 +799,8 @@ public class Dependency implements Serializable, Comparable { hash = 47 * hash + (this.license != null ? this.license.hashCode() : 0); hash = 47 * hash + (this.vulnerabilities != null ? this.vulnerabilities.hashCode() : 0); hash = 47 * hash + (this.relatedDependencies != null ? this.relatedDependencies.hashCode() : 0); + hash = 47 * hash + (this.projectReferences != null ? this.projectReferences.hashCode() : 0); + hash = 47 * hash + (this.availableVersions != null ? this.availableVersions.hashCode() : 0); return hash; } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java index 3f64b2faa..edd0f9a65 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java @@ -25,6 +25,15 @@ import java.io.Serializable; */ public class Identifier implements Serializable, Comparable { + /** + * Default constructor. Should only be used for automatic class + * creation as is the case with many XML parsers (for the parsing + * of the Dependency-Check XML report). For all other use-cases, + * please use the non-default constructors. + */ + public Identifier() { + } + /** * Constructs a new Identifier with the specified data. * diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/PomUtils.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/PomUtils.java new file mode 100644 index 000000000..ff4e9d732 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/PomUtils.java @@ -0,0 +1,226 @@ +/* + * 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) 2015 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.jaxb.pom; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.sax.SAXSource; + +import org.owasp.dependencycheck.analyzer.JarAnalyzer; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.jaxb.pom.generated.Model; +import org.owasp.dependencycheck.jaxb.pom.generated.Organization; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLFilter; +import org.xml.sax.XMLReader; + +/** + * + * @author jeremy + */ +public class PomUtils { + + /** + * The logger. + */ + private static final Logger LOGGER = Logger.getLogger(PomUtils.class.getName()); + + /** + * The unmarshaller used to parse the pom.xml from a JAR file. + */ + private Unmarshaller pomUnmarshaller; + + /** + * Constructs a new POM Utility. + */ + public PomUtils() { + try { + //final JAXBContext jaxbContext = JAXBContext.newInstance("org.owasp.dependencycheck.jaxb.pom.generated"); + final JAXBContext jaxbContext = JAXBContext.newInstance(Model.class); + pomUnmarshaller = jaxbContext.createUnmarshaller(); + } catch (JAXBException ex) { //guess we will just have a null pointer exception later... + LOGGER.log(Level.SEVERE, "Unable to load parser. See the log for more details."); + LOGGER.log(Level.FINE, null, ex); + } + } + + /** + * Reads in the specified POM and converts it to a Model. + * + * @param file the pom.xml file + * @return returns a + * @throws AnalysisException is thrown if there is an exception extracting or parsing the POM + * {@link org.owasp.dependencycheck.jaxb.pom.generated.Model} object + */ + public Model readPom(File file) throws AnalysisException { + Model model = null; + try { + final FileInputStream stream = new FileInputStream(file); + final InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); + final InputSource xml = new InputSource(reader); + final SAXSource source = new SAXSource(xml); + model = readPom(source); + } catch (SecurityException ex) { + final String msg = String.format("Unable to parse pom '%s'; invalid signature", file.getPath()); + LOGGER.log(Level.WARNING, msg); + LOGGER.log(Level.FINE, "", ex); + throw new AnalysisException(ex); + } catch (IOException ex) { + final String msg = String.format("Unable to parse pom '%s'(IO Exception)", file.getPath()); + LOGGER.log(Level.WARNING, msg); + LOGGER.log(Level.FINE, "", ex); + throw new AnalysisException(ex); + } catch (Throwable ex) { + final String msg = String.format("Unexpected error during parsing of the pom '%s'", file.getPath()); + LOGGER.log(Level.WARNING, msg); + LOGGER.log(Level.FINE, "", ex); + throw new AnalysisException(ex); + } + return model; + } + + /** + * Retrieves the specified POM from a jar file and converts it to a Model. + * + * @param source the SAXSource input stream to read the POM from + * @return returns the POM object + * @throws AnalysisException is thrown if there is an exception extracting or parsing the POM + * {@link org.owasp.dependencycheck.jaxb.pom.generated.Model} object + */ + public Model readPom(SAXSource source) throws AnalysisException { + Model model = null; + try { + final XMLFilter filter = new MavenNamespaceFilter(); + final SAXParserFactory spf = SAXParserFactory.newInstance(); + final SAXParser sp = spf.newSAXParser(); + final XMLReader xr = sp.getXMLReader(); + filter.setParent(xr); + final JAXBElement el = pomUnmarshaller.unmarshal(source, Model.class); + model = el.getValue(); + } catch (SecurityException ex) { + throw new AnalysisException(ex); + } catch (ParserConfigurationException ex) { + throw new AnalysisException(ex); + } catch (SAXException ex) { + throw new AnalysisException(ex); + } catch (JAXBException ex) { + throw new AnalysisException(ex); + } catch (Throwable ex) { + throw new AnalysisException(ex); + } + return model; + } + + /** + * Reads in the pom file and adds elements as evidence to the given dependency. + * + * @param dependency the dependency being analyzed + * @param pomFile the pom file to read + * @throws AnalysisException is thrown if there is an exception parsing the pom + */ + public void analyzePOM(Dependency dependency, File pomFile) throws AnalysisException { + final Model pom = this.readPom(pomFile); + + String groupid = pom.getGroupId(); + String parentGroupId = null; + + if (pom.getParent() != null) { + parentGroupId = pom.getParent().getGroupId(); + if ((groupid == null || groupid.isEmpty()) && parentGroupId != null && !parentGroupId.isEmpty()) { + groupid = parentGroupId; + } + } + if (groupid != null && !groupid.isEmpty()) { + dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGHEST); + dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Confidence.LOW); + if (parentGroupId != null && !parentGroupId.isEmpty() && !parentGroupId.equals(groupid)) { + dependency.getVendorEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.MEDIUM); + dependency.getProductEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.LOW); + } + } + String artifactid = pom.getArtifactId(); + String parentArtifactId = null; + if (pom.getParent() != null) { + parentArtifactId = pom.getParent().getArtifactId(); + if ((artifactid == null || artifactid.isEmpty()) && parentArtifactId != null && !parentArtifactId.isEmpty()) { + artifactid = parentArtifactId; + } + } + if (artifactid != null && !artifactid.isEmpty()) { + if (artifactid.startsWith("org.") || artifactid.startsWith("com.")) { + artifactid = artifactid.substring(4); + } + dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.HIGHEST); + dependency.getVendorEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.LOW); + if (parentArtifactId != null && !parentArtifactId.isEmpty() && !parentArtifactId.equals(artifactid)) { + dependency.getProductEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.MEDIUM); + dependency.getVendorEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.LOW); + } + } + //version + String version = pom.getVersion(); + String parentVersion = null; + if (pom.getParent() != null) { + parentVersion = pom.getParent().getVersion(); + if ((version == null || version.isEmpty()) && parentVersion != null && !parentVersion.isEmpty()) { + version = parentVersion; + } + } + if (version != null && !version.isEmpty()) { + dependency.getVersionEvidence().addEvidence("pom", "version", version, Confidence.HIGHEST); + if (parentVersion != null && !parentVersion.isEmpty() && !parentVersion.equals(version)) { + dependency.getVersionEvidence().addEvidence("pom", "parent-version", version, Confidence.LOW); + } + } + + final Organization org = pom.getOrganization(); + if (org != null) { + final String orgName = org.getName(); + if (orgName != null && !orgName.isEmpty()) { + dependency.getVendorEvidence().addEvidence("pom", "organization name", orgName, Confidence.HIGH); + } + } + final String pomName = pom.getName(); + if (pomName != null && !pomName.isEmpty()) { + dependency.getProductEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH); + dependency.getVendorEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH); + } + + if (pom.getDescription() != null) { + final String description = pom.getDescription(); + if (description != null && !description.isEmpty()) { + JarAnalyzer.addDescription(dependency, description, "pom", "description"); + } + } + JarAnalyzer.extractLicense(pom, null, dependency); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/generated/ObjectFactory.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/generated/ObjectFactory.java index e315aee91..e190ad93b 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/generated/ObjectFactory.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/jaxb/pom/generated/ObjectFactory.java @@ -31,7 +31,7 @@ import javax.xml.namespace.QName; @XmlRegistry public class ObjectFactory { - private final static QName _Project_QNAME = new QName("http://maven.apache.org/POM/4.0.0", "project"); + private static final QName _Project_QNAME = new QName("http://maven.apache.org/POM/4.0.0", "project"); /** * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.owasp.dependencycheck.analyzer.pom.generated diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java index d437ebd1f..e2322b7df 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java @@ -113,7 +113,7 @@ public class ReportGenerator { context.put("scanDate", scanDate); context.put("scanDateXML", scanDateXML); context.put("enc", enc); - context.put("version", Settings.getString("application.version", "Unknown")); + context.put("version", Settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown")); } /** @@ -137,6 +137,26 @@ public class ReportGenerator { return new VelocityContext(); } + /** + * Generates the Dependency Reports for the identified dependencies. + * + * @param outputStream the OutputStream to send the generated report to + * @param format the format the report should be written in + * @throws IOException is thrown when the template file does not exist + * @throws Exception is thrown if there is an error writing out the reports. + */ + public void generateReports(OutputStream outputStream, Format format) throws IOException, Exception { + if (format == Format.XML || format == Format.ALL) { + generateReport("XmlReport", outputStream); + } + if (format == Format.HTML || format == Format.ALL) { + generateReport("HtmlReport", outputStream); + } + if (format == Format.VULN || format == Format.ALL) { + generateReport("VulnerabilityReport", outputStream); + } + } + /** * Generates the Dependency Reports for the identified dependencies. * @@ -167,15 +187,28 @@ public class ReportGenerator { */ public void generateReports(String outputDir, String outputFormat) throws IOException, Exception { final String format = outputFormat.toUpperCase(); + final String pathToCheck = outputDir.toLowerCase(); if (format.matches("^(XML|HTML|VULN|ALL)$")) { if ("XML".equalsIgnoreCase(format)) { - generateReports(outputDir, Format.XML); + if (pathToCheck.endsWith(".xml")) { + generateReport("XmlReport", outputDir); + } else { + generateReports(outputDir, Format.XML); + } } if ("HTML".equalsIgnoreCase(format)) { - generateReports(outputDir, Format.HTML); + if (pathToCheck.endsWith(".html") || pathToCheck.endsWith(".htm")) { + generateReport("HtmlReport", outputDir); + } else { + generateReports(outputDir, Format.HTML); + } } if ("VULN".equalsIgnoreCase(format)) { - generateReports(outputDir, Format.VULN); + if (pathToCheck.endsWith(".html") || pathToCheck.endsWith(".htm")) { + generateReport("VulnReport", outputDir); + } else { + generateReports(outputDir, Format.VULN); + } } if ("ALL".equalsIgnoreCase(format)) { generateReports(outputDir, Format.ALL); @@ -189,11 +222,11 @@ public class ReportGenerator { * template file. * * @param templateName the name of the template to load. - * @param outFileName the filename and path to write the report to. + * @param outputStream the OutputStream to write the report to. * @throws IOException is thrown when the template file does not exist. * @throws Exception is thrown when an exception occurs. */ - protected void generateReport(String templateName, String outFileName) throws IOException, Exception { + protected void generateReport(String templateName, OutputStream outputStream) throws IOException, Exception { InputStream input = null; String templatePath = null; final File f = new File(templateName); @@ -216,18 +249,8 @@ public class ReportGenerator { final InputStreamReader reader = new InputStreamReader(input, "UTF-8"); OutputStreamWriter writer = null; - OutputStream outputStream = null; try { - final File outDir = new File(outFileName).getParentFile(); - if (!outDir.exists()) { - final boolean created = outDir.mkdirs(); - if (!created) { - throw new Exception("Unable to create directory '" + outDir.getAbsolutePath() + "'."); - } - } - - outputStream = new FileOutputStream(outFileName); writer = new OutputStreamWriter(outputStream, "UTF-8"); if (!engine.evaluate(context, writer, templatePath, reader)) { @@ -256,4 +279,41 @@ public class ReportGenerator { } } } + + /** + * Generates a report from a given Velocity Template. The template name provided can be the name of a template + * contained in the jar file, such as 'XmlReport' or 'HtmlReport', or the template name can be the path to a + * template file. + * + * @param templateName the name of the template to load. + * @param outFileName the filename and path to write the report to. + * @throws IOException is thrown when the template file does not exist. + * @throws Exception is thrown when an exception occurs. + */ + protected void generateReport(String templateName, String outFileName) throws Exception { + File outFile = new File(outFileName); + if (outFile.getParentFile() == null) { + outFile = new File(".", outFileName); + } + if (!outFile.getParentFile().exists()) { + final boolean created = outFile.getParentFile().mkdirs(); + if (!created) { + throw new Exception("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'."); + } + } + + OutputStream outputSteam = null; + try { + outputSteam = new FileOutputStream(outFile); + generateReport(templateName, outputSteam); + } finally { + if (outputSteam != null) { + try { + outputSteam.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "ignore", ex); + } + } + } + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java index 1254d5ea5..7d75a79c3 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java @@ -112,7 +112,7 @@ public class SuppressionRule { * @return whether or not this suppression rule as CPE entries */ public boolean hasCpe() { - return cpe.size() > 0; + return !cpe.isEmpty(); } /** * The list of cvssBelow scores. @@ -152,7 +152,7 @@ public class SuppressionRule { * @return whether or not this suppression rule has cvss suppressions */ public boolean hasCvssBelow() { - return cvssBelow.size() > 0; + return !cvssBelow.isEmpty(); } /** * The list of cwe entries to suppress. @@ -192,7 +192,7 @@ public class SuppressionRule { * @return whether this suppression rule has CWE entries */ public boolean hasCwe() { - return cwe.size() > 0; + return !cwe.isEmpty(); } /** * The list of cve entries to suppress. @@ -232,7 +232,7 @@ public class SuppressionRule { * @return whether this suppression rule has CVE entries */ public boolean hasCve() { - return cve.size() > 0; + return !cve.isEmpty(); } /** * A Maven GAV to suppression. @@ -266,10 +266,14 @@ public class SuppressionRule { return gav != null; } + /** + * A flag indicating whether or not the suppression rule is a core/base rule that should not be included in the + * resulting report in the "suppressed" section. + */ private boolean base; /** - * Get the value of base + * Get the value of base. * * @return the value of base */ @@ -278,7 +282,7 @@ public class SuppressionRule { } /** - * Set the value of base + * Set the value of base. * * @param base new value of base */ @@ -446,28 +450,28 @@ public class SuppressionRule { if (gav != null) { sb.append("gav=").append(gav).append(","); } - if (cpe != null && cpe.size() > 0) { + if (cpe != null && !cpe.isEmpty()) { sb.append("cpe={"); for (PropertyType pt : cpe) { sb.append(pt).append(","); } sb.append("}"); } - if (cwe != null && cwe.size() > 0) { + if (cwe != null && !cwe.isEmpty()) { sb.append("cwe={"); for (String s : cwe) { sb.append(s).append(","); } sb.append("}"); } - if (cve != null && cve.size() > 0) { + if (cve != null && !cve.isEmpty()) { sb.append("cve={"); for (String s : cve) { sb.append(s).append(","); } sb.append("}"); } - if (cvssBelow != null && cvssBelow.size() > 0) { + if (cvssBelow != null && !cvssBelow.isEmpty()) { sb.append("cvssBelow={"); for (Float s : cvssBelow) { sb.append(s).append(","); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DBUtils.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DBUtils.java index 819e7a798..4ee930733 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DBUtils.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DBUtils.java @@ -54,7 +54,9 @@ public final class DBUtils { int id = 0; try { rs = statement.getGeneratedKeys(); - rs.next(); + if (!rs.next()) { + throw new DatabaseException("Unable to get primary key for inserted row"); + } id = rs.getInt(1); } catch (SQLException ex) { throw new DatabaseException("Unable to get primary key for inserted row"); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DateUtil.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DateUtil.java new file mode 100644 index 000000000..9ededbab5 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DateUtil.java @@ -0,0 +1,46 @@ +/* + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.utils; + +/** + * + * @author Jeremy Long + */ +public final class DateUtil { + + /** + * Private constructor for utility class. + */ + private DateUtil() { + } + + /** + * Determines if the epoch date is within the range specified of the compareTo epoch time. This takes the + * (compareTo-date)/1000/60/60/24 to get the number of days. If the calculated days is less then the range the date + * is considered valid. + * + * @param date the date to be checked. + * @param compareTo the date to compare to. + * @param range the range in days to be considered valid. + * @return whether or not the date is within the range. + */ + public static boolean withinDateRange(long date, long compareTo, int range) { + final double differenceInDays = (compareTo - date) / 1000.0 / 60.0 / 60.0 / 24.0; + return differenceInDays < range; + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java index b0ba88311..1201c7ca0 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java @@ -65,7 +65,7 @@ public class DependencyVersion implements Iterable, Comparable(); if (version != null) { - final Pattern rx = Pattern.compile("(\\d+|[a-z]+\\d+|(release|beta|alpha)$)"); + final Pattern rx = Pattern.compile("(\\d+[a-z]{1,3}$|[a-z]+\\d+|\\d+|(release|beta|alpha)$)"); final Matcher matcher = rx.matcher(version.toLowerCase()); while (matcher.find()) { versionParts.add(matcher.group()); @@ -198,8 +198,8 @@ public class DependencyVersion implements Iterable, Comparable= 3) { if (thisVersion.compareToIgnoreCase(otherVersion) >= 0) { ret = false; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java index b67a5ac05..36991a93c 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java @@ -18,6 +18,7 @@ package org.owasp.dependencycheck.utils; import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,7 +33,7 @@ public final class DependencyVersionUtil { /** * Regular expression to extract version numbers from file names. */ - private static final Pattern RX_VERSION = Pattern.compile("\\d+(\\.\\d{1,6})+(\\.?([_-](release|beta|alpha)|[a-zA-Z_-]{1,3}\\d{1,8}))?"); + private static final Pattern RX_VERSION = Pattern.compile("\\d+(\\.\\d{1,6})+(\\.?([_-](release|beta|alpha|\\d+)|[a-zA-Z_-]{1,3}\\d{0,8}))?"); /** * Regular expression to extract a single version number without periods. This is a last ditch effort just to check * in case we are missing a version number using the previous regex. @@ -62,7 +63,7 @@ public final class DependencyVersionUtil { //'-' is a special case used within the CVE entries, just include it as the version. if ("-".equals(text)) { final DependencyVersion dv = new DependencyVersion(); - final ArrayList list = new ArrayList(); + final List list = new ArrayList(); list.add(text); dv.setVersionParts(list); return dv; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java index 3f0ae2b03..154e305aa 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java @@ -107,7 +107,7 @@ public final class ExtractionUtil { fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos, BUFFER_SIZE); int count; - final byte data[] = new byte[BUFFER_SIZE]; + final byte[] data = new byte[BUFFER_SIZE]; while ((count = zis.read(data, 0, BUFFER_SIZE)) != -1) { bos.write(data, 0, count); } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/UrlStringUtils.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/UrlStringUtils.java index bdec9b3e5..92f7ee71e 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/UrlStringUtils.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/UrlStringUtils.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; /** @@ -68,7 +69,7 @@ public final class UrlStringUtils { /** * A listing of domain parts that should not be used as evidence. Yes, this is an incomplete list. */ - private static final HashSet IGNORE_LIST = new HashSet( + private static final Set IGNORE_LIST = new HashSet( Arrays.asList("www", "com", "org", "gov", "info", "name", "net", "pro", "tel", "mobi", "xxx")); /** @@ -86,7 +87,7 @@ public final class UrlStringUtils { * @throws MalformedURLException thrown if the URL is malformed */ public static List extractImportantUrlData(String text) throws MalformedURLException { - final ArrayList importantParts = new ArrayList(); + final List importantParts = new ArrayList(); final URL url = new URL(text); final String[] domain = url.getHost().split("\\."); //add the domain except www and the tld. diff --git a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer index d752f4294..f2c4509c0 100644 --- a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer +++ b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer @@ -8,6 +8,7 @@ org.owasp.dependencycheck.analyzer.CpeSuppressionAnalyzer org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer org.owasp.dependencycheck.analyzer.NvdCveAnalyzer org.owasp.dependencycheck.analyzer.VulnerabilitySuppressionAnalyzer +org.owasp.dependencycheck.analyzer.CentralAnalyzer org.owasp.dependencycheck.analyzer.NexusAnalyzer org.owasp.dependencycheck.analyzer.NuspecAnalyzer org.owasp.dependencycheck.analyzer.AssemblyAnalyzer \ No newline at end of file diff --git a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.data.update.CachedWebDataSource b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.data.update.CachedWebDataSource index 4407069b5..73ca93754 100644 --- a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.data.update.CachedWebDataSource +++ b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.data.update.CachedWebDataSource @@ -1 +1,2 @@ -org.owasp.dependencycheck.data.update.NvdCveUpdater \ No newline at end of file +org.owasp.dependencycheck.data.update.NvdCveUpdater +org.owasp.dependencycheck.data.update.EngineVersionCheck \ No newline at end of file diff --git a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml index 0b1518d1d..6b117e543 100644 --- a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml +++ b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml @@ -9,6 +9,15 @@ cpe:/a:springsource:spring_framework cpe:/a:vmware:springsource_spring_framework + + + .*spring-security-[^\\/]*\.jar$ + cpe:/a:mod_security:mod_security + cpe:/a:springsource:spring_framework + cpe:/a:vmware:springsource_spring_framework + - org\.glassfish\.jersey\.core:jersey-(client|common):.* + (com\.sun\.jersey|org\.glassfish\.jersey\.core):jersey-(client|common):.* + cpe:/a:oracle:glassfish + cpe:/a:oracle:oracle_client + + + + org\.glassfish\.grizzly:grizzly-framework:.* cpe:/a:oracle:glassfish + + + org\.forgerock\.opendj:opendj-ldap-sdk:.* + cpe:/a:ldap_project:ldap + + + + org\.opensaml:xmltooling:.* + cpe:/a:shibboleth:opensaml + + + + org\.opensaml:openws:.* + cpe:/a:internet2:opensaml + + + + org\.opensaml:xmltooling:.* + cpe:/a:internet2:opensaml + + \ No newline at end of file diff --git a/dependency-check-core/src/main/resources/dependencycheck.properties b/dependency-check-core/src/main/resources/dependencycheck.properties index 0090e0c50..6a737631c 100644 --- a/dependency-check-core/src/main/resources/dependencycheck.properties +++ b/dependency-check-core/src/main/resources/dependencycheck.properties @@ -3,6 +3,9 @@ application.version=${pom.version} autoupdate=true max.download.threads=3 +# the url to obtain the current engine version from +engine.version.url=http://jeremylong.github.io/DependencyCheck/current.txt + #temp.directory defaults to System.getProperty("java.io.tmpdir") #temp.directory=[path to temp directory] @@ -40,11 +43,16 @@ data.driver_path= cve.url.modified.validfordays=7 # the path to the modified nvd cve xml file. -cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml -cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml +cve.url-1.2.modified=https://nvd.nist.gov/download/nvdcve-Modified.xml.gz +#cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml +cve.url-2.0.modified=https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-Modified.xml.gz +#cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml cve.startyear=2002 -cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml -cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml +cve.url-1.2.base=https://nvd.nist.gov/download/nvdcve-%d.xml.gz +#cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml +cve.url-2.0.base=https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml.gz +#cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml + # file type analyzer settings: analyzer.archive.enabled=true @@ -58,3 +66,7 @@ analyzer.nexus.url=https://repository.sonatype.org/service/local/ # If set to true, the proxy will still ONLY be used if the proxy properties (proxy.url, proxy.port) # are configured analyzer.nexus.proxy=true + +# the URL for searching search.maven.org for SHA-1 and whether it's enabled +analyzer.central.enabled=true +analyzer.central.url=http://search.maven.org/solrsearch/select diff --git a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl index 6c6d88120..bf948426c 100644 --- a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl +++ b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl @@ -534,7 +534,7 @@ arising out of or in connection with the use of this tool, the analysis performe #end
- Display: Showing Vulnerable Dependencies

+ Display: Showing Vulnerable Dependencies (click to show all)

#set($lnkcnt=0) @@ -606,22 +606,6 @@ arising out of or in connection with the use of this tool, the analysis performe #end
-##
    -## #set($lnkcnt=0) -## #foreach($dependency in $dependencies) -## #set($lnkcnt=$lnkcnt+1) -##
  • -## $enc.html($dependency.DisplayFileName) -## #if($dependency.getRelatedDependencies().size()>0) -##
      -## #foreach($related in $dependency.getRelatedDependencies()) -##
    • $enc.html($related.DisplayFileName)
    • -## #end -##
    -## #end -##
  • -## #end -##

Dependencies

#set($lnkcnt=0) #set($cnt=0) @@ -644,6 +628,19 @@ arising out of or in connection with the use of this tool, the analysis performe File Path: $enc.html($dependency.FilePath)
MD5: $enc.html($dependency.Md5sum)
SHA1: $enc.html($dependency.Sha1sum) + #if ($dependency.projectReferences.size()==1) +
Referenced In Project: + #foreach($ref in $dependency.projectReferences) + $enc.html($ref) + #end + #end + #if ($dependency.projectReferences.size()>1) +
Referenced In Projects:
    + #foreach($ref in $dependency.projectReferences) +
  • $enc.html($ref)
  • + #end +
+ #end

#set($cnt=$cnt+1) @@ -906,6 +903,6 @@ arising out of or in connection with the use of this tool, the analysis performe ## END SUPPRESSED VULNERABILITIES -


This report contains data retrieved from the National Vulnerability Database.
+


This report contains data retrieved from the National Vulnerability Database.
diff --git a/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl b/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl index 44ee05e77..9070cc7a8 100644 --- a/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl +++ b/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl @@ -236,6 +236,6 @@ arising out of or in connection with the use of this tool, the analysis performe -



This report contains data retrieved from the National Vulnerability Database.

+



This report contains data retrieved from the National Vulnerability Database.

diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/BaseTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/BaseTest.java index b294ee936..634665fac 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/BaseTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/BaseTest.java @@ -15,7 +15,10 @@ */ package org.owasp.dependencycheck; +import java.io.File; +import java.io.InputStream; import org.junit.AfterClass; +import org.junit.Assume; import org.junit.BeforeClass; import org.owasp.dependencycheck.utils.Settings; @@ -34,4 +37,31 @@ public class BaseTest { public static void tearDownClass() throws Exception { Settings.cleanup(true); } + + /** + * Returns the given resource as an InputStream using the object's class loader. The org.junit.Assume API is used so that test + * cases are skipped if the resource is not available. + * + * @param o the object used to obtain a reference to the class loader + * @param resource the name of the resource to load + * @return the resource as an InputStream + */ + public static InputStream getResourceAsStream(Object o, String resource) { + getResourceAsFile(o, resource); + return o.getClass().getClassLoader().getResourceAsStream(resource); + } + + /** + * Returns the given resource as a File using the object's class loader. The org.junit.Assume API is used so that test cases + * are skipped if the resource is not available. + * + * @param o the object used to obtain a reference to the class loader + * @param resource the name of the resource to load + * @return the resource as an File + */ + public static File getResourceAsFile(Object o, String resource) { + File f = new File(o.getClass().getClassLoader().getResource(resource).getPath()); + Assume.assumeTrue(String.format("%n%n[SEVERE] Unable to load resource for test case: %s%n%n", resource), f.exists()); + return f; + } } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java index c986ae487..86f1db682 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java @@ -23,7 +23,6 @@ import org.junit.Before; import org.junit.Test; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; -import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.reporting.ReportGenerator; import org.owasp.dependencycheck.utils.Settings; @@ -42,26 +41,6 @@ public class EngineIntegrationTest extends BaseTest { public void tearDown() { } - /** - * Test of scan method, of class Engine. - * - * @throws Exception is thrown when an exception occurs. - */ - @Test - public void testScan() throws Exception { - String testClasses = "target/test-classes/*.zip"; - boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); - Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); - Engine instance = new Engine(); - Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate); - instance.scan(testClasses); - assertTrue(instance.getDependencies().size() > 0); - for (Dependency d : instance.getDependencies()) { - assertTrue("non-zip file collected " + d.getFileName(), d.getFileName().toLowerCase().endsWith(".zip")); - } - instance.cleanup(); - } - /** * Test running the entire engine. * @@ -81,8 +60,7 @@ public class EngineIntegrationTest extends BaseTest { cveDB.open(); DatabaseProperties dbProp = cveDB.getDatabaseProperties(); cveDB.close(); - ReportGenerator rg = new ReportGenerator("DependencyCheck", - instance.getDependencies(), instance.getAnalyzers(), dbProp); + ReportGenerator rg = new ReportGenerator("DependencyCheck", instance.getDependencies(), instance.getAnalyzers(), dbProp); rg.generateReports("./target/", "ALL"); instance.cleanup(); } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzerIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzerIntegrationTest.java index 1ca50dfcf..9b07949da 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzerIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzerIntegrationTest.java @@ -23,6 +23,7 @@ import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.data.cpe.AbstractDatabaseTestCase; import org.owasp.dependencycheck.dependency.Dependency; @@ -40,7 +41,7 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { @Test public void testGetSupportedExtensions() { ArchiveAnalyzer instance = new ArchiveAnalyzer(); - Set expResult = new HashSet(); + Set expResult = new HashSet(); expResult.add("zip"); expResult.add("war"); expResult.add("ear"); @@ -129,11 +130,12 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { instance.supportsExtension("ear"); try { instance.initialize(); - - File file = new File(this.getClass().getClassLoader().getResource("daytrader-ear-2.1.7.ear").getPath()); + File file = BaseTest.getResourceAsFile(this, "daytrader-ear-2.1.7.ear"); + //File file = new File(this.getClass().getClassLoader().getResource("daytrader-ear-2.1.7.ear").getPath()); Dependency dependency = new Dependency(file); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); int initial_size = engine.getDependencies().size(); @@ -161,10 +163,12 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { instance.initialize(); //File file = new File(this.getClass().getClassLoader().getResource("file.tar").getPath()); - File file = new File(this.getClass().getClassLoader().getResource("stagedhttp-modified.tar").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("stagedhttp-modified.tar").getPath()); + File file = BaseTest.getResourceAsFile(this, "stagedhttp-modified.tar"); Dependency dependency = new Dependency(file); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); int initial_size = engine.getDependencies().size(); @@ -189,10 +193,12 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { try { instance.initialize(); - File file = new File(this.getClass().getClassLoader().getResource("file.tar.gz").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("file.tar.gz").getPath()); + File file = BaseTest.getResourceAsFile(this, "file.tar.gz"); //Dependency dependency = new Dependency(file); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); int initial_size = engine.getDependencies().size(); @@ -220,6 +226,7 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { // File file = new File(this.getClass().getClassLoader().getResource("nested.zip").getPath()); // Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); // Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); +// Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); // Engine engine = new Engine(); // // engine.scan(file); @@ -239,9 +246,11 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { try { instance.initialize(); - File file = new File(this.getClass().getClassLoader().getResource("file.tgz").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("file.tgz").getPath()); + File file = BaseTest.getResourceAsFile(this, "file.tgz"); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); int initial_size = engine.getDependencies().size(); @@ -265,10 +274,12 @@ public class ArchiveAnalyzerIntegrationTest extends AbstractDatabaseTestCase { try { instance.initialize(); - File file = new File(this.getClass().getClassLoader().getResource("test.zip").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("test.zip").getPath()); + File file = BaseTest.getResourceAsFile(this, "test.zip"); Dependency dependency = new Dependency(file); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); int initial_size = engine.getDependencies().size(); // boolean failed = false; diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java index 2d7573c41..1e1e70685 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java @@ -78,7 +78,8 @@ public class AssemblyAnalyzerTest extends BaseTest { @Test public void testAnalysis() throws Exception { - File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("GrokAssembly.exe").getPath()); + //File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("GrokAssembly.exe").getPath()); + File f = BaseTest.getResourceAsFile(this, "GrokAssembly.exe"); Dependency d = new Dependency(f); analyzer.analyze(d, null); boolean foundVendor = false; @@ -100,7 +101,9 @@ public class AssemblyAnalyzerTest extends BaseTest { @Test public void testLog4Net() throws Exception { - File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("log4net.dll").getPath()); + //File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("log4net.dll").getPath()); + File f = BaseTest.getResourceAsFile(this, "log4net.dll"); + Dependency d = new Dependency(f); analyzer.analyze(d, null); assertTrue(d.getVersionEvidence().getEvidence().contains(new Evidence("grokassembly", "version", "1.2.13.0", Confidence.HIGHEST))); @@ -115,7 +118,8 @@ public class AssemblyAnalyzerTest extends BaseTest { // Tweak the log level so the warning doesn't show in the console Logger.getLogger(AssemblyAnalyzer.class.getName()).setLevel(Level.OFF); Logger.getLogger(Dependency.class.getName()).setLevel(Level.OFF); - File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("log4net.dll").getPath()); + //File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("log4net.dll").getPath()); + File f = BaseTest.getResourceAsFile(this, "log4net.dll"); File test = new File(f.getParent(), "nonexistent.dll"); Dependency d = new Dependency(test); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java index b79d49739..ef319eb51 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java @@ -25,9 +25,12 @@ import java.util.Set; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.queryparser.classic.ParseException; import org.junit.Assert; +import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.data.cpe.AbstractDatabaseTestCase; import org.owasp.dependencycheck.data.cpe.IndexEntry; +import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; @@ -108,7 +111,8 @@ public class CPEAnalyzerIntegrationTest extends AbstractDatabaseTestCase { */ public void callDetermineCPE_full(String depName, String expResult, CPEAnalyzer instance, FileNameAnalyzer fnAnalyzer, JarAnalyzer jarAnalyzer, HintAnalyzer hAnalyzer, FalsePositiveAnalyzer fp) throws Exception { - File file = new File(this.getClass().getClassLoader().getResource(depName).getPath()); + //File file = new File(this.getClass().getClassLoader().getResource(depName).getPath()); + File file = BaseTest.getResourceAsFile(this, depName); Dependency dep = new Dependency(file); @@ -135,7 +139,8 @@ public class CPEAnalyzerIntegrationTest extends AbstractDatabaseTestCase { */ @Test public void testDetermineCPE() throws Exception { - File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File file = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); //File file = new File(this.getClass().getClassLoader().getResource("axis2-adb-1.4.1.jar").getPath()); Dependency struts = new Dependency(file); @@ -145,15 +150,18 @@ public class CPEAnalyzerIntegrationTest extends AbstractDatabaseTestCase { JarAnalyzer jarAnalyzer = new JarAnalyzer(); jarAnalyzer.analyze(struts, null); - File fileCommonValidator = new File(this.getClass().getClassLoader().getResource("commons-validator-1.4.0.jar").getPath()); + //File fileCommonValidator = new File(this.getClass().getClassLoader().getResource("commons-validator-1.4.0.jar").getPath()); + File fileCommonValidator = BaseTest.getResourceAsFile(this, "commons-validator-1.4.0.jar"); Dependency commonValidator = new Dependency(fileCommonValidator); jarAnalyzer.analyze(commonValidator, null); - File fileSpring = new File(this.getClass().getClassLoader().getResource("spring-core-2.5.5.jar").getPath()); + //File fileSpring = new File(this.getClass().getClassLoader().getResource("spring-core-2.5.5.jar").getPath()); + File fileSpring = BaseTest.getResourceAsFile(this, "spring-core-2.5.5.jar"); Dependency spring = new Dependency(fileSpring); jarAnalyzer.analyze(spring, null); - File fileSpring3 = new File(this.getClass().getClassLoader().getResource("spring-core-3.0.0.RELEASE.jar").getPath()); + //File fileSpring3 = new File(this.getClass().getClassLoader().getResource("spring-core-3.0.0.RELEASE.jar").getPath()); + File fileSpring3 = BaseTest.getResourceAsFile(this, "spring-core-3.0.0.RELEASE.jar"); Dependency spring3 = new Dependency(fileSpring3); jarAnalyzer.analyze(spring3, null); @@ -183,6 +191,30 @@ public class CPEAnalyzerIntegrationTest extends AbstractDatabaseTestCase { //Assert.assertTrue("Incorrect match - spring", spring.getIdentifiers().get(0).getValue().equals(expResultSpring)); } + /** + * Test of determineIdentifiers method, of class CPEAnalyzer. + * + * @throws Exception is thrown when an exception occurs + */ + @Test + public void testDetermineIdentifiers() throws Exception { + Dependency openssl = new Dependency(); + openssl.getVendorEvidence().addEvidence("test", "vendor", "openssl", Confidence.HIGHEST); + openssl.getProductEvidence().addEvidence("test", "product", "openssl", Confidence.HIGHEST); + openssl.getVersionEvidence().addEvidence("test", "version", "1.0.1c", Confidence.HIGHEST); + + CPEAnalyzer instance = new CPEAnalyzer(); + instance.open(); + instance.determineIdentifiers(openssl, "openssl", "openssl", Confidence.HIGHEST); + instance.close(); + + String expResult = "cpe:/a:openssl:openssl:1.0.1c"; + Identifier expIdentifier = new Identifier("cpe", expResult, expResult); + + assertTrue(openssl.getIdentifiers().contains(expIdentifier)); + + } + /** * Test of searchCPE method, of class CPEAnalyzer. * @@ -193,12 +225,12 @@ public class CPEAnalyzerIntegrationTest extends AbstractDatabaseTestCase { String vendor = "apache software foundation"; String product = "struts 2 core"; String version = "2.1.2"; - String expResult = "cpe:/a:apache:struts:2.1.2"; + String expVendor = "apache"; + String expProduct = "struts"; CPEAnalyzer instance = new CPEAnalyzer(); instance.open(); - //TODO - yeah, not a very good test as the results are the same with or without weighting... Set productWeightings = new HashSet(1); productWeightings.add("struts2"); @@ -206,9 +238,16 @@ public class CPEAnalyzerIntegrationTest extends AbstractDatabaseTestCase { vendorWeightings.add("apache"); List result = instance.searchCPE(vendor, product, productWeightings, vendorWeightings); - //TODO fix this assert - //Assert.assertEquals(expResult, result.get(0).getName()); - instance.close(); + + boolean found = false; + for (IndexEntry entry : result) { + if (expVendor.equals(entry.getVendor()) && expProduct.equals(entry.getProduct())) { + found = true; + break; + } + } + assertTrue("apache:struts was not identified", found); + } } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzerTest.java index 897ad6b3e..4978eb694 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzerTest.java @@ -86,4 +86,40 @@ public class DependencyBundlingAnalyzerTest extends BaseTest { assertEquals(expResult, result); } + @Test + public void testFirstPathIsShortest() { + DependencyBundlingAnalyzer instance = new DependencyBundlingAnalyzer(); + + String left = "./a/c.jar"; + String right = "./d/e/f.jar"; + boolean expResult = true; + boolean result = instance.firstPathIsShortest(left, right); + assertEquals(expResult, result); + + left = "./a/b/c.jar"; + right = "./d/e/f.jar"; + expResult = true; + result = instance.firstPathIsShortest(left, right); + assertEquals(expResult, result); + + left = "./d/b/c.jar"; + right = "./a/e/f.jar"; + expResult = false; + result = instance.firstPathIsShortest(left, right); + assertEquals(expResult, result); + + left = "./a/b/c.jar"; + right = "./d/f.jar"; + expResult = false; + result = instance.firstPathIsShortest(left, right); + assertEquals(expResult, result); + + left = "./a/b/c.jar"; + right = "./a/b/c.jar"; + expResult = true; + result = instance.firstPathIsShortest(left, right); + assertEquals(expResult, result); + + } + } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzerTest.java index 9f908c9ae..f91dbf2a0 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzerTest.java @@ -21,6 +21,7 @@ import java.io.File; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.dependency.Dependency; /** @@ -56,9 +57,11 @@ public class FileNameAnalyzerTest { */ @Test public void testAnalyze() throws Exception { - File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + //File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File struts = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); Dependency resultStruts = new Dependency(struts); - File axis = new File(this.getClass().getClassLoader().getResource("axis2-adb-1.4.1.jar").getPath()); + //File axis = new File(this.getClass().getClassLoader().getResource("axis2-adb-1.4.1.jar").getPath()); + File axis = BaseTest.getResourceAsFile(this, "axis2-adb-1.4.1.jar"); Dependency resultAxis = new Dependency(axis); FileNameAnalyzer instance = new FileNameAnalyzer(); instance.analyze(resultStruts, null); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/HintAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/HintAnalyzerTest.java index f4a8cb63d..6c514f169 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/HintAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/HintAnalyzerTest.java @@ -69,12 +69,15 @@ public class HintAnalyzerTest extends BaseTest { public void testAnalyze() throws Exception { HintAnalyzer instance = new HintAnalyzer(); - File guice = new File(this.getClass().getClassLoader().getResource("guice-3.0.jar").getPath()); + //File guice = new File(this.getClass().getClassLoader().getResource("guice-3.0.jar").getPath()); + File guice = BaseTest.getResourceAsFile(this, "guice-3.0.jar"); //Dependency guice = new Dependency(fileg); - File spring = new File(this.getClass().getClassLoader().getResource("spring-core-3.0.0.RELEASE.jar").getPath()); + //File spring = new File(this.getClass().getClassLoader().getResource("spring-core-3.0.0.RELEASE.jar").getPath()); + File spring = BaseTest.getResourceAsFile(this, "spring-core-3.0.0.RELEASE.jar"); //Dependency spring = new Dependency(files); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); engine.scan(guice); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JarAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JarAnalyzerTest.java index 1febeedd2..953a7ec1c 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JarAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JarAnalyzerTest.java @@ -41,14 +41,16 @@ public class JarAnalyzerTest extends BaseTest { */ @Test public void testAnalyze() throws Exception { - File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File file = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); Dependency result = new Dependency(file); JarAnalyzer instance = new JarAnalyzer(); instance.analyze(result, null); assertTrue(result.getVendorEvidence().toString().toLowerCase().contains("apache")); assertTrue(result.getVendorEvidence().getWeighting().contains("apache")); - file = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath()); + //file = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath()); + file = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar"); result = new Dependency(file); instance.analyze(result, null); boolean found = false; @@ -81,7 +83,8 @@ public class JarAnalyzerTest extends BaseTest { } assertTrue("implementation-version of 4.2.27 not found in org.mortbay.jetty.jar", found); - file = new File(this.getClass().getClassLoader().getResource("org.mortbay.jmx.jar").getPath()); + //file = new File(this.getClass().getClassLoader().getResource("org.mortbay.jmx.jar").getPath()); + file = BaseTest.getResourceAsFile(this, "org.mortbay.jmx.jar"); result = new Dependency(file); instance.analyze(result, null); assertEquals("org.mortbar,jmx.jar has version evidence?", result.getVersionEvidence().size(), 0); @@ -93,7 +96,7 @@ public class JarAnalyzerTest extends BaseTest { @Test public void testGetSupportedExtensions() { JarAnalyzer instance = new JarAnalyzer(); - Set expResult = new HashSet(); + Set expResult = new HashSet(); expResult.add("jar"); expResult.add("war"); Set result = instance.getSupportedExtensions(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JavaScriptAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JavaScriptAnalyzerTest.java index 74dfb7efa..83f897bfd 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JavaScriptAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/JavaScriptAnalyzerTest.java @@ -38,7 +38,7 @@ public class JavaScriptAnalyzerTest extends BaseTest { @Test public void testGetSupportedExtensions() { JavaScriptAnalyzer instance = new JavaScriptAnalyzer(); - Set expResult = new HashSet(); + Set expResult = new HashSet(); expResult.add("js"); Set result = instance.getSupportedExtensions(); assertEquals(expResult, result); @@ -84,9 +84,12 @@ public class JavaScriptAnalyzerTest extends BaseTest { */ @Test public void testAnalyze() throws Exception { - File jq6 = new File(this.getClass().getClassLoader().getResource("jquery-1.6.2.min.js").getPath()); - File jq10 = new File(this.getClass().getClassLoader().getResource("jquery-1.10.2.js").getPath()); - File jq10min = new File(this.getClass().getClassLoader().getResource("jquery-1.10.2.min.js").getPath()); + //File jq6 = new File(this.getClass().getClassLoader().getResource("jquery-1.6.2.min.js").getPath()); + File jq6 = BaseTest.getResourceAsFile(this, "jquery-1.6.2.min.js"); + //File jq10 = new File(this.getClass().getClassLoader().getResource("jquery-1.10.2.js").getPath()); + File jq10 = BaseTest.getResourceAsFile(this, "jquery-1.10.2.js"); + //File jq10min = new File(this.getClass().getClassLoader().getResource("jquery-1.10.2.min.js").getPath()); + File jq10min = BaseTest.getResourceAsFile(this, "jquery-1.10.2.min.js"); Dependency depJQ6 = new Dependency(jq6); Dependency depJQ10 = new Dependency(jq10); Dependency depJQ10min = new Dependency(jq10min); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzerIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzerIntegrationTest.java index 6ae7ace04..e0ca76b92 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzerIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzerIntegrationTest.java @@ -21,6 +21,7 @@ import java.io.File; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.data.cpe.AbstractDatabaseTestCase; import org.owasp.dependencycheck.dependency.Dependency; @@ -61,10 +62,13 @@ public class VulnerabilitySuppressionAnalyzerIntegrationTest extends AbstractDat @Test public void testAnalyze() throws Exception { - File file = new File(this.getClass().getClassLoader().getResource("commons-fileupload-1.2.1.jar").getPath()); - File suppression = new File(this.getClass().getClassLoader().getResource("commons-fileupload-1.2.1.suppression.xml").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("commons-fileupload-1.2.1.jar").getPath()); + File file = BaseTest.getResourceAsFile(this, "commons-fileupload-1.2.1.jar"); + //File suppression = new File(this.getClass().getClassLoader().getResource("commons-fileupload-1.2.1.suppression.xml").getPath()); + File suppression = BaseTest.getResourceAsFile(this, "commons-fileupload-1.2.1.suppression.xml"); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, false); + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, false); Engine engine = new Engine(); engine.scan(file); engine.analyzeDependencies(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/central/CentralSearchTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/central/CentralSearchTest.java new file mode 100644 index 000000000..d75ef404a --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/central/CentralSearchTest.java @@ -0,0 +1,63 @@ +package org.owasp.dependencycheck.data.central; + +import org.junit.Before; +import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; +import org.owasp.dependencycheck.utils.Settings; + +import java.io.FileNotFoundException; +import java.net.URL; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.Assert.*; + +/** + * Created by colezlaw on 10/13/14. + */ +public class CentralSearchTest extends BaseTest { + private static final Logger LOGGER = Logger.getLogger(CentralSearchTest.class.getName()); + private CentralSearch searcher; + + @Before + public void setUp() throws Exception { + String centralUrl = Settings.getString(Settings.KEYS.ANALYZER_CENTRAL_URL); + LOGGER.fine(centralUrl); + searcher = new CentralSearch(new URL(centralUrl)); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullSha1() throws Exception { searcher.searchSha1(null); } + + @Test(expected = IllegalArgumentException.class) + public void testMalformedSha1() throws Exception { + searcher.searchSha1("invalid"); + } + + // This test does generate network traffic and communicates with a host + // you may not be able to reach. Remove the @Ignore annotation if you want to + // test it anyway + @Test + public void testValidSha1() throws Exception { + List ma = searcher.searchSha1("9977a8d04e75609cf01badc4eb6a9c7198c4c5ea"); + assertEquals("Incorrect group", "org.apache.maven.plugins", ma.get(0).getGroupId()); + assertEquals("Incorrect artifact", "maven-compiler-plugin", ma.get(0).getArtifactId()); + assertEquals("Incorrect version", "3.1", ma.get(0).getVersion()); + } + + // This test does generate network traffic and communicates with a host + // you may not be able to reach. Remove the @Ignore annotation if you want to + // test it anyway + @Test(expected = FileNotFoundException.class) + public void testMissingSha1() throws Exception { + searcher.searchSha1("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + } + + // This test should give us multiple results back from Central + @Test + public void testMultipleReturns() throws Exception { + List ma = searcher.searchSha1("94A9CE681A42D0352B3AD22659F67835E560D107"); + assertTrue(ma.size() > 1); + } +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilterTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilterTest.java index cd13e0e15..ce7afb437 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilterTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/lucene/UrlTokenizingFilterTest.java @@ -20,16 +20,10 @@ package org.owasp.dependencycheck.data.lucene; import java.io.IOException; import java.io.Reader; import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.Analyzer.TokenStreamComponents; import org.apache.lucene.analysis.BaseTokenStreamTestCase; -import static org.apache.lucene.analysis.BaseTokenStreamTestCase.checkOneTerm; import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.core.KeywordTokenizer; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; /** * @@ -50,24 +44,6 @@ public class UrlTokenizingFilterTest extends BaseTokenStreamTestCase { }; } - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() throws Exception { - super.setUp(); - } - - @After - public void tearDown() throws Exception { - super.tearDown(); - } - /** * test some example domains */ @@ -102,6 +78,6 @@ public class UrlTokenizingFilterTest extends BaseTokenStreamTestCase { return new TokenStreamComponents(tokenizer, new UrlTokenizingFilter(tokenizer)); } }; - checkOneTermReuse(a, "", ""); + checkOneTerm(a, "", ""); } } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java index 292274356..144b86e22 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.utils.Settings; @@ -42,11 +43,13 @@ public class NexusSearchTest extends BaseTest { } @Test(expected = IllegalArgumentException.class) + @Ignore public void testNullSha1() throws Exception { searcher.searchSha1(null); } @Test(expected = IllegalArgumentException.class) + @Ignore public void testMalformedSha1() throws Exception { searcher.searchSha1("invalid"); } @@ -55,6 +58,7 @@ public class NexusSearchTest extends BaseTest { // you may not be able to reach. Remove the @Ignore annotation if you want to // test it anyway @Test + @Ignore public void testValidSha1() throws Exception { MavenArtifact ma = searcher.searchSha1("9977a8d04e75609cf01badc4eb6a9c7198c4c5ea"); assertEquals("Incorrect group", "org.apache.maven.plugins", ma.getGroupId()); @@ -67,6 +71,7 @@ public class NexusSearchTest extends BaseTest { // you may not be able to reach. Remove the @Ignore annotation if you want to // test it anyway @Test(expected = FileNotFoundException.class) + @Ignore public void testMissingSha1() throws Exception { searcher.searchSha1("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nuget/XPathNuspecParserTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nuget/XPathNuspecParserTest.java index fd23162bb..e38b52c6b 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nuget/XPathNuspecParserTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nuget/XPathNuspecParserTest.java @@ -18,6 +18,7 @@ package org.owasp.dependencycheck.data.nuget; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.InputStream; import java.io.PrintStream; import static org.junit.Assert.assertEquals; @@ -39,7 +40,8 @@ public class XPathNuspecParserTest extends BaseTest { @Test public void testGoodDocument() throws Exception { NuspecParser parser = new XPathNuspecParser(); - InputStream is = XPathNuspecParserTest.class.getClassLoader().getResourceAsStream("log4net.2.0.3.nuspec"); + //InputStream is = XPathNuspecParserTest.class.getClassLoader().getResourceAsStream("log4net.2.0.3.nuspec"); + InputStream is = BaseTest.getResourceAsStream(this, "log4net.2.0.3.nuspec"); NugetPackage np = parser.parse(is); assertEquals("log4net", np.getId()); assertEquals("2.0.3", np.getVersion()); @@ -57,7 +59,8 @@ public class XPathNuspecParserTest extends BaseTest { @Test(expected = NuspecParseException.class) public void testMissingDocument() throws Exception { NuspecParser parser = new XPathNuspecParser(); - InputStream is = XPathNuspecParserTest.class.getClassLoader().getResourceAsStream("dependencycheck.properties"); + //InputStream is = XPathNuspecParserTest.class.getClassLoader().getResourceAsStream("dependencycheck.properties"); + InputStream is = BaseTest.getResourceAsStream(this, "dependencycheck.properties"); //hide the fatal message from the core parser final ByteArrayOutputStream myOut = new ByteArrayOutputStream(); @@ -74,7 +77,8 @@ public class XPathNuspecParserTest extends BaseTest { @Test(expected = NuspecParseException.class) public void testNotNuspec() throws Exception { NuspecParser parser = new XPathNuspecParser(); - InputStream is = XPathNuspecParserTest.class.getClassLoader().getResourceAsStream("suppressions.xml"); + //InputStream is = XPathNuspecParserTest.class.getClassLoader().getResourceAsStream("suppressions.xml"); + InputStream is = BaseTest.getResourceAsStream(this, "suppressions.xml"); NugetPackage np = parser.parse(is); } } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/CveDBIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/CveDBIntegrationTest.java index 66f78f19c..39ae50614 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/CveDBIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/CveDBIntegrationTest.java @@ -17,11 +17,16 @@ */ package org.owasp.dependencycheck.data.nvdcve; +import java.util.HashMap; import java.util.List; +import java.util.Map.Entry; import java.util.Set; +import org.junit.Assert; import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.owasp.dependencycheck.dependency.Vulnerability; import org.owasp.dependencycheck.dependency.VulnerableSoftware; +import org.owasp.dependencycheck.utils.DependencyVersion; /** * @@ -64,12 +69,95 @@ public class CveDBIntegrationTest extends BaseDBTestCase { public void testGetVulnerabilities() throws Exception { String cpeStr = "cpe:/a:apache:struts:2.1.2"; CveDB instance = new CveDB(); + List results; try { instance.open(); - List result = instance.getVulnerabilities(cpeStr); - assertTrue(result.size() > 5); + results = instance.getVulnerabilities(cpeStr); + assertTrue(results.size() > 5); + cpeStr = "cpe:/a:jruby:jruby:1.6.3"; + results = instance.getVulnerabilities(cpeStr); + assertTrue(results.size() > 1); + + boolean found = false; + String expected = "CVE-2011-4838"; + for (Vulnerability v : results) { + if (expected.equals(v.getName())) { + found = true; + break; + } + } + assertTrue("Expected " + expected + ", but was not identified", found); + + found = false; + expected = "CVE-2012-5370"; + for (Vulnerability v : results) { + if (expected.equals(v.getName())) { + found = true; + break; + } + } + assertTrue("Expected " + expected + ", but was not identified", found); + } finally { instance.close(); } } + + /** + * Test of getMatchingSoftware method, of class CveDB. + */ + @Test + public void testGetMatchingSoftware() throws Exception { + HashMap versions = new HashMap(); + DependencyVersion identifiedVersion = new DependencyVersion("1.0.1o"); + versions.put("cpe:/a:openssl:openssl:1.0.1e", Boolean.FALSE); + + CveDB instance = new CveDB(); + Entry results = instance.getMatchingSoftware(versions, "openssl", "openssl", identifiedVersion); + Assert.assertNull(results); + versions.put("cpe:/a:openssl:openssl:1.0.1p", Boolean.FALSE); + results = instance.getMatchingSoftware(versions, "openssl", "openssl", identifiedVersion); + Assert.assertNull(results); + + versions.put("cpe:/a:openssl:openssl:1.0.1q", Boolean.TRUE); + results = instance.getMatchingSoftware(versions, "openssl", "openssl", identifiedVersion); + Assert.assertNotNull(results); + Assert.assertEquals("cpe:/a:openssl:openssl:1.0.1q", results.getKey()); + + versions.clear(); + + versions.put("cpe:/a:springsource:spring_framework:3.2.5", Boolean.FALSE); + versions.put("cpe:/a:springsource:spring_framework:3.2.6", Boolean.FALSE); + versions.put("cpe:/a:springsource:spring_framework:3.2.7", Boolean.TRUE); + + versions.put("cpe:/a:springsource:spring_framework:4.0.1", Boolean.TRUE); + versions.put("cpe:/a:springsource:spring_framework:4.0.0:m1", Boolean.FALSE); + versions.put("cpe:/a:springsource:spring_framework:4.0.0:m2", Boolean.FALSE); + versions.put("cpe:/a:springsource:spring_framework:4.0.0:rc1", Boolean.FALSE); + + identifiedVersion = new DependencyVersion("3.2.2"); + results = instance.getMatchingSoftware(versions, "springsource", "spring_framework", identifiedVersion); + Assert.assertEquals("cpe:/a:springsource:spring_framework:3.2.7", results.getKey()); + Assert.assertTrue(results.getValue()); + identifiedVersion = new DependencyVersion("3.2.12"); + results = instance.getMatchingSoftware(versions, "springsource", "spring_framework", identifiedVersion); + Assert.assertNull(results); + + identifiedVersion = new DependencyVersion("4.0.0"); + results = instance.getMatchingSoftware(versions, "springsource", "spring_framework", identifiedVersion); + Assert.assertEquals("cpe:/a:springsource:spring_framework:4.0.1", results.getKey()); + Assert.assertTrue(results.getValue()); + identifiedVersion = new DependencyVersion("4.1.0"); + results = instance.getMatchingSoftware(versions, "springsource", "spring_framework", identifiedVersion); + Assert.assertNull(results); + + versions.clear(); + + versions.put("cpe:/a:jruby:jruby:-", Boolean.FALSE); + identifiedVersion = new DependencyVersion("1.6.3"); + results = instance.getMatchingSoftware(versions, "springsource", "spring_framework", identifiedVersion); + Assert.assertNotNull(results); + + } + } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/DriverLoaderTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/DriverLoaderTest.java index 410fba3e0..e09e5c969 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/DriverLoaderTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nvdcve/DriverLoaderTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; /** * @@ -85,7 +86,8 @@ public class DriverLoaderTest { public void testLoad_String_String() throws Exception { String className = "com.mysql.jdbc.Driver"; //we know this is in target/test-classes - File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + //File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + File testClassPath = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar").getParentFile(); File driver = new File(testClassPath, "../../src/test/resources/mysql-connector-java-5.1.27-bin.jar"); assertTrue("MySQL Driver JAR file not found in src/test/resources?", driver.isFile()); @@ -108,7 +110,8 @@ public class DriverLoaderTest { public void testLoad_String_String_multiple_paths() throws Exception { final String className = "com.mysql.jdbc.Driver"; //we know this is in target/test-classes - final File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + //final File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + final File testClassPath = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar").getParentFile(); final File dir1 = new File(testClassPath, "../../src/test/"); final File dir2 = new File(testClassPath, "../../src/test/resources/"); final String paths = String.format("%s" + File.pathSeparator + "%s", dir1.getAbsolutePath(), dir2.getAbsolutePath()); @@ -130,7 +133,8 @@ public class DriverLoaderTest { public void testLoad_String_String_badClassName() throws Exception { String className = "com.mybad.jdbc.Driver"; //we know this is in target/test-classes - File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + //File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + File testClassPath = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar").getParentFile(); File driver = new File(testClassPath, "../../src/test/resources/mysql-connector-java-5.1.27-bin.jar"); assertTrue("MySQL Driver JAR file not found in src/test/resources?", driver.isFile()); @@ -144,7 +148,8 @@ public class DriverLoaderTest { public void testLoad_String_String_badPath() throws Exception { String className = "com.mysql.jdbc.Driver"; //we know this is in target/test-classes - File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + //File testClassPath = (new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath())).getParentFile(); + File testClassPath = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar").getParentFile(); File driver = new File(testClassPath, "../../src/test/bad/mysql-connector-java-5.1.27-bin.jar"); Driver d = DriverLoader.load(className, driver.getAbsolutePath()); } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/EngineVersionCheckTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/EngineVersionCheckTest.java new file mode 100644 index 000000000..197ab332b --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/EngineVersionCheckTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2014 OWASP. + * + * 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. + */ +package org.owasp.dependencycheck.data.update; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; +import mockit.Mock; +import mockit.MockUp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; +import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; +import org.owasp.dependencycheck.data.update.exception.UpdateException; +import org.owasp.dependencycheck.utils.DependencyVersion; + +/** + * + * @author Jeremy Long + */ +public class EngineVersionCheckTest extends BaseTest { + +// /** +// * Test of update method, of class EngineVersionCheck. +// */ +// @Test +// public void testUpdate() throws Exception { +// EngineVersionCheck instance = new EngineVersionCheck(); +// instance.update(); +// } + /** + * Test of shouldUpdate method, of class EngineVersionCheck. + */ + @Test + public void testShouldUpdate() throws Exception { + DatabaseProperties properties = new MockUp() { + final private Properties properties = new Properties(); + + @Mock + public void save(String key, String value) throws UpdateException { + properties.setProperty(key, value); + } + + @Mock + public String getProperty(String key) { + return properties.getProperty(key); + } + + }.getMockInstance(); + + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + + String updateToVersion = "1.2.6"; + String currentVersion = "1.2.6"; + long lastChecked = df.parse("2014-12-01").getTime(); + long now = df.parse("2014-12-01").getTime(); + + EngineVersionCheck instance = new EngineVersionCheck(); + boolean expResult = false; + instance.setUpdateToVersion(updateToVersion); + boolean result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + + updateToVersion = "1.2.5"; + currentVersion = "1.2.5"; + lastChecked = df.parse("2014-10-01").getTime(); + now = df.parse("2014-12-01").getTime(); + expResult = true; + instance.setUpdateToVersion(updateToVersion); + result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + //System.out.println(properties.getProperty(CURRENT_ENGINE_RELEASE)); + + updateToVersion = "1.2.5"; + currentVersion = "1.2.5"; + lastChecked = df.parse("2014-12-01").getTime(); + now = df.parse("2014-12-03").getTime(); + expResult = false; + instance.setUpdateToVersion(updateToVersion); + result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + + updateToVersion = "1.2.6"; + currentVersion = "1.2.5"; + lastChecked = df.parse("2014-12-01").getTime(); + now = df.parse("2014-12-03").getTime(); + expResult = true; + instance.setUpdateToVersion(updateToVersion); + result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + + updateToVersion = "1.2.5"; + currentVersion = "1.2.6"; + lastChecked = df.parse("2014-12-01").getTime(); + now = df.parse("2014-12-08").getTime(); + expResult = false; + instance.setUpdateToVersion(updateToVersion); + result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + + updateToVersion = ""; + currentVersion = "1.2.5"; + lastChecked = df.parse("2014-12-01").getTime(); + now = df.parse("2014-12-03").getTime(); + expResult = false; + instance.setUpdateToVersion(updateToVersion); + result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + + updateToVersion = ""; + currentVersion = "1.2.5"; + lastChecked = df.parse("2014-12-01").getTime(); + now = df.parse("2014-12-08").getTime(); + expResult = true; + instance.setUpdateToVersion(updateToVersion); + result = instance.shouldUpdate(lastChecked, now, properties, currentVersion); + assertEquals(expResult, result); + } + + /** + * Test of getCurrentReleaseVersion method, of class EngineVersionCheck. + */ + @Test + public void testGetCurrentReleaseVersion() { + EngineVersionCheck instance = new EngineVersionCheck(); + DependencyVersion minExpResult = new DependencyVersion("1.2.6"); + String release = instance.getCurrentReleaseVersion(); + DependencyVersion result = new DependencyVersion(release); + assertTrue(minExpResult.compareTo(result) <= 0); + } +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java index 24863e04b..f3e13388f 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java @@ -34,7 +34,8 @@ public class NvdCveUpdaterIntegrationTest extends BaseTest { public void setUp() throws Exception { int year = Calendar.getInstance().get(Calendar.YEAR); if (year <= 2014) { - File f = new File(NvdCveUpdaterIntegrationTest.class.getClassLoader().getResource("nvdcve-2.0-2014.xml").getPath()); + //File f = new File(NvdCveUpdaterIntegrationTest.class.getClassLoader().getResource("nvdcve-2.0-2014.xml").getPath()); + File f = BaseTest.getResourceAsFile(this, "nvdcve-2.0-2014.xml"); String baseURL = f.toURI().toURL().toString(); String modified12 = baseURL.replace("nvdcve-2.0-2014.xml", "nvdcve-modified.xml"); String modified20 = baseURL.replace("nvdcve-2.0-2014.xml", "nvdcve-2.0-modified.xml"); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/StandardUpdateIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/StandardUpdateIntegrationTest.java index 4a0a4a377..332a5cab2 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/StandardUpdateIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/StandardUpdateIntegrationTest.java @@ -18,8 +18,6 @@ package org.owasp.dependencycheck.data.update; import java.net.MalformedURLException; -import java.util.Calendar; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.junit.Test; import org.owasp.dependencycheck.BaseTest; @@ -47,26 +45,6 @@ public class StandardUpdateIntegrationTest extends BaseTest { instance.closeDataStores(); } - /** - * Test of withinRange method, of class StandardUpdate. - */ - @Test - public void testWithinRange() throws Exception { - Calendar c = Calendar.getInstance(); - - long current = c.getTimeInMillis(); - long lastRun = c.getTimeInMillis() - (3 * (1000 * 60 * 60 * 24)); - int range = 7; // 7 days - StandardUpdate instance = getStandardUpdateTask(); - boolean expResult = true; - boolean result = instance.withinRange(lastRun, current, range); - assertEquals(expResult, result); - - lastRun = c.getTimeInMillis() - (8 * (1000 * 60 * 60 * 24)); - expResult = false; - result = instance.withinRange(lastRun, current, range); - assertEquals(expResult, result); - } // test removed as it is duplicative of the EngineIntegrationTest and the NvdCveUpdaterIntergraionTest // /** // * Test of update method, of class StandardUpdate. @@ -77,7 +55,6 @@ public class StandardUpdateIntegrationTest extends BaseTest { // instance.update(); // //TODO make this an actual test // } - /** * Test of updatesNeeded method, of class StandardUpdate. */ diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/task/DownloadTaskTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/task/DownloadTaskTest.java new file mode 100644 index 000000000..fd7fb6de2 --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/task/DownloadTaskTest.java @@ -0,0 +1,75 @@ +/* + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.data.update.task; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.owasp.dependencycheck.data.nvdcve.CveDB; +import org.owasp.dependencycheck.data.update.NvdCveInfo; +import org.owasp.dependencycheck.utils.Settings; + +/** + * + * @author Jeremy Long + */ +public class DownloadTaskTest { + + public DownloadTaskTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + Settings.initialize(); + } + + @After + public void tearDown() { + Settings.cleanup(); + } + + /** + * Test of call method, of class DownloadTask. + */ + @Test + public void testCall() throws Exception { + NvdCveInfo cve = new NvdCveInfo(); + cve.setId("modified"); + cve.setNeedsUpdate(true); + cve.setUrl(Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL)); + cve.setOldSchemaVersionUrl(Settings.getString(Settings.KEYS.CVE_MODIFIED_12_URL)); + ExecutorService processExecutor = null; + CveDB cveDB = null; + DownloadTask instance = new DownloadTask(cve, processExecutor, cveDB, Settings.getInstance()); + Future result = instance.call(); + assertNull(result); + } +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_1_2_HandlerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_1_2_HandlerTest.java index 178211569..b4342d5bd 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_1_2_HandlerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_1_2_HandlerTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.dependency.VulnerableSoftware; /** @@ -60,7 +61,8 @@ public class NvdCve_1_2_HandlerTest { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); - File file = new File(this.getClass().getClassLoader().getResource("nvdcve-2012.xml").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("nvdcve-2012.xml").getPath()); + File file = BaseTest.getResourceAsFile(this, "nvdcve-2012.xml"); NvdCve12Handler instance = new NvdCve12Handler(); saxParser.parse(file, instance); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_2_0_HandlerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_2_0_HandlerTest.java index db003ee66..1ab52fbff 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_2_0_HandlerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/xml/NvdCve_2_0_HandlerTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; /** * @@ -59,7 +60,8 @@ public class NvdCve_2_0_HandlerTest { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); - File file = new File(this.getClass().getClassLoader().getResource("nvdcve-2.0-2012.xml").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("nvdcve-2.0-2012.xml").getPath()); + File file = BaseTest.getResourceAsFile(this, "nvdcve-2.0-2012.xml"); NvdCve20Handler instance = new NvdCve20Handler(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/dependency/DependencyTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/dependency/DependencyTest.java index 8062ddb11..d66055e06 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/dependency/DependencyTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/dependency/DependencyTest.java @@ -23,10 +23,13 @@ import java.util.Set; import org.junit.After; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; /** * @@ -150,9 +153,11 @@ public class DependencyTest { */ @Test public void testGetMd5sum() { - File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File file = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); + Dependency instance = new Dependency(file); -// assertEquals("89CE9E36AA9A9E03F1450936D2F4F8DD0F961F8B", result.getSha1sum()); + //assertEquals("89CE9E36AA9A9E03F1450936D2F4F8DD0F961F8B", result.getSha1sum()); String expResult = "C30B57142E1CCBC1EFD5CD15F307358F"; String result = instance.getMd5sum(); assertEquals(expResult, result); @@ -174,7 +179,8 @@ public class DependencyTest { */ @Test public void testGetSha1sum() { - File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File file = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); Dependency instance = new Dependency(file); String expResult = "89CE9E36AA9A9E03F1450936D2F4F8DD0F961F8B"; String result = instance.getSha1sum(); @@ -294,4 +300,34 @@ public class DependencyTest { EvidenceCollection result = instance.getVersionEvidence(); assertTrue(true); //this is just a getter setter pair. } + + /** + * Test of addAsEvidence method, of class Dependency. + */ + @Test + public void testAddAsEvidence() { + Dependency instance = new Dependency(); + MavenArtifact mavenArtifact = new MavenArtifact("group", "artifact", "version", "url"); + instance.addAsEvidence("pom", mavenArtifact, Confidence.HIGH); + assertTrue(instance.getEvidence().contains(Confidence.HIGH)); + assertFalse(instance.getEvidence().getEvidence("pom", "groupid").isEmpty()); + assertFalse(instance.getEvidence().getEvidence("pom", "artifactid").isEmpty()); + assertFalse(instance.getEvidence().getEvidence("pom", "version").isEmpty()); + assertFalse(instance.getIdentifiers().isEmpty()); + } + + /** + * Test of addAsEvidence method, of class Dependency. + */ + @Test + public void testAddAsEvidenceWithEmptyArtefact() { + Dependency instance = new Dependency(); + MavenArtifact mavenArtifact = new MavenArtifact(null, null, null, null); + instance.addAsEvidence("pom", mavenArtifact, Confidence.HIGH); + assertFalse(instance.getEvidence().contains(Confidence.HIGH)); + assertTrue(instance.getEvidence().getEvidence("pom", "groupid").isEmpty()); + assertTrue(instance.getEvidence().getEvidence("pom", "artifactid").isEmpty()); + assertTrue(instance.getEvidence().getEvidence("pom", "version").isEmpty()); + assertTrue(instance.getIdentifiers().isEmpty()); + } } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIntegrationTest.java index ac213659c..8ceb23748 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIntegrationTest.java @@ -105,8 +105,8 @@ public class ReportGeneratorIntegrationTest extends BaseTest { } /** - * Generates an XML report containing known vulnerabilities and realistic data and validates the generated XML - * document against the XSD. + * Generates an XML report containing known vulnerabilities and realistic data and validates the generated XML document + * against the XSD. * * @throws Exception */ @@ -120,9 +120,12 @@ public class ReportGeneratorIntegrationTest extends BaseTest { } String writeTo = "target/test-reports/Report.xml"; - File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); - File axis = new File(this.getClass().getClassLoader().getResource("axis2-adb-1.4.1.jar").getPath()); - File jetty = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath()); + //File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File struts = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); + //File axis = new File(this.getClass().getClassLoader().getResource("axis2-adb-1.4.1.jar").getPath()); + File axis = BaseTest.getResourceAsFile(this, "axis2-adb-1.4.1.jar"); + //File jetty = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath()); + File jetty = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar"); boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java index ea678aad3..4b3e25210 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; @@ -66,9 +67,11 @@ public class SuppressionHandlerTest { */ @Test public void testHandler() throws Exception { - File file = new File(this.getClass().getClassLoader().getResource("suppressions.xml").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("suppressions.xml").getPath()); + File file = BaseTest.getResourceAsFile(this, "suppressions.xml"); - File schema = new File(this.getClass().getClassLoader().getResource("schema/suppression.xsd").getPath()); + //File schema = new File(this.getClass().getClassLoader().getResource("schema/suppression.xsd").getPath()); + File schema = BaseTest.getResourceAsFile(this, "schema/suppression.xsd"); SuppressionHandler handler = new SuppressionHandler(); SAXParserFactory factory = SAXParserFactory.newInstance(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java index fd6c0e930..83a86c19f 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; /** * Test of the suppression parser. @@ -57,7 +58,8 @@ public class SuppressionParserTest { */ @Test public void testParseSuppressionRules() throws Exception { - File file = new File(this.getClass().getClassLoader().getResource("suppressions.xml").getPath()); + //File file = new File(this.getClass().getClassLoader().getResource("suppressions.xml").getPath()); + File file = BaseTest.getResourceAsFile(this, "suppressions.xml"); SuppressionParser instance = new SuppressionParser(); List result = instance.parseSuppressionRules(file); assertTrue(result.size() > 3); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java index 46f0edf0b..5c65b6614 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; @@ -422,7 +423,8 @@ public class SuppressionRuleTest { */ @Test public void testProcess() { - File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + //File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); + File struts = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); Dependency dependency = new Dependency(struts); dependency.addIdentifier("cpe", "cpe:/a:microsoft:.net_framework:4.5", "some url not needed for this test"); String sha1 = dependency.getSha1sum(); @@ -501,7 +503,8 @@ public class SuppressionRuleTest { */ @Test public void testProcessGAV() { - File spring = new File(this.getClass().getClassLoader().getResource("spring-security-web-3.0.0.RELEASE.jar").getPath()); + //File spring = new File(this.getClass().getClassLoader().getResource("spring-security-web-3.0.0.RELEASE.jar").getPath()); + File spring = BaseTest.getResourceAsFile(this, "spring-security-web-3.0.0.RELEASE.jar"); Dependency dependency = new Dependency(spring); dependency.addIdentifier("cpe", "cpe:/a:vmware:springsource_spring_framework:3.0.0", "some url not needed for this test"); dependency.addIdentifier("cpe", "cpe:/a:springsource:spring_framework:3.0.0", "some url not needed for this test"); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DateUtilTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DateUtilTest.java new file mode 100644 index 000000000..657b95737 --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DateUtilTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 OWASP. + * + * 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. + */ +package org.owasp.dependencycheck.utils; + +import java.util.Calendar; +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author Jeremy Long + */ +public class DateUtilTest { + + public DateUtilTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of withinDateRange method, of class DateUtil. + */ + @Test + public void testWithinDateRange() { + Calendar c = Calendar.getInstance(); + + long current = c.getTimeInMillis(); + long lastRun = c.getTimeInMillis() - (3 * (1000 * 60 * 60 * 24)); + int range = 7; // 7 days + boolean expResult = true; + boolean result = DateUtil.withinDateRange(lastRun, current, range); + assertEquals(expResult, result); + + lastRun = c.getTimeInMillis() - (8 * (1000 * 60 * 60 * 24)); + expResult = false; + result = DateUtil.withinDateRange(lastRun, current, range); + assertEquals(expResult, result); + } + +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionTest.java index 642bc99a4..c786cf0cc 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionTest.java @@ -165,6 +165,14 @@ public class DependencyVersionTest { version = new DependencyVersion("1.2.3.1"); assertEquals(-1, instance.compareTo(version)); + instance = new DependencyVersion("1.0.1n"); + version = new DependencyVersion("1.0.1m"); + assertEquals(1, instance.compareTo(version)); + version = new DependencyVersion("1.0.1n"); + assertEquals(0, instance.compareTo(version)); + version = new DependencyVersion("1.0.1o"); + assertEquals(-1, instance.compareTo(version)); + DependencyVersion[] dv = new DependencyVersion[7]; dv[0] = new DependencyVersion("2.1.3"); dv[1] = new DependencyVersion("2.1.3.r2"); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java index 9cb022a06..766e61f7e 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/utils/DependencyVersionUtilTest.java @@ -54,13 +54,13 @@ public class DependencyVersionUtilTest { * Test of parseVersion method, of class DependencyVersionUtil. */ @Test - public void testParseVersionFromFileName() { + public void testParseVersion() { final String[] fileName = {"something-0.9.5.jar", "lib2-1.1.jar", "lib1.5r4-someflag-R26.jar", "lib-1.2.5-dev-20050313.jar", "testlib_V4.4.0.jar", "lib-core-2.0.0-RC1-SNAPSHOT.jar", "lib-jsp-2.0.1_R114940.jar", "dev-api-2.3.11_R121413.jar", "lib-api-3.7-SNAPSHOT.jar", - "-", "", "1.3-beta", "6"}; + "-", "", "1.3-beta", "6", "openssl1.0.1c", "jsf-impl-2.2.8-02.jar"}; final String[] expResult = {"0.9.5", "1.1", "1.5.r4", "1.2.5", "4.4.0", "2.0.0.rc1", - "2.0.1.r114940", "2.3.11.r121413", "3.7", "-", null, "1.3.beta", "6"}; + "2.0.1.r114940", "2.3.11.r121413", "3.7", "-", null, "1.3.beta", "6", "1.0.1c", "2.2.8.02"}; for (int i = 0; i < fileName.length; i++) { final DependencyVersion version = DependencyVersionUtil.parseVersion(fileName[i]); diff --git a/dependency-check-core/src/test/resources/dependencycheck.properties b/dependency-check-core/src/test/resources/dependencycheck.properties index 61efb2407..a0a7c8aad 100644 --- a/dependency-check-core/src/test/resources/dependencycheck.properties +++ b/dependency-check-core/src/test/resources/dependencycheck.properties @@ -3,6 +3,9 @@ application.version=${pom.version} autoupdate=true max.download.threads=3 +# the url to obtain the current engine version from +engine.version.url=http://jeremylong.github.io/DependencyCheck/current.txt + #temp.directory defaults to System.getProperty("java.io.tmpdir") #temp.directory=[path to temp directory] @@ -47,11 +50,16 @@ cpe.meta.url=http://static.nvd.nist.gov/feeds/xml/cpe/dictionary/official-cpe-di cve.url.modified.validfordays=7 # the path to the modified nvd cve xml file. -cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml -cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml cve.startyear=2014 -cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml -cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml +cve.url-1.2.modified=https://nvd.nist.gov/download/nvdcve-Modified.xml.gz +#cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml +cve.url-2.0.modified=https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-Modified.xml.gz +#cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml +cve.url-1.2.base=https://nvd.nist.gov/download/nvdcve-%d.xml.gz +#cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml +cve.url-2.0.base=https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml.gz +#cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml + # the URL for searching Nexus for SHA-1 hashes and whether it's enabled analyzer.nexus.enabled=true @@ -59,3 +67,7 @@ analyzer.nexus.url=https://repository.sonatype.org/service/local/ # If set to true, the proxy will still ONLY be used if the proxy properties (proxy.url, proxy.port) # are configured analyzer.nexus.proxy=true + +# the URL for searching search.maven.org for SHA-1 and whether it's enabled +analyzer.central.enabled=true +analyzer.central.url=http://search.maven.org/solrsearch/select diff --git a/dependency-check-jenkins/pom.xml b/dependency-check-jenkins/pom.xml index 7c87b0e1d..a29bd4424 100644 --- a/dependency-check-jenkins/pom.xml +++ b/dependency-check-jenkins/pom.xml @@ -3,9 +3,8 @@ org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT - org.owasp dependency-check-jenkins Dependency-Check Jenkins Plugin http://wiki.jenkins-ci.org/display/JENKINS/OWASP+Dependency-Check+Plugin @@ -59,7 +58,6 @@ org.apache.maven.plugins maven-site-plugin - 3.3 org.apache.maven.doxia diff --git a/dependency-check-maven/pom.xml b/dependency-check-maven/pom.xml index dfbdb8e37..2453c5c7d 100644 --- a/dependency-check-maven/pom.xml +++ b/dependency-check-maven/pom.xml @@ -22,7 +22,7 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT dependency-check-maven @@ -40,9 +40,6 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. - - 3.0 - @@ -66,7 +63,6 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-plugin-plugin - 3.2 true dependency-check @@ -86,11 +82,26 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved.
+ + org.apache.maven.plugins + maven-surefire-plugin + + + + data.directory + ${project.build.directory}/dependency-check-data + + + temp.directory + ${project.build.directory}/temp + + + + true org.apache.maven.plugins maven-enforcer-plugin - 1.2 enforce-maven-3 @@ -110,169 +121,135 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.apache.maven.plugins - maven-site-plugin - 3.3 - - - org.apache.maven.doxia - doxia-module-markdown - 1.5 - - + maven-compiler-plugin + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.2 - true - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.7 - - - - index - summary - license - help - - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.2 - - dependency-check - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - Copyright© 2012-14 Jeremy Long. All Rights Reserved. - - - - default - - javadoc - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.1 - - - - dependency-updates-report - plugin-updates-report - - - - - - org.apache.maven.plugins - maven-jxr-plugin - 2.4 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.16 - - - - report-only - - - - - - org.codehaus.mojo - taglist-maven-plugin - 2.4 - - - - - Todo Work - - - todo - ignoreCase - - - FIXME - exact - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - false - ${basedir}/../src/main/config/checkstyle-checks.xml - ${basedir}/../src/main/config/checkstyle-header.txt - ${basedir}/../src/main/config/checkstyle-suppressions.xml - checkstyle.suppressions.file - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.1 - - 1.6 - true - utf-8 - - **/generated/**/*.java - **/HelpMojo.java - - - ../src/main/config/dcrules.xml - /rulesets/java/basic.xml - /rulesets/java/imports.xml - /rulesets/java/unusedcode.xml - - - - - org.codehaus.mojo - findbugs-maven-plugin - 2.5.3 - - + dependency-check org.apache.maven.plugins - maven-compiler-plugin - 3.1 + maven-javadoc-plugin + 2.9.1 - false - 1.6 - 1.6 + Copyright© 2012-15 Jeremy Long. All Rights Reserved. + + + + default + + javadoc + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + + dependency-updates-report + plugin-updates-report + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.4 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.16 + + + + report-only + + + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Todo Work + + + todo + ignoreCase + + + FIXME + exact + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.11 + + false + false + ${basedir}/../src/main/config/checkstyle-checks.xml + ${basedir}/../src/main/config/checkstyle-header.txt + ${basedir}/../src/main/config/checkstyle-suppressions.xml + checkstyle.suppressions.file + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.1 + + 1.6 + true + utf-8 + + **/generated/**/*.java + **/HelpMojo.java + + + ../src/main/config/dcrules.xml + /rulesets/java/basic.xml + /rulesets/java/imports.xml + /rulesets/java/unusedcode.xml + + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + - - + org.owasp @@ -287,27 +264,27 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.apache.maven maven-plugin-api - 3.0 + 3.2.5 org.apache.maven maven-settings - 3.0 + 3.2.5 org.apache.maven maven-core - 3.0 + 3.2.5 org.apache.maven.plugins maven-site-plugin - 3.0 + 3.4 org.apache.maven.plugin-tools maven-plugin-annotations - 3.0 + 3.4 compile @@ -316,16 +293,14 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. 3.0 - junit - junit - 4.11 + org.jmockit + jmockit test - jar org.apache.maven.plugin-testing maven-plugin-testing-harness - 2.1 + 3.3.0 test diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java new file mode 100644 index 000000000..a4ce79719 --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java @@ -0,0 +1,264 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.utils.Settings; + +/** + * Maven Plugin that checks project dependencies and the dependencies of all child modules to see if they have any known published + * vulnerabilities. + * + * @author Jeremy Long + */ +@Mojo( + name = "aggregate", + defaultPhase = LifecyclePhase.SITE, + aggregator = true, + threadSafe = true, + requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, + requiresOnline = true +) +public class AggregateMojo extends BaseDependencyCheckMojo { + + /** + * Logger field reference. + */ + private static final Logger LOGGER = Logger.getLogger(AggregateMojo.class.getName()); + + /** + * Executes the aggregate dependency-check goal. This runs dependency-check and generates the subsequent reports. + * + * @throws MojoExecutionException thrown if there is ane exception running the mojo + * @throws MojoFailureException thrown if dependency-check is configured to fail the build + */ + @Override + public void runCheck() throws MojoExecutionException, MojoFailureException { + final Engine engine = generateDataFile(); + + if (getProject() == getReactorProjects().get(getReactorProjects().size() - 1)) { + + //ensure that the .ser file was created for each. + for (MavenProject current : getReactorProjects()) { + final File dataFile = getDataFile(current); + if (dataFile == null) { //dc was never run on this project. write the ser to the target. + LOGGER.fine(String.format("Executing dependency-check on %s", current.getName())); + generateDataFile(engine, current); + } + } + + for (MavenProject current : getReactorProjects()) { + List dependencies = readDataFile(current); + if (dependencies == null) { + dependencies = new ArrayList(); + } + final Set childProjects = getDescendants(current); + for (MavenProject reportOn : childProjects) { + final List childDeps = readDataFile(reportOn); + if (childDeps != null && !childDeps.isEmpty()) { + LOGGER.fine(String.format("Adding %d dependencies from %s", childDeps.size(), reportOn.getName())); + dependencies.addAll(childDeps); + } else { + LOGGER.fine(String.format("No dependencies read for %s", reportOn.getName())); + } + } + engine.getDependencies().clear(); + engine.getDependencies().addAll(dependencies); + final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer(); + try { + LOGGER.fine(String.format("Dependency count pre-bundler: %s", engine.getDependencies().size())); + bundler.analyze(null, engine); + LOGGER.fine(String.format("Dependency count post-bundler: %s", engine.getDependencies().size())); + } catch (AnalysisException ex) { + LOGGER.log(Level.WARNING, "An error occured grouping the dependencies; duplicate entries may exist in the report", ex); + LOGGER.log(Level.FINE, "Bundling Exception", ex); + } + + File outputDir = getCorrectOutputDirectory(current); + if (outputDir == null) { + //in some regards we shouldn't be writting this, but we are anyway. + //we shouldn't write this because nothing is configured to generate this report. + outputDir = new File(current.getBuild().getDirectory()); + } + writeReports(engine, current, outputDir); + } + } + engine.cleanup(); + Settings.cleanup(); + } + + /** + * Returns a set containing all the descendant projects of the given project. + * + * @param project the project for which all descendants will be returned + * @return the set of descendant projects + */ + protected Set getDescendants(MavenProject project) { + if (project == null) { + return Collections.emptySet(); + } + final Set descendants = new HashSet(); + int size = 0; + LOGGER.fine(String.format("Collecting descendants of %s", project.getName())); + for (String m : project.getModules()) { + for (MavenProject mod : getReactorProjects()) { + try { + File mpp = new File(project.getBasedir(), m); + mpp = mpp.getCanonicalFile(); + if (mpp.compareTo(mod.getBasedir()) == 0) { + if (descendants.add(mod)) { + LOGGER.fine(String.format("Decendent module %s added", mod.getName())); + } + } + } catch (IOException ex) { + LOGGER.log(Level.FINE, "Unable to determine module path", ex); + } + } + } + do { + size = descendants.size(); + for (MavenProject p : getReactorProjects()) { + if (project.equals(p.getParent()) || descendants.contains(p.getParent())) { + if (descendants.add(p)) { + LOGGER.fine(String.format("Decendent %s added", p.getName())); + } + for (MavenProject modTest : getReactorProjects()) { + if (p.getModules() != null && p.getModules().contains(modTest.getName())) { + if (descendants.add(modTest)) { + LOGGER.fine(String.format("Decendent %s added", modTest.getName())); + } + } + } + } + for (MavenProject dec : descendants) { + for (String mod : dec.getModules()) { + try { + File mpp = new File(dec.getBasedir(), mod); + mpp = mpp.getCanonicalFile(); + if (mpp.compareTo(p.getBasedir()) == 0) { + if (descendants.add(p)) { + LOGGER.fine(String.format("Decendent module %s added", p.getName())); + } + } + } catch (IOException ex) { + LOGGER.log(Level.FINE, "Unable to determine module path", ex); + } + } + } + } + } while (size != 0 && size != descendants.size()); + LOGGER.fine(String.format("%s has %d children", project, descendants.size())); + return descendants; + } + + /** + * Test if the project has pom packaging + * + * @param mavenProject Project to test + * @return true if it has a pom packaging; otherwise false + */ + protected boolean isMultiModule(MavenProject mavenProject) { + return "pom".equals(mavenProject.getPackaging()); + } + + /** + * Initilizes the engine, runs a scan, and writes the serialized dependencies to disk. + * + * @return the Engine used to execute dependency-check + * @throws MojoExecutionException thrown if there is an exception running the mojo + * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are identified. + */ + protected Engine generateDataFile() throws MojoExecutionException, MojoFailureException { + final Engine engine; + try { + engine = initializeEngine(); + } catch (DatabaseException ex) { + LOGGER.log(Level.FINE, "Database connection error", ex); + throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + } + return generateDataFile(engine, getProject()); + } + + /** + * Runs dependency-check's Engine and writes the serialized dependencies to disk. + * + * @param engine the Engine to use when scanning. + * @param project the project to scan and generate the data file for + * @return the Engine used to execute dependency-check + * @throws MojoExecutionException thrown if there is an exception running the mojo + * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are identified. + */ + protected Engine generateDataFile(Engine engine, MavenProject project) throws MojoExecutionException, MojoFailureException { + LOGGER.fine(String.format("Begin Scanning: %s", project.getName())); + engine.getDependencies().clear(); + engine.resetFileTypeAnalyzers(); + scanArtifacts(project, engine); + engine.analyzeDependencies(); + final File target = new File(project.getBuild().getDirectory()); + writeDataFile(project, target, engine.getDependencies()); + showSummary(project, engine.getDependencies()); + checkForFailure(engine.getDependencies()); + return engine; + } + + @Override + public boolean canGenerateReport() { + return true; //aggregate always returns true for now - we can look at a more complicated/acurate solution later + } + + /** + * Returns the report name. + * + * @param locale the location + * @return the report name + */ + public String getName(Locale locale) { + return "dependency-check:aggregate"; + } + + /** + * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * + * @param locale The Locale to get the description for + * @return the description + */ + public String getDescription(Locale locale) { + return "Generates an aggregate report of all child Maven projects providing details on any " + + "published vulnerabilities within project dependencies. This report is a best " + + "effort and may contain false positives and false negatives."; + } +} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java similarity index 53% rename from dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java rename to dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 26b9ef4c5..50e52d031 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/DependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright (c) 2013 Jeremy Long. All Rights Reserved. + * Copyright (c) 2014 Jeremy Long. All Rights Reserved. */ package org.owasp.dependencycheck.maven; @@ -29,45 +29,47 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.List; import java.util.Locale; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.reporting.MavenReport; import org.apache.maven.reporting.MavenReportException; import org.apache.maven.settings.Proxy; -import org.owasp.dependencycheck.Engine; -import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer; -import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; +import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; +import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.reporting.ReportGenerator; +import org.owasp.dependencycheck.utils.DependencyVersion; import org.owasp.dependencycheck.utils.LogUtils; import org.owasp.dependencycheck.utils.Settings; /** - * Maven Plugin that checks project dependencies to see if they have any known published vulnerabilities. * * @author Jeremy Long */ -@Mojo(name = "check", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true, - requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM, - requiresOnline = true) -public class DependencyCheckMojo extends ReportAggregationMojo { +public abstract class BaseDependencyCheckMojo extends AbstractMojo implements MavenReport { // /** * Logger field reference. */ - private static final Logger LOGGER = Logger.getLogger(DependencyCheckMojo.class.getName()); + private static final Logger LOGGER = Logger.getLogger(BaseDependencyCheckMojo.class.getName()); /** * The properties file location. */ @@ -81,45 +83,88 @@ public class DependencyCheckMojo extends ReportAggregationMojo { */ private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern(); /** - * The dependency-check engine used to scan the project. + * Sets whether or not the external report format should be used. */ - private Engine engine = null; - // + @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true) + private String dataFileName; + // // + /** + * The Maven Project Object. + */ + @Component + private MavenProject project; + /** + * The meta data source for retrieving artifact version information. + */ + @Component + private ArtifactMetadataSource metadataSource; + /** + * A reference to the local repository. + */ + @Parameter(property = "localRepository", readonly = true) + private ArtifactRepository localRepository; + /** + * References to the remote repositories. + */ + @Parameter(property = "project.remoteArtifactRepositories", readonly = true) + private List remoteRepositories; + /** + * List of Maven project of the current build + */ + @Parameter(readonly = true, required = true, property = "reactorProjects") + private List reactorProjects; /** * The path to the verbose log. */ - @Parameter(property = "logfile", defaultValue = "") + @SuppressWarnings("CanBeFinal") + @Parameter(property = "logFile", defaultValue = "") private String logFile = null; + + //"project.reporting.outputDirectory" /** * The output directory. This generally maps to "target". */ @Parameter(defaultValue = "${project.build.directory}", required = true) private File outputDirectory; /** - * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 - * which means since the CVSS scores are 0-10, by default the build will never fail. + * Specifies the destination directory for the generated Dependency-Check report. This generally maps to "target/site". + */ + //Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true) + @Parameter(property = "project.reporting.outputDirectory", required = true) + private File reportOutputDirectory; + /** + * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which + * means since the CVSS scores are 0-10, by default the build will never fail. */ @SuppressWarnings("CanBeFinal") @Parameter(property = "failBuildOnCVSS", defaultValue = "11", required = true) private float failBuildOnCVSS = 11; /** - * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to - * false. Default is true. + * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. Default + * is true. */ @SuppressWarnings("CanBeFinal") @Parameter(property = "autoupdate", defaultValue = "true", required = true) private boolean autoUpdate = true; /** - * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this - * within the Site plugin unless the externalReport is set to true. Default is HTML. + * Generate aggregate reports in multi-module projects. + * + * @deprecated use the aggregate goal instead + */ + @Parameter(property = "aggregate", defaultValue = "false") + @Deprecated + private boolean aggregate; + /** + * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the + * Site plug-in unless the externalReport is set to true. Default is HTML. */ @SuppressWarnings("CanBeFinal") @Parameter(property = "format", defaultValue = "HTML", required = true) private String format = "HTML"; /** - * The maven settings. + * The Maven settings. */ @Parameter(property = "mavenSettings", defaultValue = "${settings}", required = false) private org.apache.maven.settings.Settings mavenSettings; @@ -178,14 +223,22 @@ public class DependencyCheckMojo extends ReportAggregationMojo { @Parameter(property = "nuspecAnalyzerEnabled", defaultValue = "true", required = false) private boolean nuspecAnalyzerEnabled = true; + /** + * Whether or not the Central Analyzer is enabled. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "centralAnalyzerEnabled", defaultValue = "true", required = false) + private boolean centralAnalyzerEnabled = true; + /** * Whether or not the Nexus Analyzer is enabled. */ @SuppressWarnings("CanBeFinal") @Parameter(property = "nexusAnalyzerEnabled", defaultValue = "true", required = false) private boolean nexusAnalyzerEnabled = true; + /** - * Whether or not the Nexus Analyzer is enabled. + * The URL of a Nexus Pro server. */ @Parameter(property = "nexusUrl", defaultValue = "", required = false) private String nexusUrl; @@ -293,82 +346,267 @@ public class DependencyCheckMojo extends ReportAggregationMojo { @Deprecated private String externalReport = null; // + // + /** - * Constructs a new dependency-check-mojo. + * Executes dependency-check. + * + * @throws MojoExecutionException thrown if there is an exception executing the mojo + * @throws MojoFailureException thrown if dependency-check failed the build */ - public DependencyCheckMojo() { - final InputStream in = DependencyCheckMojo.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE); - LogUtils.prepareLogger(in, logFile); + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + validateAggregate(); + project.setContextValue(getOutputDirectoryContextKey(), this.outputDirectory); + runCheck(); } /** - * Executes the Dependency-Check on the dependent libraries. + * Checks if the aggregate configuration parameter has been set to true. If it has a MojoExecutionException is thrown because + * the aggregate configuration parameter is no longer supported. * - * @return the Engine used to scan the dependencies. - * @throws DatabaseException thrown if there is an exception connecting to the database + * @throws MojoExecutionException thrown if aggregate is set to true */ - private Engine executeDependencyCheck() throws DatabaseException { - return executeDependencyCheck(getProject()); + private void validateAggregate() throws MojoExecutionException { + if (aggregate) { + final String msg = "Aggregate configuration detected - as of dependency-check 1.2.8 this no longer supported. " + + "Please use the aggregate goal instead."; + throw new MojoExecutionException(msg); + } } /** - * Executes the Dependency-Check on the dependent libraries. + * Generates the Dependency-Check Site Report. * - * @param project the project to run dependency-check on - * @return the Engine used to scan the dependencies. - * @throws DatabaseException thrown if there is an exception connecting to the database + * @param sink the sink to write the report to + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, java.util.Locale)} instead. */ - private Engine executeDependencyCheck(MavenProject project) throws DatabaseException { - final Engine localEngine = initializeEngine(); + @Deprecated + public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException { + generate((Sink) sink, locale); + } - final Set artifacts = project.getArtifacts(); - for (Artifact a : artifacts) { + /** + * Generates the Dependency-Check Site Report. + * + * @param sink the sink to write the report to + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + */ + public void generate(Sink sink, Locale locale) throws MavenReportException { + try { + validateAggregate(); + } catch (MojoExecutionException ex) { + throw new MavenReportException(ex.getMessage()); + } + project.setContextValue(getOutputDirectoryContextKey(), getReportOutputDirectory()); + try { + runCheck(); + } catch (MojoExecutionException ex) { + throw new MavenReportException(ex.getMessage(), ex); + } catch (MojoFailureException ex) { + LOGGER.warning("Vulnerabilities were identifies that exceed the CVSS threshold for failing the build"); + } + } + + /** + * Returns the correct output directory depending on if a site is being executed or not. + * + * @return the directory to write the report(s) + * @throws MojoExecutionException thrown if there is an error loading the file path + */ + protected File getCorrectOutputDirectory() throws MojoExecutionException { + return getCorrectOutputDirectory(this.project); + } + + /** + * Returns the correct output directory depending on if a site is being executed or not. + * + * @param current the Maven project to get the output directory from + * @return the directory to write the report(s) + */ + protected File getCorrectOutputDirectory(MavenProject current) { + final Object obj = current.getContextValue(getOutputDirectoryContextKey()); + if (obj != null) { + if (obj instanceof File) { + return (File) obj; + } + } + File target = new File(current.getBuild().getDirectory()); + if (target.getParentFile() != null && "target".equals(target.getParentFile().getName())) { + target = target.getParentFile(); + } + return target; + } + + /** + * Returns the correct output directory depending on if a site is being executed or not. + * + * @param current the Maven project to get the output directory from + * @return the directory to write the report(s) + */ + protected File getDataFile(MavenProject current) { + LOGGER.fine(String.format("Getting data filefor %s using key '%s'", current.getName(), getDataFileContextKey())); + final Object obj = current.getContextValue(getDataFileContextKey()); + if (obj != null) { + if (obj instanceof File) { + return (File) obj; + } + } else { + LOGGER.fine("Context value not found"); + } + return null; + } + + /** + * Scans the project's artifacts and adds them to the engine's dependency list. + * + * @param project the project to scan the dependencies of + * @param engine the engine to use to scan the dependencies + */ + protected void scanArtifacts(MavenProject project, Engine engine) { + for (Artifact a : project.getArtifacts()) { if (excludeFromScan(a)) { continue; } - - localEngine.scan(a.getFile().getAbsolutePath()); + final List deps = engine.scan(a.getFile().getAbsoluteFile()); + if (deps != null) { + if (deps.size() == 1) { + final Dependency d = deps.get(0); + if (d != null) { + final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion()); + d.addAsEvidence("pom", ma, Confidence.HIGHEST); + d.addProjectReference(project.getName()); + LOGGER.fine(String.format("Adding project reference %s on dependency %s", project.getName(), + d.getDisplayFileName())); + if (metadataSource != null) { + try { + final DependencyVersion currentVersion = new DependencyVersion(a.getVersion()); + final List versions = metadataSource.retrieveAvailableVersions(a, + localRepository, remoteRepositories); + for (ArtifactVersion av : versions) { + final DependencyVersion newVersion = new DependencyVersion(av.toString()); + if (currentVersion.compareTo(newVersion) < 0) { + d.addAvailableVersion(av.toString()); + } + } + } catch (ArtifactMetadataRetrievalException ex) { + LOGGER.log(Level.WARNING, + "Unable to check for new versions of dependencies; see the log for more details."); + LOGGER.log(Level.FINE, null, ex); + } catch (Throwable t) { + LOGGER.log(Level.WARNING, + "Unexpected error occured checking for new versions; see the log for more details."); + LOGGER.log(Level.FINE, "", t); + } + } + } + } else { + final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s:%s:%s'", + a.getGroupId(), a.getArtifactId(), a.getVersion()); + LOGGER.fine(msg); + } + } } - localEngine.analyzeDependencies(); - - return localEngine; } + /** + * Executes the dependency-check scan and generates the necassary report. + * + * @throws MojoExecutionException thrown if there is an exception running the scan + * @throws MojoFailureException thrown if dependency-check is configured to fail the build + */ + public abstract void runCheck() throws MojoExecutionException, MojoFailureException; + + /** + * Sets the Reporting output directory. + * + * @param directory the output directory + */ + @Override + public void setReportOutputDirectory(File directory) { + reportOutputDirectory = directory; + } + + /** + * Returns the report output directory. + * + * @return the report output directory + */ + @Override + public File getReportOutputDirectory() { + return reportOutputDirectory; + } + + /** + * Returns the output directory. + * + * @return the output directory + */ + public File getOutputDirectory() { + return outputDirectory; + } + + /** + * Returns whether this is an external report. This method always returns true. + * + * @return true + */ + @Override + public final boolean isExternalReport() { + return true; + } + + /** + * Returns the output name. + * + * @return the output name + */ + public String getOutputName() { + if ("HTML".equalsIgnoreCase(this.format) || "ALL".equalsIgnoreCase(this.format)) { + return "dependency-check-report"; + } else if ("XML".equalsIgnoreCase(this.format)) { + return "dependency-check-report.xml#"; + } else if ("VULN".equalsIgnoreCase(this.format)) { + return "dependency-check-vulnerability"; + } else { + LOGGER.log(Level.WARNING, "Unknown report format used during site generation."); + return "dependency-check-report"; + } + } + + /** + * Returns the category name. + * + * @return the category name + */ + public String getCategoryName() { + return MavenReport.CATEGORY_PROJECT_REPORTS; + } + // + /** * Initializes a new Engine that can be used for scanning. * * @return a newly instantiated Engine * @throws DatabaseException thrown if there is a database exception */ - private Engine initializeEngine() throws DatabaseException { + protected Engine initializeEngine() throws DatabaseException { + final InputStream in = BaseDependencyCheckMojo.class + .getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE); + LogUtils.prepareLogger(in, logFile); + populateSettings(); - final Engine localEngine = new Engine(); - return localEngine; + + return new Engine(this.project, + this.reactorProjects); } /** - * Tests is the artifact should be included in the scan (i.e. is the dependency in a scope that is being scanned). - * - * @param a the Artifact to test - * @return true if the artifact is in an excluded scope; otherwise false - */ - private boolean excludeFromScan(Artifact a) { - if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) { - return true; - } - if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) { - return true; - } - if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) { - return true; - } - return false; - } - - // - /** - * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system - * properties required to change the proxy url, port, and connection timeout. + * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties + * required to change the proxy url, port, and connection timeout. */ private void populateSettings() { Settings.initialize(); @@ -396,8 +634,7 @@ public class DependencyCheckMojo extends ReportAggregationMojo { } if (proxyUrl != null && !proxyUrl.isEmpty()) { - LOGGER.warning("Deprecated configuration detected, proxyUrl will be ignored; use the maven settings " - + "to configure the proxy instead"); + LOGGER.warning("Deprecated configuration detected, proxyUrl will be ignored; use the maven settings " + "to configure the proxy instead"); } final Proxy proxy = getMavenProxy(); if (proxy != null) { @@ -427,6 +664,8 @@ public class DependencyCheckMojo extends ReportAggregationMojo { //NUSPEC ANALYZER Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, nuspecAnalyzerEnabled); //NEXUS ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled); + //NEXUS ANALYZER Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled); if (nexusUrl != null && !nexusUrl.isEmpty()) { Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl); @@ -492,7 +731,7 @@ public class DependencyCheckMojo extends ReportAggregationMojo { private Proxy getMavenProxy() { if (mavenSettings != null) { final List proxies = mavenSettings.getProxies(); - if (proxies != null && proxies.size() > 0) { + if (proxies != null && !proxies.isEmpty()) { if (mavenSettingsProxyId != null) { for (Proxy proxy : proxies) { if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) { @@ -502,245 +741,98 @@ public class DependencyCheckMojo extends ReportAggregationMojo { } else if (proxies.size() == 1) { return proxies.get(0); } else { - LOGGER.warning("Multiple proxy defentiions exist in the Maven settings. In the dependency-check " - + "configuration set the maveSettingsProxyId so that the correct proxy will be used."); + LOGGER.warning("Multiple proxy definitions exist in the Maven settings. In the dependency-check " + + "configuration set the mavenSettingsProxyId so that the correct proxy will be used."); throw new IllegalStateException("Ambiguous proxy definition"); } } } return null; } - // /** - * Executes the dependency-check and generates the report. + * Tests is the artifact should be included in the scan (i.e. is the dependency in a scope that is being scanned). * - * @throws MojoExecutionException if a maven exception occurs - * @throws MojoFailureException thrown if a CVSS score is found that is higher then the configured level + * @param a the Artifact to test + * @return true if the artifact is in an excluded scope; otherwise false */ - @Override - protected void performExecute() throws MojoExecutionException, MojoFailureException { - try { - engine = executeDependencyCheck(); - ReportingUtil.generateExternalReports(engine, outputDirectory, getProject().getName(), format); - if (this.showSummary) { - showSummary(engine.getDependencies()); - } - if (this.failBuildOnCVSS <= 10) { - checkForFailure(engine.getDependencies()); - } - } catch (DatabaseException ex) { - LOGGER.log(Level.SEVERE, - "Unable to connect to the dependency-check database; analysis has stopped"); - LOGGER.log(Level.FINE, "", ex); - } - } - - @Override - protected void postExecute() throws MojoExecutionException, MojoFailureException { - try { - super.postExecute(); - } finally { - cleanupEngine(); - } - } - - @Override - protected void postGenerate() throws MavenReportException { - try { - super.postGenerate(); - } finally { - cleanupEngine(); - } - } - - /** - * Calls engine.cleanup() to release resources. - */ - private void cleanupEngine() { - if (engine != null) { - engine.cleanup(); - engine = null; - } - Settings.cleanup(true); - } - - /** - * Generates the Dependency-Check Site Report. - * - * @param locale the locale to use when generating the report - * @throws MavenReportException if a maven report exception occurs - */ - @Override - protected void executeNonAggregateReport(Locale locale) throws MavenReportException { - - final List deps = readDataFile(); - if (deps != null) { - try { - engine = initializeEngine(); - engine.getDependencies().addAll(deps); - } catch (DatabaseException ex) { - final String msg = String.format("An unrecoverable exception with the dependency-check initialization occured while scanning %s", - getProject().getName()); - throw new MavenReportException(msg, ex); - } - } else { - try { - engine = executeDependencyCheck(); - } catch (DatabaseException ex) { - final String msg = String.format("An unrecoverable exception with the dependency-check scan occured while scanning %s", - getProject().getName()); - throw new MavenReportException(msg, ex); - } - } - ReportingUtil.generateExternalReports(engine, getReportOutputDirectory(), getProject().getName(), format); - } - - @Override - protected void executeAggregateReport(MavenProject project, Locale locale) throws MavenReportException { - List deps = readDataFile(project); - if (deps != null) { - try { - engine = initializeEngine(); - engine.getDependencies().addAll(deps); - } catch (DatabaseException ex) { - final String msg = String.format("An unrecoverable exception with the dependency-check initialization occured while scanning %s", - project.getName()); - throw new MavenReportException(msg, ex); - } - } else { - try { - engine = executeDependencyCheck(project); - } catch (DatabaseException ex) { - final String msg = String.format("An unrecoverable exception with the dependency-check scan occured while scanning %s", - project.getName()); - throw new MavenReportException(msg, ex); - } - } - for (MavenProject child : getAllChildren(project)) { - deps = readDataFile(child); - if (deps == null) { - final String msg = String.format("Unable to include information on %s in the dependency-check aggregate report", child.getName()); - LOGGER.severe(msg); - } else { - engine.getDependencies().addAll(deps); - } - } - final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer(); - try { - bundler.analyze(null, engine); - } catch (AnalysisException ex) { - LOGGER.log(Level.WARNING, "An error occured grouping the dependencies; duplicate entries may exist in the report", ex); - LOGGER.log(Level.FINE, "Bundling Exception", ex); - } - final File outputDir = getReportOutputDirectory(project); - if (outputDir != null) { - ReportingUtil.generateExternalReports(engine, outputDir, project.getName(), format); - } - } - - // - /** - * Returns the output name. - * - * @return the output name - */ - public String getOutputName() { - if ("HTML".equalsIgnoreCase(this.format) - || "ALL".equalsIgnoreCase(this.format)) { - return "dependency-check-report"; - } else if ("XML".equalsIgnoreCase(this.format)) { - return "dependency-check-report.xml#"; - } else if ("VULN".equalsIgnoreCase(this.format)) { - return "dependency-check-vulnerability"; - } else { - LOGGER.log(Level.WARNING, "Unknown report format used during site generation."); - return "dependency-check-report"; - } - } - - /** - * Returns the category name. - * - * @return the category name - */ - public String getCategoryName() { - return MavenReport.CATEGORY_PROJECT_REPORTS; - } - - /** - * Returns the report name. - * - * @param locale the location - * @return the report name - */ - public String getName(Locale locale) { - return "dependency-check"; - } - - /** - * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. - * - * @param locale The Locale to get the description for - * @return the description - */ - public String getDescription(Locale locale) { - return "A report providing details on any published " - + "vulnerabilities within project dependencies. This report is a best effort but may contain " - + "false positives and false negatives."; - } - - /** - * Returns whether or not a report can be generated. - * - * @return true if a report can be generated; otherwise false - */ - public boolean canGenerateReport() { - if (canGenerateAggregateReport() || (isAggregate() && isMultiModule())) { + protected boolean excludeFromScan(Artifact a) { + if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) { return true; } - if (canGenerateNonAggregateReport()) { + if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) { + return true; + } + if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) { return true; - } else { - final String msg; - if (getProject().getArtifacts().size() > 0) { - msg = "No project dependencies exist in the included scope - dependency-check:check is unable to generate a report."; - } else { - msg = "No project dependencies exist - dependency-check:check is unable to generate a report."; - } - LOGGER.warning(msg); } - return false; } /** - * Returns whether or not a non-aggregate report can be generated. + * Returns a reference to the current project. This method is used instead of auto-binding the project via component + * annotation in concrete implementations of this. If the child has a @Component MavenProject project; defined + * then the abstract class (i.e. this class) will not have access to the current project (just the way Maven works with the + * binding). * - * @return true if a non-aggregate report can be generated; otherwise false + * @return returns a reference to the current project */ - @Override - protected boolean canGenerateNonAggregateReport() { - boolean ability = false; - for (Artifact a : getProject().getArtifacts()) { - if (!excludeFromScan(a)) { - ability = true; - break; - } - } - return ability; + protected MavenProject getProject() { + return project; } /** - * Returns whether or not an aggregate report can be generated. + * Returns the list of Maven Projects in this build. * - * @return true if an aggregate report can be generated; otherwise false + * @return the list of Maven Projects in this build */ - @Override - protected boolean canGenerateAggregateReport() { - return isAggregate() && isLastProject(); + protected List getReactorProjects() { + return reactorProjects; + } + + /** + * Returns the report format. + * + * @return the report format + */ + protected String getFormat() { + return format; + } + + /** + * Generates the reports for a given dependency-check engine. + * + * @param engine a dependency-check engine + * @param p the maven project + * @param outputDir the directory path to write the report(s). + */ + protected void writeReports(Engine engine, MavenProject p, File outputDir) { + DatabaseProperties prop = null; + CveDB cve = null; + try { + cve = new CveDB(); + cve.open(); + prop = cve.getDatabaseProperties(); + } catch (DatabaseException ex) { + LOGGER.log(Level.FINE, "Unable to retrieve DB Properties", ex); + } finally { + if (cve != null) { + cve.close(); + } + } + final ReportGenerator r = new ReportGenerator(p.getName(), engine.getDependencies(), engine.getAnalyzers(), prop); + try { + r.generateReports(outputDir.getAbsolutePath(), format); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, + "Unexpected exception occurred during analysis; please see the verbose error log for more details."); + LOGGER.log(Level.FINE, null, ex); + } catch (Throwable ex) { + LOGGER.log(Level.SEVERE, + "Unexpected exception occurred during analysis; please see the verbose error log for more details."); + LOGGER.log(Level.FINE, null, ex); + } } - // // /** @@ -750,96 +842,131 @@ public class DependencyCheckMojo extends ReportAggregationMojo { * @param dependencies the list of dependency objects * @throws MojoFailureException thrown if a CVSS score is found that is higher then the threshold set */ - private void checkForFailure(List dependencies) throws MojoFailureException { - final StringBuilder ids = new StringBuilder(); - for (Dependency d : dependencies) { - boolean addName = true; - for (Vulnerability v : d.getVulnerabilities()) { - if (v.getCvssScore() >= failBuildOnCVSS) { - if (addName) { - addName = false; - ids.append(NEW_LINE).append(d.getFileName()).append(": "); - ids.append(v.getName()); - } else { - ids.append(", ").append(v.getName()); + protected void checkForFailure(List dependencies) throws MojoFailureException { + if (failBuildOnCVSS <= 10) { + final StringBuilder ids = new StringBuilder(); + for (Dependency d : dependencies) { + boolean addName = true; + for (Vulnerability v : d.getVulnerabilities()) { + if (v.getCvssScore() >= failBuildOnCVSS) { + if (addName) { + addName = false; + ids.append(NEW_LINE).append(d.getFileName()).append(": "); + ids.append(v.getName()); + } else { + ids.append(", ").append(v.getName()); + } } } } - } - if (ids.length() > 0) { - final String msg = String.format("%n%nDependency-Check Failure:%n" - + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n" - + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString()); - throw new MojoFailureException(msg); + if (ids.length() > 0) { + final String msg = String.format("%n%nDependency-Check Failure:%n" + + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n" + + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString()); + throw new MojoFailureException(msg); + } } } /** * Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries. * + * @param mp the Maven project for which the summary is shown * @param dependencies a list of dependency objects */ - private void showSummary(List dependencies) { - final StringBuilder summary = new StringBuilder(); - for (Dependency d : dependencies) { - boolean firstEntry = true; - final StringBuilder ids = new StringBuilder(); - for (Vulnerability v : d.getVulnerabilities()) { - if (firstEntry) { - firstEntry = false; - } else { - ids.append(", "); - } - ids.append(v.getName()); - } - if (ids.length() > 0) { - summary.append(d.getFileName()).append(" ("); - firstEntry = true; - for (Identifier id : d.getIdentifiers()) { + protected void showSummary(MavenProject mp, List dependencies) { + if (showSummary) { + final StringBuilder summary = new StringBuilder(); + for (Dependency d : dependencies) { + boolean firstEntry = true; + final StringBuilder ids = new StringBuilder(); + for (Vulnerability v : d.getVulnerabilities()) { if (firstEntry) { firstEntry = false; } else { - summary.append(", "); + ids.append(", "); } - summary.append(id.getValue()); + ids.append(v.getName()); } - summary.append(") : ").append(ids).append(NEW_LINE); + if (ids.length() > 0) { + summary.append(d.getFileName()).append(" ("); + firstEntry = true; + for (Identifier id : d.getIdentifiers()) { + if (firstEntry) { + firstEntry = false; + } else { + summary.append(", "); + } + summary.append(id.getValue()); + } + summary.append(") : ").append(ids).append(NEW_LINE); + } + } + if (summary.length() > 0) { + final String msg = String.format("%n%n" + "One or more dependencies were identified with known vulnerabilities in %s:%n%n%s" + + "%n%nSee the dependency-check report for more details.%n%n", mp.getName(), summary.toString()); + LOGGER.log(Level.WARNING, msg); } } - if (summary.length() > 0) { - final String msg = String.format("%n%n" - + "One or more dependencies were identified with known vulnerabilities:%n%n%s" - + "%n%nSee the dependency-check report for more details.%n%n", summary.toString()); - LOGGER.log(Level.WARNING, msg); - } } - // + // // + /** + * Returns the key used to store the path to the data file that is saved by writeDataFile(). This key is used in + * the MavenProject.(set|get)ContextValue. + * + * @return the key used to store the path to the data file + */ + protected String getDataFileContextKey() { + return "dependency-check-path-" + dataFileName; + } + + /** + * Returns the key used to store the path to the output directory. When generating the report in the + * executeAggregateReport() the output directory should be obtained by using this key. + * + * @return the key used to store the path to the output directory + */ + protected String getOutputDirectoryContextKey() { + return "dependency-output-dir-" + dataFileName; + } + /** * Writes the scan data to disk. This is used to serialize the scan data between the "check" and "aggregate" phase. * - * @return the File object referencing the data file that was written + * @param mp the mMven project for which the data file was created + * @param writeTo the directory to write the data file + * @param dependencies the list of dependencies to serialize */ - @Override - protected File writeDataFile() { - File file = null; - if (engine != null && getProject().getContextValue(this.getDataFileContextKey()) == null) { - file = new File(getProject().getBuild().getDirectory(), getDataFileName()); + protected void writeDataFile(MavenProject mp, File writeTo, List dependencies) { + File file; + //check to see if this was already written out + if (mp.getContextValue(this.getDataFileContextKey()) == null) { + if (writeTo == null) { + file = new File(mp.getBuild().getDirectory()); + file = new File(file, dataFileName); + } else { + file = new File(writeTo, dataFileName); + } OutputStream os = null; OutputStream bos = null; ObjectOutputStream out = null; try { - os = new FileOutputStream(file); - bos = new BufferedOutputStream(os); - out = new ObjectOutputStream(bos); - out.writeObject(engine.getDependencies()); - out.flush(); - - //call reset to prevent resource leaks per - //https://www.securecoding.cert.org/confluence/display/java/SER10-J.+Avoid+memory+and+resource+leaks+during+serialization - out.reset(); + if (dependencies != null) { + os = new FileOutputStream(file); + bos = new BufferedOutputStream(os); + out = new ObjectOutputStream(bos); + out.writeObject(dependencies); + out.flush(); + //call reset to prevent resource leaks per + //https://www.securecoding.cert.org/confluence/display/java/SER10-J.+Avoid+memory+and+resource+leaks+during+serialization + out.reset(); + } + LOGGER.fine(String.format("Serialized data file written to '%s' for %s, referenced by key %s", + file.getAbsolutePath(), mp.getName(), this.getDataFileContextKey())); + mp.setContextValue(this.getDataFileContextKey(), file.getAbsolutePath()); } catch (IOException ex) { LOGGER.log(Level.WARNING, "Unable to create data file used for report aggregation; " + "if report aggregation is being used the results may be incomplete."); @@ -868,23 +995,11 @@ public class DependencyCheckMojo extends ReportAggregationMojo { } } } - return file; } /** - * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and - * "aggregate" phase. - * - * @return a Engine object populated with dependencies if the serialized data file exists; otherwise - * null is returned - */ - protected List readDataFile() { - return readDataFile(getProject()); - } - - /** - * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and - * "aggregate" phase. + * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and "aggregate" + * phase. * * @param project the Maven project to read the data file from * @return a Engine object populated with dependencies if the serialized data file exists; otherwise diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java new file mode 100644 index 000000000..063cb0c95 --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java @@ -0,0 +1,118 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.utils.Settings; + +/** + * Maven Plugin that checks the project dependencies to see if they have any known published vulnerabilities. + * + * @author Jeremy Long + */ +@Mojo( + name = "check", + defaultPhase = LifecyclePhase.COMPILE, + threadSafe = true, + requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, + requiresOnline = true +) +public class CheckMojo extends BaseDependencyCheckMojo { + + /** + * Logger field reference. + */ + private static final Logger LOGGER = Logger.getLogger(CheckMojo.class.getName()); + + /** + * Returns whether or not a the report can be generated. + * + * @return true if the report can be generated; otherwise false + */ + @Override + public boolean canGenerateReport() { + boolean isCapable = false; + for (Artifact a : getProject().getArtifacts()) { + if (!excludeFromScan(a)) { + isCapable = true; + break; + } + } + return isCapable; + } + + /** + * Executes the dependency-check engine on the project's dependencies and generates the report. + * + * @throws MojoExecutionException thrown if there is an exception executing the goal + * @throws MojoFailureException thrown if dependency-check is configured to fail the build + */ + @Override + public void runCheck() throws MojoExecutionException, MojoFailureException { + final Engine engine; + try { + engine = initializeEngine(); + } catch (DatabaseException ex) { + LOGGER.log(Level.FINE, "Database connection error", ex); + throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + } + scanArtifacts(getProject(), engine); + if (engine.getDependencies().isEmpty()) { + LOGGER.info("No dependencies were identified that could be analyzed by dependency-check"); + } else { + engine.analyzeDependencies(); + writeReports(engine, getProject(), getCorrectOutputDirectory()); + writeDataFile(getProject(), null, engine.getDependencies()); + showSummary(getProject(), engine.getDependencies()); + checkForFailure(engine.getDependencies()); + } + engine.cleanup(); + Settings.cleanup(); + } + + /** + * Returns the report name. + * + * @param locale the location + * @return the report name + */ + public String getName(Locale locale) { + return "dependency-check"; + } + + /** + * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * + * @param locale The Locale to get the description for + * @return the description + */ + public String getDescription(Locale locale) { + return "Generates a report providing details on any published vulnerabilities within project dependencies. " + + "This report is a best effort and may contain false positives and false negatives."; + } + +} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/Engine.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/Engine.java new file mode 100644 index 000000000..fb50e203b --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/Engine.java @@ -0,0 +1,222 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.util.List; +import java.util.logging.Logger; +import org.apache.maven.project.MavenProject; +import org.owasp.dependencycheck.analyzer.Analyzer; +import org.owasp.dependencycheck.analyzer.CPEAnalyzer; +import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.utils.Settings; + +/** + * A modified version of the core engine specifically designed to persist some + * data between multiple executions of a multi-module Maven project. + * + * @author Jeremy Long + */ +public class Engine extends org.owasp.dependencycheck.Engine { + + /** + * The logger. + */ + private static final transient Logger LOGGER = Logger.getLogger(Engine.class.getName()); + /** + * A key used to persist an object in the MavenProject. + */ + private static final String CPE_ANALYZER_KEY = "dependency-check-CPEAnalyzer"; + /** + * The current MavenProject. + */ + private MavenProject currentProject; + /** + * The list of MavenProjects that are part of the current build. + */ + private List reactorProjects; + /** + * Key used in the MavenProject context values to note whether or not an + * update has been executed. + */ + public static final String UPDATE_EXECUTED_FLAG = "dependency-check-update-executed"; + + /** + * Creates a new Engine to perform anyalsis on dependencies. + * + * @param project the current Maven project + * @param reactorProjects the reactor projects for the current Maven + * execution + * @throws DatabaseException thrown if there is an issue connecting to the + * database + */ + public Engine(MavenProject project, List reactorProjects) throws DatabaseException { + this.currentProject = project; + this.reactorProjects = reactorProjects; + initializeEngine(); + } + + /** + * Runs the analyzers against all of the dependencies. + */ + @Override + public void analyzeDependencies() { + final MavenProject root = getExecutionRoot(); + if (root != null) { + LOGGER.fine(String.format("Checking root project, %s, if updates have already been completed", root.getArtifactId())); + } else { + LOGGER.fine("Checking root project, null, if updates have already been completed"); + } + if (root != null && root.getContextValue(UPDATE_EXECUTED_FLAG) != null) { + System.setProperty(Settings.KEYS.AUTO_UPDATE, Boolean.FALSE.toString()); + } + super.analyzeDependencies(); + if (root != null) { + root.setContextValue(UPDATE_EXECUTED_FLAG, Boolean.TRUE); + } + } + + /** + * This constructor should not be called. Use Engine(MavenProject) instead. + * + * @throws DatabaseException thrown if there is an issue connecting to the + * database + */ + private Engine() throws DatabaseException { + } + + /** + * Initializes the given analyzer. This skips the initialization of the + * CPEAnalyzer if it has been initialized by a previous execution. + * + * @param analyzer the analyzer to initialize + * @return the initialized analyzer + */ + @Override + protected Analyzer initializeAnalyzer(Analyzer analyzer) { + if ((analyzer instanceof CPEAnalyzer)) { + CPEAnalyzer cpe = getPreviouslyLoadedCPEAnalyzer(); + if (cpe != null) { + return cpe; + } + cpe = (CPEAnalyzer) super.initializeAnalyzer(analyzer); + storeCPEAnalyzer(cpe); + } + return super.initializeAnalyzer(analyzer); + } + + /** + * Releases resources used by the analyzers by calling close() on each + * analyzer. + */ + @Override + public void cleanup() { + super.cleanup(); + if (currentProject == null || reactorProjects == null) { + return; + } + if (this.currentProject == reactorProjects.get(reactorProjects.size() - 1)) { + final CPEAnalyzer cpe = getPreviouslyLoadedCPEAnalyzer(); + if (cpe != null) { + cpe.close(); + } + } + } + + /** + * Closes the given analyzer. This skips closing the CPEAnalyzer. + * + * @param analyzer the analyzer to close + */ + @Override + protected void closeAnalyzer(Analyzer analyzer) { + if ((analyzer instanceof CPEAnalyzer)) { + if (getPreviouslyLoadedCPEAnalyzer() == null) { + super.closeAnalyzer(analyzer); + } + } else { + super.closeAnalyzer(analyzer); + } + } + + /** + * Gets the CPEAnalyzer from the root Maven Project. + * + * @return an initialized CPEAnalyzer + */ + private CPEAnalyzer getPreviouslyLoadedCPEAnalyzer() { + CPEAnalyzer cpe = null; + final MavenProject project = getExecutionRoot(); + if (project != null) { + final Object obj = project.getContextValue(CPE_ANALYZER_KEY); + if (obj != null && obj instanceof CPEAnalyzer) { + cpe = (CPEAnalyzer) project.getContextValue(CPE_ANALYZER_KEY); + } + } + return cpe; + } + + /** + * Stores a CPEAnalyzer in the root Maven Project. + * + * @param cpe the CPEAnalyzer to store + */ + private void storeCPEAnalyzer(CPEAnalyzer cpe) { + final MavenProject p = getExecutionRoot(); + if (p != null) { + p.setContextValue(CPE_ANALYZER_KEY, cpe); + } + } + + /** + * Returns the root Maven Project. + * + * @return the root Maven Project + */ + private MavenProject getExecutionRoot() { + if (reactorProjects == null) { + return null; + } + for (MavenProject p : reactorProjects) { + if (p.isExecutionRoot()) { + return p; + } + } + //the following should never run, but leaving it as a failsafe. + if (this.currentProject == null) { + return null; + } + MavenProject p = this.currentProject; + while (p.getParent() != null) { + p = p.getParent(); + } + return p; + } + + /** + * Resets the file type analyzers so that they can be re-used to scan + * additional directories. Without the reset the analyzer might be disabled + * because the first scan/analyze did not identify any files that could be + * processed by the analyzer. + */ + public void resetFileTypeAnalyzers() { + for (FileTypeAnalyzer a : getFileTypeAnalyzers()) { + a.reset(); + } + } +} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java deleted file mode 100644 index 34dd4f750..000000000 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * This file is part of dependency-check-maven. - * - * 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) 2014 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.maven; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; -import org.apache.maven.doxia.sink.Sink; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Component; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.apache.maven.reporting.MavenReport; -import org.apache.maven.reporting.MavenReportException; - -/** - *

- * This is an abstract reporting mojo that enables report aggregation. Some of the code in the this class was copied - * from the CoberturaReportMojo (http://mojo.codehaus.org/cobertura-maven-plugin/, version 2.6). The authors of the - * CoberturaReportMojo were Will Gwaltney and - * Joakim Erdfelt. There working example of how to do report aggregation was - * invaluable.

- *

- * An important point about using this abstract class is that it is intended for one to write some form of serialized - * data (via the {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#writeDataFile() }; note that the - * writeDataFile() function is called automatically after either {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#executeNonAggregateReport(org.apache.maven.doxia.sink.Sink, - * org.apache.maven.doxia.sink.SinkFactory, java.util.Locale) - * } or {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#executeAggregateReport(org.apache.maven.doxia.sink.Sink, - * org.apache.maven.doxia.sink.SinkFactory, java.util.Locale) - * } are called. When executeAggregateReport() is implemented, one can call {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#getChildDataFiles() - * } to obtain a list of the data files to aggregate.

- * - * - * @author Jeremy Long - */ -public abstract class ReportAggregationMojo extends AbstractMojo implements MavenReport { - - /** - * The Maven Project Object. - */ - @Component - private MavenProject project; - - /** - * Logger field reference. - */ - private static final Logger LOGGER = Logger.getLogger(ReportAggregationMojo.class.getName()); - - /** - * List of Maven project of the current build - */ - @Parameter(readonly = true, required = true, property = "reactorProjects") - private List reactorProjects; - - /** - * Generate aggregate reports in multi-module projects. - */ - @Parameter(property = "aggregate", defaultValue = "false") - private boolean aggregate; - - /** - * Sets whether or not the external report format should be used. - */ - @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true) - private String dataFileName; - /** - * Specifies the destination directory for the generated Dependency-Check report. This generally maps to - * "target/site". - */ - @Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true) - private File reportOutputDirectory; - - /** - * Sets the Reporting output directory. - * - * @param directory the output directory - */ - @Override - public void setReportOutputDirectory(File directory) { - reportOutputDirectory = directory; - } - - /** - * Returns the output directory. - * - * @return the output directory - */ - @Override - public File getReportOutputDirectory() { - return reportOutputDirectory; - } - - /** - * Returns the output directory for the given project. - * - * @param project the Maven project to get the output directory for - * @return the output directory for the given project - */ - public File getReportOutputDirectory(MavenProject project) { - final Object o = project.getContextValue(getOutputDirectoryContextKey()); - if (o != null && o instanceof File) { - return (File) o; - } - return null; - } - - /** - * Returns whether this is an external report. This method always returns true. - * - * @return true - */ - @Override - public final boolean isExternalReport() { - return true; - } - - /** - * The collection of child projects. - */ - private final Map> projectChildren = new HashMap>(); - - /** - * Called before execute; allows for any setup that is needed. If this is overridden you must call - *
super.preExecute(). - * - * @throws MojoExecutionException thrown if there is an issue executing the mojo - * @throws MojoFailureException thrown if there is an issue executing the mojo - */ - protected void preExecute() throws MojoExecutionException, MojoFailureException { - buildAggregateInfo(); - } - - /** - * Called when the mojo is being executed. - * - * @throws MojoExecutionException thrown if there is an issue executing the mojo - * @throws MojoFailureException thrown if there is an issue executing the mojo - */ - protected abstract void performExecute() throws MojoExecutionException, MojoFailureException; - - /** - * Runs after the mojo has executed. This implementation will call writeDataFile(). As such, it is - * important that if this method is overriden that super.postExecute() is called. - * - * @throws MojoExecutionException thrown if there is an issue executing the mojo - * @throws MojoFailureException thrown if there is an issue executing the mojo - */ - protected void postExecute() throws MojoExecutionException, MojoFailureException { - final File written = writeDataFile(); - if (written != null) { - project.setContextValue(getDataFileContextKey(), written.getAbsolutePath()); - } - } - - /** - * Returns the key used to store the path to the data file that is saved by writeDataFile(). This key - * is used in the MavenProject.(set|get)ContextValue. - * - * @return the key used to store the path to the data file - */ - protected String getDataFileContextKey() { - return "dependency-check-path-" + this.getDataFileName(); - } - - /** - * Returns the key used to store the path to the output directory. When generating the report in the - * executeAggregateReport() the output directory should be obtained by using this key. - * - * @return the key used to store the path to the output directory - */ - protected String getOutputDirectoryContextKey() { - return "dependency-output-dir-" + this.getDataFileName(); - } - - /** - * Is called by Maven to execute the mojo. - * - * @throws MojoExecutionException thrown if there is an issue executing the mojo - * @throws MojoFailureException thrown if there is an issue executing the mojo - */ - public final void execute() throws MojoExecutionException, MojoFailureException { - try { - preExecute(); - performExecute(); - } finally { - postExecute(); - } - } - - /** - * Runs prior to the site report generation. - * - * @throws MavenReportException if a maven report exception occurs - */ - protected void preGenerate() throws MavenReportException { - buildAggregateInfo(); - - project.setContextValue(getOutputDirectoryContextKey(), getReportOutputDirectory()); - } - - /** - * Executes after the site report has been generated. - * - * @throws MavenReportException if a maven report exception occurs - */ - protected void postGenerate() throws MavenReportException { - final File written = writeDataFile(); - if (written != null) { - project.setContextValue(getDataFileContextKey(), written.getAbsolutePath()); - } - } - - /** - * Generates the non aggregate report. - * - * @param locale the locale to use when generating the report - * @throws MavenReportException if a maven report exception occurs - */ - protected abstract void executeNonAggregateReport(Locale locale) throws MavenReportException; - - /** - * Generates the aggregate Site Report. - * - * @param project the maven project used to generate the aggregate report - * @param locale the locale to use when generating the report - * @throws MavenReportException if a maven report exception occurs - */ - protected abstract void executeAggregateReport(MavenProject project, Locale locale) throws MavenReportException; - - /** - * Generates the Dependency-Check Site Report. - * - * @param sink the sink to write the report to - * @param locale the locale to use when generating the report - * @throws MavenReportException if a maven report exception occurs - * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, java.util.Locale) instead. - */ - @Deprecated - public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException { - generate((Sink) sink, locale); - } - - /** - * Generates the Dependency-Check Site Report. - * - * @param sink the sink to write the report to - * @param locale the locale to use when generating the report - * @throws MavenReportException if a maven report exception occurs - */ - public final void generate(Sink sink, Locale locale) throws MavenReportException { - try { - preGenerate(); - if (canGenerateNonAggregateReport()) { - executeNonAggregateReport(locale); - } - - if (canGenerateAggregateReport()) { - for (MavenProject proj : reactorProjects) { - if (!isMultiModule(proj)) { - continue; - } - executeAggregateReport(proj, locale); - } - } - } finally { - postGenerate(); - } - } - - /** - * Returns whether or not the mojo can generate a non-aggregate report for this project. - * - * @return true if a non-aggregate report can be generated, otherwise false - */ - protected abstract boolean canGenerateNonAggregateReport(); - - /** - * Returns whether or not we can generate any aggregate reports at this time. - * - * @return true if an aggregate report can be generated, otherwise false - */ - protected abstract boolean canGenerateAggregateReport(); - - /** - * Returns the name of the data file that contains the serialized data. - * - * @return the name of the data file that contains the serialized data - */ - protected String getDataFileName() { - return dataFileName; - } - - /** - * Writes the data file to disk in the target directory. - * - * @return the File object referencing the data file that was written - */ - protected abstract File writeDataFile(); - - /** - * Collects the information needed for building aggregate reports. - */ - private void buildAggregateInfo() { - // build parent-child map - for (MavenProject proj : reactorProjects) { - Set depList = projectChildren.get(proj.getParent()); - if (depList == null) { - depList = new HashSet(); - projectChildren.put(proj.getParent(), depList); - } - depList.add(proj); - } - } - - /** - * Returns a list containing all the recursive, non-pom children of the given project, never null. - * - * @return a list of child projects - */ - protected List getAllChildren() { - return getAllChildren(project); - } - - /** - * Returns a list containing all the recursive, non-pom children of the given project, never null. - * - * @param parentProject the parent project to collect the child project references - * @return a list of child projects - */ - protected List getAllChildren(MavenProject parentProject) { - final Set children = projectChildren.get(parentProject); - if (children == null) { - return Collections.emptyList(); - } - - final List result = new ArrayList(); - for (MavenProject child : children) { - if (isMultiModule(child)) { - result.addAll(getAllChildren(child)); - } else { - result.add(child); - } - } - return result; - } - - /** - * Returns a list of data files that were produced by the direct children of the given MavenProject. - * - * @param project the Maven project to obtain the child data files from - * @return a list of the data files - */ - protected List getAllChildDataFiles(MavenProject project) { - final List children = getAllChildren(project); - return getDataFiles(children); - } - - /** - * Returns any existing output files from the given list of projects. - * - * @param projects the list of projects to obtain the output files from - * @return a list of output files - */ - protected List getDataFiles(List projects) { - final List files = new ArrayList(); - for (MavenProject proj : projects) { - final Object path = project.getContextValue(getDataFileContextKey()); - if (path == null) { - final String msg = String.format("Unable to aggregate data for '%s' - aggregate data file was not generated", - proj.getName()); - LOGGER.warning(msg); - } else { - final File outputFile = new File((String) path); - if (outputFile.exists()) { - files.add(outputFile); - } else { - if (!isMultiModule(project)) { - final String msg = String.format("Unable to aggregate data for '%s' - missing data file '%s'", - proj.getName(), outputFile.getPath()); - LOGGER.warning(msg); - } - } - } - } - return files; - } - - /** - * Test if the project has pom packaging - * - * @param mavenProject Project to test - * @return true if it has a pom packaging; otherwise false - */ - protected boolean isMultiModule(MavenProject mavenProject) { - return "pom".equals(mavenProject.getPackaging()); - } - - /** - * Test if the current project has pom packaging - * - * @return true if it has a pom packaging; otherwise false - */ - protected boolean isMultiModule() { - return isMultiModule(project); - } - - /** - * Check whether the current project is the last project in a multi-module build. If the maven build is not a - * multi-module project then this will always return true. - * - * @return true if the current project is the last project in a multi-module build; otherwise - * false - */ - protected boolean isLastProject() { - return project.equals(reactorProjects.get(reactorProjects.size() - 1)); - } - - /** - * Returns whether or not the mojo is configured to perform report aggregation. - * - * @return true if report aggregation is enabled; otherwise false - */ - public boolean isAggregate() { - return aggregate; - } - - /** - * Returns a reference to the current project. This method is used instead of auto-binding the project via component - * annotation in concrete implementations of this. If the child has a @Component MavenProject project; - * defined then the abstract class (i.e. this class) will not have access to the current project (just the way Maven - * works with the binding). - * - * @return returns a reference to the current project - */ - protected MavenProject getProject() { - return project; - } -} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportingUtil.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportingUtil.java deleted file mode 100644 index d26e9b18e..000000000 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportingUtil.java +++ /dev/null @@ -1,456 +0,0 @@ -/* - * This file is part of dependency-check-maven. - * - * 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) 2014 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.maven; - -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.text.DateFormat; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.apache.maven.doxia.sink.Sink; -import org.owasp.dependencycheck.Engine; -import org.owasp.dependencycheck.data.nvdcve.CveDB; -import org.owasp.dependencycheck.data.nvdcve.DatabaseException; -import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; -import org.owasp.dependencycheck.dependency.Dependency; -import org.owasp.dependencycheck.dependency.Evidence; -import org.owasp.dependencycheck.dependency.Identifier; -import org.owasp.dependencycheck.dependency.Reference; -import org.owasp.dependencycheck.dependency.Vulnerability; -import org.owasp.dependencycheck.dependency.VulnerableSoftware; -import org.owasp.dependencycheck.reporting.ReportGenerator; - -/** - * A utility class that encapsulates the report generation for dependency-check-maven. - * - * @author Jeremy Long - */ -final class ReportingUtil { - - /** - * Logger field reference. - */ - private static final Logger LOGGER = Logger.getLogger(ReportingUtil.class.getName()); - - /** - * Empty private constructor for this utility class. - */ - private ReportingUtil() { - } - - /** - * Generates the reports for a given dependency-check engine. - * - * @param engine a dependency-check engine - * @param outDirectory the directory to write the reports to - * @param projectName the name of the project that a report is being generated for - * @param format the format of the report to generate - */ - static void generateExternalReports(Engine engine, File outDirectory, String projectName, String format) { - DatabaseProperties prop = null; - CveDB cve = null; - try { - cve = new CveDB(); - cve.open(); - prop = cve.getDatabaseProperties(); - } catch (DatabaseException ex) { - LOGGER.log(Level.FINE, "Unable to retrieve DB Properties", ex); - } finally { - if (cve != null) { - cve.close(); - } - } - final ReportGenerator r = new ReportGenerator(projectName, engine.getDependencies(), engine.getAnalyzers(), prop); - try { - r.generateReports(outDirectory.getCanonicalPath(), format); - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, - "Unexpected exception occurred during analysis; please see the verbose error log for more details."); - LOGGER.log(Level.FINE, null, ex); - } catch (Throwable ex) { - LOGGER.log(Level.SEVERE, - "Unexpected exception occurred during analysis; please see the verbose error log for more details."); - LOGGER.log(Level.FINE, null, ex); - } - } - - /** - * Generates a dependency-check report using the Maven Site format. - * - * @param engine the engine used to scan the dependencies - * @param sink the sink to write the data to - * @param projectName the name of the project - */ - static void generateMavenSiteReport(final Engine engine, Sink sink, String projectName) { - final List dependencies = engine.getDependencies(); - - writeSiteReportHeader(sink, projectName); - writeSiteReportTOC(sink, dependencies); - - int cnt = 0; - for (Dependency d : dependencies) { - writeSiteReportDependencyHeader(sink, d); - cnt = writeSiteReportDependencyEvidenceUsed(d, cnt, sink); - cnt = writeSiteReportDependencyRelatedDependencies(d, cnt, sink); - writeSiteReportDependencyIdentifiers(d, sink); - writeSiteReportDependencyVulnerabilities(d, sink, cnt); - } - sink.body_(); - } - - // - /** - * Writes the vulnerabilities to the site report. - * - * @param d the dependency - * @param sink the sink to write the data to - * @param collapsibleHeaderCount the collapsible header count - */ - private static void writeSiteReportDependencyVulnerabilities(Dependency d, Sink sink, int collapsibleHeaderCount) { - int cnt = collapsibleHeaderCount; - if (d.getVulnerabilities() != null && !d.getVulnerabilities().isEmpty()) { - for (Vulnerability v : d.getVulnerabilities()) { - - sink.paragraph(); - sink.bold(); - try { - sink.link("http://web.nvd.nist.gov/view/vuln/detail?vulnId=" + URLEncoder.encode(v.getName(), "US-ASCII")); - sink.text(v.getName()); - sink.link_(); - sink.bold_(); - } catch (UnsupportedEncodingException ex) { - sink.text(v.getName()); - sink.bold_(); - sink.lineBreak(); - sink.text("http://web.nvd.nist.gov/view/vuln/detail?vulnId=" + v.getName()); - } - sink.paragraph_(); - sink.paragraph(); - sink.text("Severity: "); - if (v.getCvssScore() < 4.0) { - sink.text("Low"); - } else { - if (v.getCvssScore() >= 7.0) { - sink.text("High"); - } else { - sink.text("Medium"); - } - } - sink.lineBreak(); - sink.text("CVSS Score: " + v.getCvssScore()); - if (v.getCwe() != null && !v.getCwe().isEmpty()) { - sink.lineBreak(); - sink.text("CWE: "); - sink.text(v.getCwe()); - } - sink.paragraph_(); - sink.paragraph(); - sink.text(v.getDescription()); - if (v.getReferences() != null && !v.getReferences().isEmpty()) { - sink.list(); - for (Reference ref : v.getReferences()) { - sink.listItem(); - sink.text(ref.getSource()); - sink.text(" - "); - sink.link(ref.getUrl()); - sink.text(ref.getName()); - sink.link_(); - sink.listItem_(); - } - sink.list_(); - } - sink.paragraph_(); - if (v.getVulnerableSoftware() != null && !v.getVulnerableSoftware().isEmpty()) { - sink.paragraph(); - - cnt += 1; - sink.rawText("Vulnerable Software [-]"); - sink.rawText("
"); - sink.list(); - for (VulnerableSoftware vs : v.getVulnerableSoftware()) { - sink.listItem(); - try { - sink.link("http://web.nvd.nist.gov/view/vuln/search-results?cpe=" + URLEncoder.encode(vs.getName(), "US-ASCII")); - sink.text(vs.getName()); - sink.link_(); - if (vs.hasPreviousVersion()) { - sink.text(" and all previous versions."); - } - } catch (UnsupportedEncodingException ex) { - sink.text(vs.getName()); - if (vs.hasPreviousVersion()) { - sink.text(" and all previous versions."); - } - sink.text(" (http://web.nvd.nist.gov/view/vuln/search-results?cpe=" + vs.getName() + ")"); - } - - sink.listItem_(); - } - sink.list_(); - sink.rawText("
"); - sink.paragraph_(); - } - } - } - } - - /** - * Writes the identifiers to the site report. - * - * @param d the dependency - * @param sink the sink to write the data to - */ - private static void writeSiteReportDependencyIdentifiers(Dependency d, Sink sink) { - if (d.getIdentifiers() != null && !d.getIdentifiers().isEmpty()) { - sink.sectionTitle4(); - sink.text("Identifiers"); - sink.sectionTitle4_(); - sink.list(); - for (Identifier i : d.getIdentifiers()) { - sink.listItem(); - sink.text(i.getType()); - sink.text(": "); - if (i.getUrl() != null && i.getUrl().length() > 0) { - sink.link(i.getUrl()); - sink.text(i.getValue()); - sink.link_(); - } else { - sink.text(i.getValue()); - } - if (i.getDescription() != null && i.getDescription().length() > 0) { - sink.lineBreak(); - sink.text(i.getDescription()); - } - sink.listItem_(); - } - sink.list_(); - } - } - - /** - * Writes the related dependencies to the site report. - * - * @param d the dependency - * @param sink the sink to write the data to - * @param collapsibleHeaderCount the collapsible header count - * @return the collapsible header count - */ - private static int writeSiteReportDependencyRelatedDependencies(Dependency d, int collapsibleHeaderCount, Sink sink) { - int cnt = collapsibleHeaderCount; - if (d.getRelatedDependencies() != null && !d.getRelatedDependencies().isEmpty()) { - cnt += 1; - sink.sectionTitle4(); - sink.rawText("Related Dependencies [+]"); - sink.sectionTitle4_(); - sink.rawText("
"); - sink.list(); - for (Dependency r : d.getRelatedDependencies()) { - sink.listItem(); - sink.text(r.getFileName()); - sink.list(); - writeListItem(sink, "File Path: " + r.getFilePath()); - writeListItem(sink, "SHA1: " + r.getSha1sum()); - writeListItem(sink, "MD5: " + r.getMd5sum()); - sink.list_(); - sink.listItem_(); - } - sink.list_(); - sink.rawText("
"); - } - return cnt; - } - - /** - * Writes the evidence used to the site report. - * - * @param d the dependency - * @param sink the sink to write the data to - * @param collapsibleHeaderCount the collapsible header count - * @return the collapsible header count - */ - private static int writeSiteReportDependencyEvidenceUsed(Dependency d, int collapsibleHeaderCount, Sink sink) { - int cnt = collapsibleHeaderCount; - final Set evidence = d.getEvidenceForDisplay(); - if (evidence != null && evidence.size() > 0) { - cnt += 1; - sink.sectionTitle4(); - sink.rawText("Evidence Collected [+]"); - sink.sectionTitle4_(); - sink.rawText("
"); - sink.table(); - sink.tableRow(); - writeTableHeaderCell(sink, "Source"); - writeTableHeaderCell(sink, "Name"); - writeTableHeaderCell(sink, "Value"); - sink.tableRow_(); - for (Evidence e : evidence) { - sink.tableRow(); - writeTableCell(sink, e.getSource()); - writeTableCell(sink, e.getName()); - writeTableCell(sink, e.getValue()); - sink.tableRow_(); - } - sink.table_(); - sink.rawText("
"); - } - return cnt; - } - - /** - * Writes the dependency header to the site report. - * - * @param d the dependency - * @param sink the sink to write the data to - */ - private static void writeSiteReportDependencyHeader(Sink sink, Dependency d) { - sink.sectionTitle2(); - sink.anchor("sha1" + d.getSha1sum()); - sink.text(d.getFileName()); - sink.anchor_(); - sink.sectionTitle2_(); - if (d.getDescription() != null && d.getDescription().length() > 0) { - sink.paragraph(); - sink.bold(); - sink.text("Description: "); - sink.bold_(); - sink.text(d.getDescription()); - sink.paragraph_(); - } - if (d.getLicense() != null && d.getLicense().length() > 0) { - sink.paragraph(); - sink.bold(); - sink.text("License: "); - sink.bold_(); - if (d.getLicense().startsWith("http://") && !d.getLicense().contains(" ")) { - sink.link(d.getLicense()); - sink.text(d.getLicense()); - sink.link_(); - } else { - sink.text(d.getLicense()); - } - sink.paragraph_(); - } - } - - /** - * Adds a list item to the site report. - * - * @param sink the sink to write the data to - * @param text the text to write - */ - private static void writeListItem(Sink sink, String text) { - sink.listItem(); - sink.text(text); - sink.listItem_(); - } - - /** - * Adds a table cell to the site report. - * - * @param sink the sink to write the data to - * @param text the text to write - */ - private static void writeTableCell(Sink sink, String text) { - sink.tableCell(); - sink.text(text); - sink.tableCell_(); - } - - /** - * Adds a table header cell to the site report. - * - * @param sink the sink to write the data to - * @param text the text to write - */ - private static void writeTableHeaderCell(Sink sink, String text) { - sink.tableHeaderCell(); - sink.text(text); - sink.tableHeaderCell_(); - } - - /** - * Writes the TOC for the site report. - * - * @param sink the sink to write the data to - * @param dependencies the dependencies that are being reported on - */ - private static void writeSiteReportTOC(Sink sink, final List dependencies) { - sink.list(); - for (Dependency d : dependencies) { - sink.listItem(); - sink.link("#sha1" + d.getSha1sum()); - sink.text(d.getFileName()); - sink.link_(); - if (!d.getVulnerabilities().isEmpty()) { - sink.rawText(" •"); - } - if (!d.getRelatedDependencies().isEmpty()) { - sink.list(); - for (Dependency r : d.getRelatedDependencies()) { - writeListItem(sink, r.getFileName()); - } - sink.list_(); - } - sink.listItem_(); - } - sink.list_(); - } - - /** - * Writes the site report header. - * - * @param sink the sink to write the data to - * @param projectName the name of the project - */ - private static void writeSiteReportHeader(Sink sink, String projectName) { - sink.head(); - sink.title(); - sink.text("Dependency-Check Report: " + projectName); - sink.title_(); - sink.head_(); - sink.body(); - sink.rawText(""); - sink.section1(); - sink.sectionTitle1(); - sink.text("Project: " + projectName); - sink.sectionTitle1_(); - sink.date(); - final Date now = new Date(); - sink.text(DateFormat.getDateTimeInstance().format(now)); - sink.date_(); - sink.section1_(); - } - //
- -} diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md index cccc733fb..10b6d4b53 100644 --- a/dependency-check-maven/src/site/markdown/configuration.md +++ b/dependency-check-maven/src/site/markdown/configuration.md @@ -1,10 +1,18 @@ +Goals +==================== + +Goal | Description +-----------|----------------------- +aggregate | Runs dependency-check against the child projects and aggregates the results into a single report. +check | Runs dependency-check against the project and generates a report. + Configuration ==================== The following properties can be set on the dependency-check-maven plugin. Property | Description | Default Value ---------------------|------------------------------------|------------------ -aggregate | Sets whether report aggregation will be performed for multi-module site reports. This option only affects the report generation when configured within the reporting section. | false +aggregate | Deprecated - use the aggregate goal instead. |   autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true outputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build | 'target' failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which means since the CVSS scores are 0-10, by default the build will never fail. | 11 @@ -23,17 +31,18 @@ Note, that specific analyzers will automatically disable themselves if no file types that they support are detected - so specifically disabling them may not be needed. -Property | Description | Default Value -------------------------|------------------------------------|------------------ -archiveAnalyzerEnabled | Sets whether the Archive Analyzer will be used. | true +Property | Description | Default Value +------------------------|---------------------------------------------------------------------------|------------------ +archiveAnalyzerEnabled | Sets whether the Archive Analyzer will be used. | true zipExtensions | A comma-separated list of additional file extensions to be treated like a ZIP file, the contents will be extracted and analyzed. |   -jarAnalyzer | Sets whether Jar Analyzer will be used. | true -nexusAnalyzerEnabled | Sets whether Nexus Analyzer will be used. | true -nexusUrl | Defines the Nexus URL. | https://repository.sonatype.org/service/local/ +jarAnalyzer | Sets whether Jar Analyzer will be used. | true +centralAnalyzerEnabled | Sets whether Central Analyzer will be used. If this analyzer is being disabled there is a good chance you also want to disable the Nexus Analyzer (see below). | true +nexusAnalyzerEnabled | Sets whether Nexus Analyzer will be used. This analyzer is superceded by the Central Analyzer; however, you can configure this to run against a Nexus Pro installation. | true +nexusUrl | Defines the Nexus Pro Server URL. If not set the Nexus Analyzer will be disabled. |   nexusUsesProxy | Whether or not the defined proxy should be used when connecting to Nexus. | true -nuspecAnalyzerEnabled | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | true -assemblyAnalyzerEnabled | Sets whether or not the .NET Assembly Analyzer should be used. | true -pathToMono | The path to Mono for .NET assembly analysis on non-windows systems |   +nuspecAnalyzerEnabled | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | true +assemblyAnalyzerEnabled | Sets whether or not the .NET Assembly Analyzer should be used. | true +pathToMono | The path to Mono for .NET assembly analysis on non-windows systems. |   Advanced Configuration ==================== diff --git a/dependency-check-maven/src/site/markdown/usage.md.vm b/dependency-check-maven/src/site/markdown/usage.md.vm index 6c2c7e10c..8ff9ce975 100644 --- a/dependency-check-maven/src/site/markdown/usage.md.vm +++ b/dependency-check-maven/src/site/markdown/usage.md.vm @@ -13,7 +13,7 @@ seven days the update will only take a few seconds. #set( $H = '#' ) $H$H$H Example 1: -Create the DependencyCheck-report.html in the target directory +Create the DependencyCheck-report.html in the target directory. ```xml @@ -43,7 +43,7 @@ Create the DependencyCheck-report.html in the target directory ``` $H$H$H Example 2: -Create an aggregated dependency-check report within the site +Create an aggregated dependency-check report within the site. ```xml @@ -57,13 +57,10 @@ Create an aggregated dependency-check report within the site org.owasp dependency-check-maven ${project.version} - - true - - check + aggregate @@ -78,7 +75,7 @@ Create an aggregated dependency-check report within the site ``` $H$H$H Example 3: -Create the DependencyCheck-report.html and fail the build for CVSS greater then 8 +Create the DependencyCheck-report.html and fail the build for CVSS greater then 8. ```xml @@ -111,7 +108,7 @@ Create the DependencyCheck-report.html and fail the build for CVSS greater then ``` $H$H$H Example 4: -Create the DependencyCheck-report.html and skip artifacts not bundled in distribution (Provided and Runtime scope) +Create the DependencyCheck-report.html and skip artifacts not bundled in distribution (Provided and Runtime scope). ```xml @@ -145,7 +142,7 @@ Create the DependencyCheck-report.html and skip artifacts not bundled in distrib ``` $H$H$H Example 5: -Create the DependencyCheck-report.html and use internal mirroring of CVE contents +Create the DependencyCheck-report.html and use internal mirroring of CVE contents. ```xml diff --git a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java new file mode 100644 index 000000000..cac2f77bf --- /dev/null +++ b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java @@ -0,0 +1,111 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.io.File; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import mockit.Mock; +import mockit.MockUp; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.testing.stubs.ArtifactStub; +import org.apache.maven.project.MavenProject; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.utils.InvalidSettingException; +import org.owasp.dependencycheck.utils.Settings; + +/** + * + * @author Jeremy Long + */ +public class BaseDependencyCheckMojoTest extends BaseTest { + + /** + * Test of scanArtifacts method, of class BaseDependencyCheckMojo. + */ + @Test + public void testScanArtifacts() throws DatabaseException, InvalidSettingException { + MavenProject project = new MockUp() { + @Mock + public Set getArtifacts() { + Set artifacts = new HashSet(); + Artifact a = new ArtifactStub(); + try { + File file = new File(Test.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + a.setFile(file); + artifacts.add(a); + } catch (URISyntaxException ex) { + Logger.getLogger(BaseDependencyCheckMojoTest.class.getName()).log(Level.SEVERE, null, ex); + } + //File file = new File(this.getClass().getClassLoader().getResource("daytrader-ear-2.1.7.ear").getPath()); + + return artifacts; + } + + @Mock + public String getName() { + return "test-project"; + } + }.getMockInstance(); + + boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); + Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); + Engine engine = new Engine(null, null); + Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate); + + assertTrue(engine.getDependencies().isEmpty()); + BaseDependencyCheckMojoImpl instance = new BaseDependencyCheckMojoImpl(); + instance.scanArtifacts(project, engine); + assertFalse(engine.getDependencies().isEmpty()); + engine.cleanup(); + } + + public class BaseDependencyCheckMojoImpl extends BaseDependencyCheckMojo { + + @Override + public void runCheck() throws MojoExecutionException, MojoFailureException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getName(Locale locale) { + return "test implementation"; + } + + @Override + public String getDescription(Locale locale) { + return "test implementation"; + } + + @Override + public boolean canGenerateReport() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + } + +} diff --git a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java new file mode 100644 index 000000000..4d0ca1381 --- /dev/null +++ b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java @@ -0,0 +1,47 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.io.InputStream; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.owasp.dependencycheck.utils.Settings; + +/** + * + * @author Jeremy Long + */ +public class BaseTest { + + /** + * The properties file location. + */ + public static final String PROPERTIES_FILE = "mojo.properties"; + + @BeforeClass + public static void setUpClass() throws Exception { + Settings.initialize(); + InputStream mojoProperties = BaseTest.class.getClassLoader().getResourceAsStream(BaseTest.PROPERTIES_FILE); + Settings.mergeProperties(mojoProperties); + } + + @AfterClass + public static void tearDownClass() throws Exception { + Settings.cleanup(true); + } +} diff --git a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/DependencyCheckMojoTest.java b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/DependencyCheckMojoTest.java deleted file mode 100644 index 2c161ecaa..000000000 --- a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/DependencyCheckMojoTest.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * This file is part of dependency-check-maven. - * - * 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) 2013 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.maven; - -import org.apache.maven.plugin.testing.AbstractMojoTestCase; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * TODO - figure out how to get the test harness to work. ATM no tests are running. - * - * @author Jeremy Long - */ -public class DependencyCheckMojoTest extends AbstractMojoTestCase { - - public DependencyCheckMojoTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - } - - @AfterClass - public static void tearDownClass() throws Exception { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } -// -// /** -// * Test of execute method, of class DependencyCheckMojo. -// */ -// @Test -// public void testExecute() throws Exception { -// System.out.println("execute"); -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// instance.execute(); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of generate method, of class DependencyCheckMojo. -// */ -// @Test -// public void testGenerate_Sink_Locale() throws Exception { -// System.out.println("generate"); -// org.codehaus.doxia.sink.Sink sink = null; -// Locale locale = null; -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// instance.generate(sink, locale); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } - - /** - * Test of generate method, of class DependencyCheckMojo. - */ - @Test - public void testGenerate_Sink_SinkFactory_Locale() throws Exception { - //can't get the test-harness to work. -// File samplePom = new File(this.getClass().getClassLoader().getResource("sample.xml").toURI()); -// DependencyCheckMojo mojo = (DependencyCheckMojo) lookupMojo("check", samplePom); -// assertNotNull("Unable to load mojo", mojo); -// -// File out = mojo.getReportOutputDirectory(); -// OutputStream os = new FileOutputStream(out); -// MySink sink = new MySink(os); -// Locale locale = new Locale("en"); -// -// -// mojo.generate(sink, null, locale); -// sink.close(); - } -// /** -// * Test of getOutputName method, of class DependencyCheckMojo. -// */ -// @Test -// public void testGetOutputName() { -// System.out.println("getOutputName"); -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// String expResult = ""; -// String result = instance.getOutputName(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of getCategoryName method, of class DependencyCheckMojo. -// */ -// @Test -// public void testGetCategoryName() { -// System.out.println("getCategoryName"); -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// String expResult = ""; -// String result = instance.getCategoryName(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of getName method, of class DependencyCheckMojo. -// */ -// @Test -// public void testGetName() { -// System.out.println("getName"); -// Locale locale = null; -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// String expResult = ""; -// String result = instance.getName(locale); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of setReportOutputDirectory method, of class DependencyCheckMojo. -// */ -// @Test -// public void testSetReportOutputDirectory() { -// System.out.println("setReportOutputDirectory"); -// File directory = null; -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// instance.setReportOutputDirectory(directory); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of getReportOutputDirectory method, of class DependencyCheckMojo. -// */ -// @Test -// public void testGetReportOutputDirectory() { -// System.out.println("getReportOutputDirectory"); -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// File expResult = null; -// File result = instance.getReportOutputDirectory(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of getDescription method, of class DependencyCheckMojo. -// */ -// @Test -// public void testGetDescription() { -// System.out.println("getDescription"); -// Locale locale = null; -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// String expResult = ""; -// String result = instance.getDescription(locale); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of isExternalReport method, of class DependencyCheckMojo. -// */ -// @Test -// public void testIsExternalReport() { -// System.out.println("isExternalReport"); -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// boolean expResult = false; -// boolean result = instance.isExternalReport(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of canGenerateReport method, of class DependencyCheckMojo. -// */ -// @Test -// public void testCanGenerateReport() { -// System.out.println("canGenerateReport"); -// DependencyCheckMojo instance = new DependencyCheckMojo(); -// boolean expResult = false; -// boolean result = instance.canGenerateReport(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -} diff --git a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/MySink.java b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/MySink.java deleted file mode 100644 index dfdad6270..000000000 --- a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/MySink.java +++ /dev/null @@ -1,601 +0,0 @@ -/* - * This file is part of dependency-check-maven. - * - * 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) 2013 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.maven; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.apache.maven.doxia.logging.Log; -import org.apache.maven.doxia.sink.Sink; -import org.apache.maven.doxia.sink.SinkEventAttributes; - -/** - * - * @author Jeremy Long - */ -public class MySink implements Sink { - - private OutputStreamWriter out = null; - - public MySink(OutputStream os) { - out = new OutputStreamWriter(os); - - } - - private void writeTag(String tag) { - try { - out.write(tag); - } catch (IOException ex) { - Logger.getLogger(MySink.class.getName()).log(Level.SEVERE, "Error writing a tag; unable to generate the report"); - Logger.getLogger(MySink.class.getName()).log(Level.FINE, null, ex); - } - } - - public void head() { - writeTag(""); - } - - public void head_() { - writeTag(""); - } - - public void title() { - writeTag(""); - } - - public void title_() { - writeTag(""); - } - - public void author() { - writeTag(""); - } - - public void author_() { - writeTag(""); - } - - public void date() { - writeTag(""); - } - - public void body() { - writeTag(""); - } - - public void body_() { - writeTag(""); - } - - public void sectionTitle() { - writeTag("

"); - } - - public void sectionTitle_() { - writeTag("

"); - } - - public void section1() { - writeTag("
"); - } - - public void section1_() { - writeTag("
"); - } - - public void sectionTitle1() { - writeTag("

"); - } - - public void sectionTitle1_() { - writeTag("

"); - } - - public void section2() { - writeTag("
"); - } - - public void section2_() { - writeTag("
"); - } - - public void sectionTitle2() { - writeTag("

"); - } - - public void sectionTitle2_() { - writeTag("

"); - } - - public void section3() { - writeTag("
"); - } - - public void section3_() { - writeTag("
"); - } - - public void sectionTitle3() { - writeTag("

"); - } - - public void sectionTitle3_() { - writeTag("

"); - } - - public void section4() { - writeTag("
"); - } - - public void section4_() { - writeTag("
"); - } - - public void sectionTitle4() { - writeTag("
"); - } - - public void sectionTitle4_() { - writeTag("
"); - } - - public void section5() { - writeTag("
"); - } - - public void section5_() { - writeTag("
"); - } - - public void sectionTitle5() { - writeTag("
"); - } - - public void sectionTitle5_() { - writeTag("
"); - } - - public void list() { - writeTag("
    "); - } - - public void list_() { - writeTag("
"); - } - - public void listItem() { - writeTag("
  • "); - } - - public void listItem_() { - writeTag("
  • "); - } - - public void numberedList(int numbering) { - writeTag("
      "); - } - - public void numberedList_() { - writeTag("
    "); - } - - public void numberedListItem() { - writeTag("
  • "); - } - - public void numberedListItem_() { - writeTag("
  • "); - } - - public void definitionList() { - writeTag("
    "); - } - - public void definitionList_() { - writeTag("
    "); - } - - public void definitionListItem() { - writeTag("
    "); - } - - public void definitionListItem_() { - writeTag("
    "); - } - - public void definition() { - writeTag("
    "); - } - - public void definition_() { - writeTag("
    "); - } - - public void definedTerm() { - writeTag("
    "); - } - - public void definedTerm_() { - writeTag("
    "); - } - - public void figure() { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void figure_() { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void figureCaption() { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void figureCaption_() { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void figureGraphics(String name) { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void table() { - writeTag(""); - } - - public void table_() { - writeTag("
    "); - } - - public void tableRows(int[] justification, boolean grid) { - writeTag(""); - } - - public void tableRows_() { - writeTag(""); - } - - public void tableRow() { - writeTag(""); - } - - public void tableRow_() { - writeTag(""); - } - - public void tableCell() { - writeTag(""); - } - - public void tableCell(String width) { - writeTag(""); - } - - public void tableCell_() { - writeTag(""); - } - - public void tableHeaderCell() { - writeTag(""); - } - - public void tableHeaderCell(String width) { - writeTag(""); - } - - public void tableHeaderCell_() { - writeTag(""); - } - - public void tableCaption() { - writeTag(""); - } - - public void tableCaption_() { - writeTag(""); - } - - public void paragraph() { - writeTag("

    "); - } - - public void paragraph_() { - writeTag("

    "); - } - - public void verbatim(boolean boxed) { - writeTag("
    ");
    -    }
    -
    -    public void verbatim_() {
    -        writeTag("
    "); - } - - public void horizontalRule() { - writeTag("
    "); - } - - public void pageBreak() { - writeTag("
    "); - } - - public void anchor(String name) { - writeTag(""); - } - - public void anchor_() { - writeTag(""); - } - - public void link(String name) { - writeTag(""); - } - - public void link_() { - writeTag(""); - } - - public void italic() { - writeTag(""); - } - - public void italic_() { - writeTag(""); - } - - public void bold() { - writeTag(""); - } - - public void bold_() { - writeTag(""); - } - - public void monospaced() { - writeTag("
    ");
    -    }
    -
    -    public void monospaced_() {
    -        writeTag("
    "); - } - - public void lineBreak() { - writeTag("
    "); - } - - public void nonBreakingSpace() { - writeTag(" "); - } - - public void text(String text) { - try { - //TODO add HTML Encoding - or figure out how to get the doxia xhtmlsink to work. - out.write(text); - } catch (IOException ex) { - Logger.getLogger(MySink.class.getName()).log(Level.SEVERE, "Error writing a text; unable to generate the report"); - Logger.getLogger(MySink.class.getName()).log(Level.FINE, null, ex); - } - } - - public void rawText(String text) { - try { - out.write(text); - } catch (IOException ex) { - Logger.getLogger(MySink.class.getName()).log(Level.SEVERE, "Error writing raw text; unable to generate the report"); - Logger.getLogger(MySink.class.getName()).log(Level.FINE, null, ex); - } - } - - public void flush() { - try { - out.flush(); - } catch (IOException ex) { - Logger.getLogger(MySink.class.getName()).log(Level.FINEST, null, ex); - } - } - - public void close() { - flush(); - try { - out.close(); - } catch (IOException ex) { - Logger.getLogger(MySink.class.getName()).log(Level.FINEST, null, ex); - } - } - - @Override - public void head(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void title(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void author(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void date(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void body(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void section(int i, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void section_(int i) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void sectionTitle(int i, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void sectionTitle_(int i) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void list(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void listItem(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void numberedList(int i, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void numberedListItem(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void definitionList(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void definitionListItem(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void definition(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void definedTerm(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void figure(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void figureCaption(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void figureGraphics(String string, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void table(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void tableRow(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void tableCell(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void tableHeaderCell(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void tableCaption(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void paragraph(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void verbatim(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void horizontalRule(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void anchor(String string, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void link(String string, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void lineBreak(SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void text(String string, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void comment(String string) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void unknown(String string, Object[] os, SinkEventAttributes sea) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void enableLogging(Log log) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } -} diff --git a/dependency-check-maven/src/test/resources/mojo.properties b/dependency-check-maven/src/test/resources/mojo.properties new file mode 100644 index 000000000..98d17c44c --- /dev/null +++ b/dependency-check-maven/src/test/resources/mojo.properties @@ -0,0 +1,2 @@ +# the path to the data directory +data.directory=[JAR]/dependency-check-data diff --git a/dependency-check-utils/pom.xml b/dependency-check-utils/pom.xml index 8119ffb50..44020de98 100644 --- a/dependency-check-utils/pom.xml +++ b/dependency-check-utils/pom.xml @@ -21,7 +21,7 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT dependency-check-utils @@ -45,11 +45,10 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved. org.codehaus.mojo cobertura-maven-plugin - 2.6 - + 85 85 @@ -78,7 +77,6 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-surefire-plugin - 2.16 @@ -94,190 +92,137 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-failsafe-plugin - 2.16 - - - - temp.directory - ${project.build.directory}/temp - - - - **/*IntegrationTest.java - - - - - - integration-test - verify - - - org.apache.maven.plugins maven-compiler-plugin - 3.1 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 - false - 1.6 - 1.6 + Copyright© 2012-15 Jeremy Long. All Rights Reserved. + + + + default + + javadoc + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + + dependency-updates-report + plugin-updates-report + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.4 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.16 + + + + report-only + + + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Todo Work + + + todo + ignoreCase + + + FIXME + exact + + + + + org.apache.maven.plugins - maven-site-plugin - 3.3 - - - org.apache.maven.doxia - doxia-module-markdown - 1.5 - - + maven-checkstyle-plugin + 2.11 - true - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.7 - - - - index - summary - license - help - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - Copyright© 2012-14 Jeremy Long. All Rights Reserved. - - - - default - - javadoc - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.1 - - - - dependency-updates-report - plugin-updates-report - - - - - - org.apache.maven.plugins - maven-jxr-plugin - 2.4 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.16 - - - - report-only - - - - - - org.codehaus.mojo - taglist-maven-plugin - 2.4 - - - - - Todo Work - - - todo - ignoreCase - - - FIXME - exact - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - false - ${basedir}/../src/main/config/checkstyle-checks.xml - ${basedir}/../src/main/config/checkstyle-header.txt - ${basedir}/../src/main/config/checkstyle-suppressions.xml - checkstyle.suppressions.file - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.0.1 - - 1.6 - true - utf-8 - - ../src/main/config/dcrules.xml - /rulesets/java/basic.xml - /rulesets/java/imports.xml - /rulesets/java/unusedcode.xml - - - - - org.codehaus.mojo - findbugs-maven-plugin - 2.5.3 - - + false + false + ${basedir}/../src/main/config/checkstyle-checks.xml + ${basedir}/../src/main/config/checkstyle-header.txt + ${basedir}/../src/main/config/checkstyle-suppressions.xml + checkstyle.suppressions.file + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.0.1 + + 1.6 + true + utf-8 + + **/org/owasp/dependencycheck/org/apache/**/*.java + + + ../src/main/config/dcrules.xml + /rulesets/java/basic.xml + /rulesets/java/imports.xml + /rulesets/java/unusedcode.xml + + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + org.owasp.dependencycheck.utils.* - + commons-io commons-io 2.4 - - junit - junit - 4.11 - test -
    diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/BuildException.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/BuildException.java new file mode 100644 index 000000000..59446d3ce --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/BuildException.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant; + +/** + * Signals an error condition during a build + */ +public class BuildException extends RuntimeException { + + private static final long serialVersionUID = -5419014565354664240L; + + /** Location in the build file where the exception occurred */ + private Location location = Location.UNKNOWN_LOCATION; + + /** + * Constructs a build exception with no descriptive information. + */ + public BuildException() { + super(); + } + + /** + * Constructs an exception with the given descriptive message. + * + * @param message A description of or information about the exception. + * Should not be null. + */ + public BuildException(String message) { + super(message); + } + + /** + * Constructs an exception with the given message and exception as + * a root cause. + * + * @param message A description of or information about the exception. + * Should not be null unless a cause is specified. + * @param cause The exception that might have caused this one. + * May be null. + */ + public BuildException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an exception with the given message and exception as + * a root cause and a location in a file. + * + * @param msg A description of or information about the exception. + * Should not be null unless a cause is specified. + * @param cause The exception that might have caused this one. + * May be null. + * @param location The location in the project file where the error + * occurred. Must not be null. + */ + public BuildException(String msg, Throwable cause, Location location) { + this(msg, cause); + this.location = location; + } + + /** + * Constructs an exception with the given exception as a root cause. + * + * @param cause The exception that might have caused this one. + * Should not be null. + */ + public BuildException(Throwable cause) { + super(cause); + } + + /** + * Constructs an exception with the given descriptive message and a + * location in a file. + * + * @param message A description of or information about the exception. + * Should not be null. + * @param location The location in the project file where the error + * occurred. Must not be null. + */ + public BuildException(String message, Location location) { + super(message); + this.location = location; + } + + /** + * Constructs an exception with the given exception as + * a root cause and a location in a file. + * + * @param cause The exception that might have caused this one. + * Should not be null. + * @param location The location in the project file where the error + * occurred. Must not be null. + */ + public BuildException(Throwable cause, Location location) { + this(cause); + this.location = location; + } + + /** + * Returns the nested exception, if any. + * + * @return the nested exception, or null if no + * exception is associated with this one + * @deprecated Use {@link #getCause} instead. + */ + public Throwable getException() { + return getCause(); + } + + /** + * Returns the location of the error and the error message. + * + * @return the location of the error and the error message + */ + public String toString() { + return location.toString() + getMessage(); + } + + /** + * Sets the file location where the error occurred. + * + * @param location The file location where the error occurred. + * Must not be null. + */ + public void setLocation(Location location) { + this.location = location; + } + + /** + * Returns the file location where the error occurred. + * + * @return the file location where the error occurred. + */ + public Location getLocation() { + return location; + } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/DirectoryScanner.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/DirectoryScanner.java new file mode 100644 index 000000000..e65d994a0 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/DirectoryScanner.java @@ -0,0 +1,1816 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.Vector; +import org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition.Os; +import org.owasp.dependencycheck.org.apache.tools.ant.types.Resource; +import org.owasp.dependencycheck.org.apache.tools.ant.types.ResourceFactory; +import org.owasp.dependencycheck.org.apache.tools.ant.types.resources.FileResource; +import org.owasp.dependencycheck.org.apache.tools.ant.types.selectors.FileSelector; +import org.owasp.dependencycheck.org.apache.tools.ant.types.selectors.SelectorScanner; +import org.owasp.dependencycheck.org.apache.tools.ant.types.selectors.SelectorUtils; +import org.owasp.dependencycheck.org.apache.tools.ant.types.selectors.TokenizedPath; +import org.owasp.dependencycheck.org.apache.tools.ant.types.selectors.TokenizedPattern; +import org.owasp.dependencycheck.org.apache.tools.ant.util.CollectionUtils; +import org.owasp.dependencycheck.org.apache.tools.ant.util.FileUtils; +import org.owasp.dependencycheck.org.apache.tools.ant.util.SymbolicLinkUtils; +import org.owasp.dependencycheck.org.apache.tools.ant.util.VectorSet; + +/** + * Class for scanning a directory for files/directories which match certain criteria. + *

    + * These criteria consist of selectors and patterns which have been specified. With the selectors you can select which + * files you want to have included. Files which are not selected are excluded. With patterns you can include or exclude + * files based on their filename. + *

    + * The idea is simple. A given directory is recursively scanned for all files and directories. Each file/directory is + * matched against a set of selectors, including special support for matching against filenames with include and and + * exclude patterns. Only files/directories which match at least one pattern of the include pattern list or other file + * selector, and don't match any pattern of the exclude pattern list or fail to match against a required selector will + * be placed in the list of files/directories found. + *

    + * When no list of include patterns is supplied, "**" will be used, which means that everything will be matched. When no + * list of exclude patterns is supplied, an empty list is used, such that nothing will be excluded. When no selectors + * are supplied, none are applied. + *

    + * The filename pattern matching is done as follows: The name to be matched is split up in path segments. A path segment + * is the name of a directory or file, which is bounded by File.separator ('/' under UNIX, '\' under + * Windows). For example, "abc/def/ghi/xyz.java" is split up in the segments "abc", "def","ghi" and "xyz.java". The same + * is done for the pattern against which should be matched. + *

    + * The segments of the name and the pattern are then matched against each other. When '**' is used for a path segment in + * the pattern, it matches zero or more path segments of the name. + *

    + * There is a special case regarding the use of File.separators at the beginning of the pattern and the + * string to match:
    + * When a pattern starts with a File.separator, the string to match must also start with a + * File.separator. When a pattern does not start with a File.separator, the string to match + * may not start with a File.separator. When one of these rules is not obeyed, the string will not match. + *

    + * When a name path segment is matched against a pattern path segment, the following special characters can be used:
    + * '*' matches zero or more characters
    + * '?' matches one character. + *

    + * Examples: + *

    + * "**\*.class" matches all .class files/dirs in a directory tree. + *

    + * "test\a??.java" matches all files/dirs which start with an 'a', then two more characters and then ".java", in a + * directory called test. + *

    + * "**" matches everything in a directory tree. + *

    + * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where there is a parent directory called test + * (e.g. "abc\test\def\ghi\XYZ123"). + *

    + * Case sensitivity may be turned off if necessary. By default, it is turned on. + *

    + * Example of usage: + *

    + *   String[] includes = {"**\\*.class"};
    + *   String[] excludes = {"modules\\*\\**"};
    + *   ds.setIncludes(includes);
    + *   ds.setExcludes(excludes);
    + *   ds.setBasedir(new File("test"));
    + *   ds.setCaseSensitive(true);
    + *   ds.scan();
    + *
    + *   System.out.println("FILES:");
    + *   String[] files = ds.getIncludedFiles();
    + *   for (int i = 0; i < files.length; i++) {
    + *     System.out.println(files[i]);
    + *   }
    + * 
    This will scan a directory called test for .class files, but excludes all files in all proper subdirectories + * of a directory called "modules". + * + */ +public class DirectoryScanner + implements FileScanner, SelectorScanner, ResourceFactory { + + /** + * Is OpenVMS the operating system we're running on? + */ + private static final boolean ON_VMS = Os.isFamily("openvms"); + + /** + * Patterns which should be excluded by default. + * + *

    + * Note that you can now add patterns to the list of default excludes. Added patterns will not become part of this + * array that has only been kept around for backwards compatibility reasons.

    + * + * @deprecated since 1.6.x. Use the {@link #getDefaultExcludes getDefaultExcludes} method instead. + */ + protected static final String[] DEFAULTEXCLUDES = { + // Miscellaneous typical temporary files + SelectorUtils.DEEP_TREE_MATCH + "/*~", + SelectorUtils.DEEP_TREE_MATCH + "/#*#", + SelectorUtils.DEEP_TREE_MATCH + "/.#*", + SelectorUtils.DEEP_TREE_MATCH + "/%*%", + SelectorUtils.DEEP_TREE_MATCH + "/._*", + // CVS + SelectorUtils.DEEP_TREE_MATCH + "/CVS", + SelectorUtils.DEEP_TREE_MATCH + "/CVS/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.cvsignore", + // SCCS + SelectorUtils.DEEP_TREE_MATCH + "/SCCS", + SelectorUtils.DEEP_TREE_MATCH + "/SCCS/" + SelectorUtils.DEEP_TREE_MATCH, + // Visual SourceSafe + SelectorUtils.DEEP_TREE_MATCH + "/vssver.scc", + // Subversion + SelectorUtils.DEEP_TREE_MATCH + "/.svn", + SelectorUtils.DEEP_TREE_MATCH + "/.svn/" + SelectorUtils.DEEP_TREE_MATCH, + // Git + SelectorUtils.DEEP_TREE_MATCH + "/.git", + SelectorUtils.DEEP_TREE_MATCH + "/.git/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.gitattributes", + SelectorUtils.DEEP_TREE_MATCH + "/.gitignore", + SelectorUtils.DEEP_TREE_MATCH + "/.gitmodules", + // Mercurial + SelectorUtils.DEEP_TREE_MATCH + "/.hg", + SelectorUtils.DEEP_TREE_MATCH + "/.hg/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.hgignore", + SelectorUtils.DEEP_TREE_MATCH + "/.hgsub", + SelectorUtils.DEEP_TREE_MATCH + "/.hgsubstate", + SelectorUtils.DEEP_TREE_MATCH + "/.hgtags", + // Bazaar + SelectorUtils.DEEP_TREE_MATCH + "/.bzr", + SelectorUtils.DEEP_TREE_MATCH + "/.bzr/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.bzrignore", + // Mac + SelectorUtils.DEEP_TREE_MATCH + "/.DS_Store" + }; + + /** + * default value for {@link #maxLevelsOfSymlinks maxLevelsOfSymlinks} + * + * @since Ant 1.8.0 + */ + public static final int MAX_LEVELS_OF_SYMLINKS = 5; + /** + * The end of the exception message if something that should be there doesn't exist. + */ + public static final String DOES_NOT_EXIST_POSTFIX = " does not exist."; + + /** + * Helper. + */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Helper. + */ + private static final SymbolicLinkUtils SYMLINK_UTILS + = SymbolicLinkUtils.getSymbolicLinkUtils(); + + /** + * Patterns which should be excluded by default. + * + * @see #addDefaultExcludes() + */ + private static final Set defaultExcludes = new HashSet(); + + static { + resetDefaultExcludes(); + } + + // CheckStyle:VisibilityModifier OFF - bc + /** + * The base directory to be scanned. + */ + protected File basedir; + + /** + * The patterns for the files to be included. + */ + protected String[] includes; + + /** + * The patterns for the files to be excluded. + */ + protected String[] excludes; + + /** + * Selectors that will filter which files are in our candidate list. + */ + protected FileSelector[] selectors = null; + + /** + * The files which matched at least one include and no excludes and were selected. + */ + protected Vector filesIncluded; + + /** + * The files which did not match any includes or selectors. + */ + protected Vector filesNotIncluded; + + /** + * The files which matched at least one include and at least one exclude. + */ + protected Vector filesExcluded; + + /** + * The directories which matched at least one include and no excludes and were selected. + */ + protected Vector dirsIncluded; + + /** + * The directories which were found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The directories which matched at least one include and at least one exclude. + */ + protected Vector dirsExcluded; + + /** + * The files which matched at least one include and no excludes and which a selector discarded. + */ + protected Vector filesDeselected; + + /** + * The directories which matched at least one include and no excludes but which a selector discarded. + */ + protected Vector dirsDeselected; + + /** + * Whether or not our results were built by a slow scan. + */ + protected boolean haveSlowResults = false; + + /** + * Whether or not the file system should be treated as a case sensitive one. + */ + protected boolean isCaseSensitive = true; + + /** + * Whether a missing base directory is an error. + * + * @since Ant 1.7.1 + */ + protected boolean errorOnMissingDir = true; + + /** + * Whether or not symbolic links should be followed. + * + * @since Ant 1.5 + */ + private boolean followSymlinks = true; + + /** + * Whether or not everything tested so far has been included. + */ + protected boolean everythingIncluded = true; + + // CheckStyle:VisibilityModifier ON + /** + * List of all scanned directories. + * + * @since Ant 1.6 + */ + private Set scannedDirs = new HashSet(); + + /** + * Map of all include patterns that are full file names and don't contain any wildcards. + * + *

    + * Maps pattern string to TokenizedPath.

    + * + *

    + * If this instance is not case sensitive, the file names get turned to upper case.

    + * + *

    + * Gets lazily initialized on the first invocation of isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).

    + * + * @since Ant 1.8.0 + */ + private Map includeNonPatterns = new HashMap(); + + /** + * Map of all exclude patterns that are full file names and don't contain any wildcards. + * + *

    + * Maps pattern string to TokenizedPath.

    + * + *

    + * If this instance is not case sensitive, the file names get turned to upper case.

    + * + *

    + * Gets lazily initialized on the first invocation of isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).

    + * + * @since Ant 1.8.0 + */ + private Map excludeNonPatterns = new HashMap(); + + /** + * Array of all include patterns that contain wildcards. + * + *

    + * Gets lazily initialized on the first invocation of isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).

    + */ + private TokenizedPattern[] includePatterns; + + /** + * Array of all exclude patterns that contain wildcards. + * + *

    + * Gets lazily initialized on the first invocation of isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).

    + */ + private TokenizedPattern[] excludePatterns; + + /** + * Have the non-pattern sets and pattern arrays for in- and excludes been initialized? + * + * @since Ant 1.6.3 + */ + private boolean areNonPatternSetsReady = false; + + /** + * Scanning flag. + * + * @since Ant 1.6.3 + */ + private boolean scanning = false; + + /** + * Scanning lock. + * + * @since Ant 1.6.3 + */ + private Object scanLock = new Object(); + + /** + * Slow scanning flag. + * + * @since Ant 1.6.3 + */ + private boolean slowScanning = false; + + /** + * Slow scanning lock. + * + * @since Ant 1.6.3 + */ + private Object slowScanLock = new Object(); + + /** + * Exception thrown during scan. + * + * @since Ant 1.6.3 + */ + private IllegalStateException illegal = null; + + /** + * The maximum number of times a symbolic link may be followed during a scan. + * + * @since Ant 1.8.0 + */ + private int maxLevelsOfSymlinks = MAX_LEVELS_OF_SYMLINKS; + + /** + * Absolute paths of all symlinks that haven't been followed but would have been if followsymlinks had been true or + * maxLevelsOfSymlinks had been higher. + * + * @since Ant 1.8.0 + */ + private Set notFollowedSymlinks = new HashSet(); + + /** + * Sole constructor. + */ + public DirectoryScanner() { + } + + /** + * Test whether or not a given path matches the start of a given pattern up to the first "**". + *

    + * This is not a general purpose test and should only be used if you can live with false positives. For example, + * pattern=**\a and str=b will yield true. + * + * @param pattern The pattern to match against. Must not be null. + * @param str The path to match, as a String. Must not be null. + * + * @return whether or not a given path matches the start of a given pattern up to the first "**". + */ + protected static boolean matchPatternStart(String pattern, String str) { + return SelectorUtils.matchPatternStart(pattern, str); + } + + /** + * Test whether or not a given path matches the start of a given pattern up to the first "**". + *

    + * This is not a general purpose test and should only be used if you can live with false positives. For example, + * pattern=**\a and str=b will yield true. + * + * @param pattern The pattern to match against. Must not be null. + * @param str The path to match, as a String. Must not be null. + * @param isCaseSensitive Whether or not matching should be performed case sensitively. + * + * @return whether or not a given path matches the start of a given pattern up to the first "**". + */ + protected static boolean matchPatternStart(String pattern, String str, + boolean isCaseSensitive) { + return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive); + } + + /** + * Test whether or not a given path matches a given pattern. + * + * @param pattern The pattern to match against. Must not be null. + * @param str The path to match, as a String. Must not be null. + * + * @return true if the pattern matches against the string, or false otherwise. + */ + protected static boolean matchPath(String pattern, String str) { + return SelectorUtils.matchPath(pattern, str); + } + + /** + * Test whether or not a given path matches a given pattern. + * + * @param pattern The pattern to match against. Must not be null. + * @param str The path to match, as a String. Must not be null. + * @param isCaseSensitive Whether or not matching should be performed case sensitively. + * + * @return true if the pattern matches against the string, or false otherwise. + */ + protected static boolean matchPath(String pattern, String str, + boolean isCaseSensitive) { + return SelectorUtils.matchPath(pattern, str, isCaseSensitive); + } + + /** + * Test whether or not a string matches against a pattern. The pattern may contain two special characters:
    + * '*' means zero or more characters
    + * '?' means one and only one character + * + * @param pattern The pattern to match against. Must not be null. + * @param str The string which must be matched against the pattern. Must not be null. + * + * @return true if the string matches against the pattern, or false otherwise. + */ + public static boolean match(String pattern, String str) { + return SelectorUtils.match(pattern, str); + } + + /** + * Test whether or not a string matches against a pattern. The pattern may contain two special characters:
    + * '*' means zero or more characters
    + * '?' means one and only one character + * + * @param pattern The pattern to match against. Must not be null. + * @param str The string which must be matched against the pattern. Must not be null. + * @param isCaseSensitive Whether or not matching should be performed case sensitively. + * + * + * @return true if the string matches against the pattern, or false otherwise. + */ + protected static boolean match(String pattern, String str, + boolean isCaseSensitive) { + return SelectorUtils.match(pattern, str, isCaseSensitive); + } + + /** + * Get the list of patterns that should be excluded by default. + * + * @return An array of String based on the current contents of the defaultExcludes + * Set. + * + * @since Ant 1.6 + */ + public static String[] getDefaultExcludes() { + synchronized (defaultExcludes) { + return (String[]) defaultExcludes.toArray(new String[defaultExcludes + .size()]); + } + } + + /** + * Add a pattern to the default excludes unless it is already a default exclude. + * + * @param s A string to add as an exclude pattern. + * @return true if the string was added; false if it already existed. + * + * @since Ant 1.6 + */ + public static boolean addDefaultExclude(String s) { + synchronized (defaultExcludes) { + return defaultExcludes.add(s); + } + } + + /** + * Remove a string if it is a default exclude. + * + * @param s The string to attempt to remove. + * @return true if s was a default exclude (and thus was removed); false if + * s was not in the default excludes list to begin with. + * + * @since Ant 1.6 + */ + public static boolean removeDefaultExclude(String s) { + synchronized (defaultExcludes) { + return defaultExcludes.remove(s); + } + } + + /** + * Go back to the hardwired default exclude patterns. + * + * @since Ant 1.6 + */ + public static void resetDefaultExcludes() { + synchronized (defaultExcludes) { + defaultExcludes.clear(); + for (int i = 0; i < DEFAULTEXCLUDES.length; i++) { + defaultExcludes.add(DEFAULTEXCLUDES[i]); + } + } + } + + /** + * Set the base directory to be scanned. This is the directory which is scanned recursively. All '/' and '\' + * characters are replaced by File.separatorChar, so the separator used need not match + * File.separatorChar. + * + * @param basedir The base directory to scan. + */ + public void setBasedir(String basedir) { + setBasedir(basedir == null ? (File) null + : new File(basedir.replace('/', File.separatorChar).replace( + '\\', File.separatorChar))); + } + + /** + * Set the base directory to be scanned. This is the directory which is scanned recursively. + * + * @param basedir The base directory for scanning. + */ + public synchronized void setBasedir(File basedir) { + this.basedir = basedir; + } + + /** + * Return the base directory to be scanned. This is the directory which is scanned recursively. + * + * @return the base directory to be scanned. + */ + public synchronized File getBasedir() { + return basedir; + } + + /** + * Find out whether include exclude patterns are matched in a case sensitive way. + * + * @return whether or not the scanning is case sensitive. + * @since Ant 1.6 + */ + public synchronized boolean isCaseSensitive() { + return isCaseSensitive; + } + + /** + * Set whether or not include and exclude patterns are matched in a case sensitive way. + * + * @param isCaseSensitive whether or not the file system should be regarded as a case sensitive one. + */ + public synchronized void setCaseSensitive(boolean isCaseSensitive) { + this.isCaseSensitive = isCaseSensitive; + } + + /** + * Sets whether or not a missing base directory is an error + * + * @param errorOnMissingDir whether or not a missing base directory is an error + * @since Ant 1.7.1 + */ + public void setErrorOnMissingDir(boolean errorOnMissingDir) { + this.errorOnMissingDir = errorOnMissingDir; + } + + /** + * Get whether or not a DirectoryScanner follows symbolic links. + * + * @return flag indicating whether symbolic links should be followed. + * + * @since Ant 1.6 + */ + public synchronized boolean isFollowSymlinks() { + return followSymlinks; + } + + /** + * Set whether or not symbolic links should be followed. + * + * @param followSymlinks whether or not symbolic links should be followed. + */ + public synchronized void setFollowSymlinks(boolean followSymlinks) { + this.followSymlinks = followSymlinks; + } + + /** + * The maximum number of times a symbolic link may be followed during a scan. + * + * @since Ant 1.8.0 + */ + public void setMaxLevelsOfSymlinks(int max) { + maxLevelsOfSymlinks = max; + } + + /** + * Set the list of include patterns to use. All '/' and '\' characters are replaced by + * File.separatorChar, so the separator used need not match File.separatorChar. + *

    + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param includes A list of include patterns. May be null, indicating that all files should be + * included. If a non-null list is given, all elements must be non-null. + */ + public synchronized void setIncludes(String[] includes) { + if (includes == null) { + this.includes = null; + } else { + this.includes = new String[includes.length]; + for (int i = 0; i < includes.length; i++) { + this.includes[i] = normalizePattern(includes[i]); + } + } + } + + public synchronized void setIncludes(String include) { + if (include == null) { + this.includes = null; + } else { + this.includes = new String[1]; + this.includes[0] = normalizePattern(include); + } + } + + /** + * Set the list of exclude patterns to use. All '/' and '\' characters are replaced by + * File.separatorChar, so the separator used need not match File.separatorChar. + *

    + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes A list of exclude patterns. May be null, indicating that no files should be + * excluded. If a non-null list is given, all elements must be non-null. + */ + public synchronized void setExcludes(String[] excludes) { + if (excludes == null) { + this.excludes = null; + } else { + this.excludes = new String[excludes.length]; + for (int i = 0; i < excludes.length; i++) { + this.excludes[i] = normalizePattern(excludes[i]); + } + } + } + + /** + * Add to the list of exclude patterns to use. All '/' and '\' characters are replaced by + * File.separatorChar, so the separator used need not match File.separatorChar. + *

    + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes A list of exclude patterns. May be null, in which case the exclude patterns don't + * get changed at all. + * + * @since Ant 1.6.3 + */ + public synchronized void addExcludes(String[] excludes) { + if (excludes != null && excludes.length > 0) { + if (this.excludes != null && this.excludes.length > 0) { + String[] tmp = new String[excludes.length + + this.excludes.length]; + System.arraycopy(this.excludes, 0, tmp, 0, + this.excludes.length); + for (int i = 0; i < excludes.length; i++) { + tmp[this.excludes.length + i] + = normalizePattern(excludes[i]); + } + this.excludes = tmp; + } else { + setExcludes(excludes); + } + } + } + + /** + * All '/' and '\' characters are replaced by File.separatorChar, so the separator used need not match + * File.separatorChar. + * + *

    + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @since Ant 1.6.3 + */ + private static String normalizePattern(String p) { + String pattern = p.replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + if (pattern.endsWith(File.separator)) { + pattern += SelectorUtils.DEEP_TREE_MATCH; + } + return pattern; + } + + /** + * Set the selectors that will select the filelist. + * + * @param selectors specifies the selectors to be invoked on a scan. + */ + public synchronized void setSelectors(FileSelector[] selectors) { + this.selectors = selectors; + } + + /** + * Return whether or not the scanner has included all the files or directories it has come across so far. + * + * @return true if all files and directories which have been found so far have been included. + */ + public synchronized boolean isEverythingIncluded() { + return everythingIncluded; + } + + /** + * Scan for files which match at least one include pattern and don't match any exclude patterns. If there are + * selectors then the files must pass muster there, as well. Scans under basedir, if set; otherwise the include + * patterns without leading wildcards specify the absolute paths of the files that may be included. + * + * @exception IllegalStateException if the base directory was set incorrectly (i.e. if it doesn't exist or isn't a + * directory). + */ + public void scan() throws IllegalStateException { + synchronized (scanLock) { + if (scanning) { + while (scanning) { + try { + scanLock.wait(); + } catch (InterruptedException e) { + continue; + } + } + if (illegal != null) { + throw illegal; + } + return; + } + scanning = true; + } + File savedBase = basedir; + try { + synchronized (this) { + illegal = null; + clearResults(); + + // set in/excludes to reasonable defaults if needed: + boolean nullIncludes = (includes == null); + includes = nullIncludes + ? new String[]{SelectorUtils.DEEP_TREE_MATCH} : includes; + boolean nullExcludes = (excludes == null); + excludes = nullExcludes ? new String[0] : excludes; + + if (basedir != null && !followSymlinks + && SYMLINK_UTILS.isSymbolicLink(basedir)) { + notFollowedSymlinks.add(basedir.getAbsolutePath()); + basedir = null; + } + + if (basedir == null) { + // if no basedir and no includes, nothing to do: + if (nullIncludes) { + return; + } + } else { + if (!basedir.exists()) { + if (errorOnMissingDir) { + illegal = new IllegalStateException("basedir " + + basedir + + DOES_NOT_EXIST_POSTFIX); + } else { + // Nothing to do - basedir does not exist + return; + } + } else if (!basedir.isDirectory()) { + illegal = new IllegalStateException("basedir " + + basedir + + " is not a" + + " directory."); + } + if (illegal != null) { + throw illegal; + } + } + if (isIncluded(TokenizedPath.EMPTY_PATH)) { + if (!isExcluded(TokenizedPath.EMPTY_PATH)) { + if (isSelected("", basedir)) { + dirsIncluded.addElement(""); + } else { + dirsDeselected.addElement(""); + } + } else { + dirsExcluded.addElement(""); + } + } else { + dirsNotIncluded.addElement(""); + } + checkIncludePatterns(); + clearCaches(); + includes = nullIncludes ? null : includes; + excludes = nullExcludes ? null : excludes; + } + } catch (IOException ex) { + throw new BuildException(ex); + } finally { + basedir = savedBase; + synchronized (scanLock) { + scanning = false; + scanLock.notifyAll(); + } + } + } + + /** + * This routine is actually checking all the include patterns in order to avoid scanning everything under base dir. + * + * @since Ant 1.6 + */ + private void checkIncludePatterns() { + ensureNonPatternSetsReady(); + Map newroots = new HashMap(); + + // put in the newroots map the include patterns without + // wildcard tokens + for (int i = 0; i < includePatterns.length; i++) { + String pattern = includePatterns[i].toString(); + if (!shouldSkipPattern(pattern)) { + newroots.put(includePatterns[i].rtrimWildcardTokens(), + pattern); + } + } + for (Map.Entry entry : includeNonPatterns.entrySet()) { + String pattern = entry.getKey(); + if (!shouldSkipPattern(pattern)) { + newroots.put(entry.getValue(), pattern); + } + } + + if (newroots.containsKey(TokenizedPath.EMPTY_PATH) + && basedir != null) { + // we are going to scan everything anyway + scandir(basedir, "", true); + } else { + File canonBase = null; + if (basedir != null) { + try { + canonBase = basedir.getCanonicalFile(); + } catch (IOException ex) { + throw new BuildException(ex); + } + } + // only scan directories that can include matched files or + // directories + for (Map.Entry entry : newroots.entrySet()) { + TokenizedPath currentPath = entry.getKey(); + String currentelement = currentPath.toString(); + if (basedir == null + && !FileUtils.isAbsolutePath(currentelement)) { + continue; + } + File myfile = new File(basedir, currentelement); + + if (myfile.exists()) { + // may be on a case insensitive file system. We want + // the results to show what's really on the disk, so + // we need to double check. + try { + String path = (basedir == null) + ? myfile.getCanonicalPath() + : FILE_UTILS.removeLeadingPath(canonBase, + myfile.getCanonicalFile()); + if (!path.equals(currentelement) || ON_VMS) { + myfile = currentPath.findFile(basedir, true); + if (myfile != null && basedir != null) { + currentelement = FILE_UTILS.removeLeadingPath( + basedir, myfile); + if (!currentPath.toString() + .equals(currentelement)) { + currentPath + = new TokenizedPath(currentelement); + } + } + } + } catch (IOException ex) { + throw new BuildException(ex); + } + } + + if ((myfile == null || !myfile.exists()) && !isCaseSensitive()) { + File f = currentPath.findFile(basedir, false); + if (f != null && f.exists()) { + // adapt currentelement to the case we've + // actually found + currentelement = (basedir == null) + ? f.getAbsolutePath() + : FILE_UTILS.removeLeadingPath(basedir, f); + myfile = f; + currentPath = new TokenizedPath(currentelement); + } + } + + if (myfile != null && myfile.exists()) { + if (!followSymlinks && currentPath.isSymlink(basedir)) { + accountForNotFollowedSymlink(currentPath, myfile); + continue; + } + if (myfile.isDirectory()) { + if (isIncluded(currentPath) + && currentelement.length() > 0) { + accountForIncludedDir(currentPath, myfile, true); + } else { + scandir(myfile, currentPath, true); + } + } else if (myfile.isFile()) { + String originalpattern = (String) entry.getValue(); + boolean included = isCaseSensitive() + ? originalpattern.equals(currentelement) + : originalpattern.equalsIgnoreCase(currentelement); + if (included) { + accountForIncludedFile(currentPath, myfile); + } + } + } + } + } + } + + /** + * true if the pattern specifies a relative path without basedir or an absolute path not inside basedir. + * + * @since Ant 1.8.0 + */ + private boolean shouldSkipPattern(String pattern) { + if (FileUtils.isAbsolutePath(pattern)) { + //skip abs. paths not under basedir, if set: + if (basedir != null + && !SelectorUtils.matchPatternStart(pattern, + basedir.getAbsolutePath(), + isCaseSensitive())) { + return true; + } + } else if (basedir == null) { + //skip non-abs. paths if basedir == null: + return true; + } + return false; + } + + /** + * Clear the result caches for a scan. + */ + protected synchronized void clearResults() { + filesIncluded = new VectorSet(); + filesNotIncluded = new VectorSet(); + filesExcluded = new VectorSet(); + filesDeselected = new VectorSet(); + dirsIncluded = new VectorSet(); + dirsNotIncluded = new VectorSet(); + dirsExcluded = new VectorSet(); + dirsDeselected = new VectorSet(); + everythingIncluded = (basedir != null); + scannedDirs.clear(); + notFollowedSymlinks.clear(); + } + + /** + * Top level invocation for a slow scan. A slow scan builds up a full list of excluded/included files/directories, + * whereas a fast scan will only have full results for included files, as it ignores directories which can't + * possibly hold any included files/directories. + *

    + * Returns immediately if a slow scan has already been completed. + */ + protected void slowScan() { + synchronized (slowScanLock) { + if (haveSlowResults) { + return; + } + if (slowScanning) { + while (slowScanning) { + try { + slowScanLock.wait(); + } catch (InterruptedException e) { + // Empty + } + } + return; + } + slowScanning = true; + } + try { + synchronized (this) { + + // set in/excludes to reasonable defaults if needed: + boolean nullIncludes = (includes == null); + includes = nullIncludes + ? new String[]{SelectorUtils.DEEP_TREE_MATCH} : includes; + boolean nullExcludes = (excludes == null); + excludes = nullExcludes ? new String[0] : excludes; + + String[] excl = new String[dirsExcluded.size()]; + dirsExcluded.copyInto(excl); + + String[] notIncl = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto(notIncl); + + ensureNonPatternSetsReady(); + + processSlowScan(excl); + processSlowScan(notIncl); + clearCaches(); + includes = nullIncludes ? null : includes; + excludes = nullExcludes ? null : excludes; + } + } finally { + synchronized (slowScanLock) { + haveSlowResults = true; + slowScanning = false; + slowScanLock.notifyAll(); + } + } + } + + private void processSlowScan(String[] arr) { + for (int i = 0; i < arr.length; i++) { + TokenizedPath path = new TokenizedPath(arr[i]); + if (!couldHoldIncluded(path) || contentsExcluded(path)) { + scandir(new File(basedir, arr[i]), path, false); + } + } + } + + /** + * Scan the given directory for files and directories. Found files and directories are placed in their respective + * collections, based on the matching of includes, excludes, and the selectors. When a directory is found, it is + * scanned recursively. + * + * @param dir The directory to scan. Must not be null. + * @param vpath The path relative to the base directory (needed to prevent problems with an absolute path when using + * dir). Must not be null. + * @param fast Whether or not this call is part of a fast scan. + * + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + * @see #slowScan + */ + protected void scandir(File dir, String vpath, boolean fast) { + scandir(dir, new TokenizedPath(vpath), fast); + } + + /** + * Scan the given directory for files and directories. Found files and directories are placed in their respective + * collections, based on the matching of includes, excludes, and the selectors. When a directory is found, it is + * scanned recursively. + * + * @param dir The directory to scan. Must not be null. + * @param path The path relative to the base directory (needed to prevent problems with an absolute path when using + * dir). Must not be null. + * @param fast Whether or not this call is part of a fast scan. + * + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + * @see #slowScan + */ + private void scandir(File dir, TokenizedPath path, boolean fast) { + if (dir == null) { + throw new BuildException("dir must not be null."); + } + String[] newfiles = dir.list(); + if (newfiles == null) { + if (!dir.exists()) { + throw new BuildException(dir + DOES_NOT_EXIST_POSTFIX); + } else if (!dir.isDirectory()) { + throw new BuildException(dir + " is not a directory."); + } else { + throw new BuildException("IO error scanning directory '" + + dir.getAbsolutePath() + "'"); + } + } + scandir(dir, path, fast, newfiles, new LinkedList()); + } + + private void scandir(File dir, TokenizedPath path, boolean fast, + String[] newfiles, LinkedList directoryNamesFollowed) { + String vpath = path.toString(); + if (vpath.length() > 0 && !vpath.endsWith(File.separator)) { + vpath += File.separator; + } + + // avoid double scanning of directories, can only happen in fast mode + if (fast && hasBeenScanned(vpath)) { + return; + } + if (!followSymlinks) { + ArrayList noLinks = new ArrayList(); + for (int i = 0; i < newfiles.length; i++) { + try { + if (SYMLINK_UTILS.isSymbolicLink(dir, newfiles[i])) { + String name = vpath + newfiles[i]; + File file = new File(dir, newfiles[i]); + if (file.isDirectory()) { + dirsExcluded.addElement(name); + } else if (file.isFile()) { + filesExcluded.addElement(name); + } + accountForNotFollowedSymlink(name, file); + } else { + noLinks.add(newfiles[i]); + } + } catch (IOException ioe) { + String msg = "IOException caught while checking " + + "for links, couldn't get canonical path!"; + // will be caught and redirected to Ant's logging system + System.err.println(msg); + noLinks.add(newfiles[i]); + } + } + newfiles = (String[]) (noLinks.toArray(new String[noLinks.size()])); + } else { + directoryNamesFollowed.addFirst(dir.getName()); + } + + for (int i = 0; i < newfiles.length; i++) { + String name = vpath + newfiles[i]; + TokenizedPath newPath = new TokenizedPath(path, newfiles[i]); + File file = new File(dir, newfiles[i]); + String[] children = file.list(); + if (children == null || (children.length == 0 && file.isFile())) { + if (isIncluded(newPath)) { + accountForIncludedFile(newPath, file); + } else { + everythingIncluded = false; + filesNotIncluded.addElement(name); + } + } else if (file.isDirectory()) { // dir + + if (followSymlinks + && causesIllegalSymlinkLoop(newfiles[i], dir, + directoryNamesFollowed)) { + // will be caught and redirected to Ant's logging system + System.err.println("skipping symbolic link " + + file.getAbsolutePath() + + " -- too many levels of symbolic" + + " links."); + notFollowedSymlinks.add(file.getAbsolutePath()); + continue; + } + + if (isIncluded(newPath)) { + accountForIncludedDir(newPath, file, fast, children, + directoryNamesFollowed); + } else { + everythingIncluded = false; + dirsNotIncluded.addElement(name); + if (fast && couldHoldIncluded(newPath) + && !contentsExcluded(newPath)) { + scandir(file, newPath, fast, children, + directoryNamesFollowed); + } + } + if (!fast) { + scandir(file, newPath, fast, children, directoryNamesFollowed); + } + } + } + + if (followSymlinks) { + directoryNamesFollowed.removeFirst(); + } + } + + /** + * Process included file. + * + * @param name path of the file relative to the directory of the FileSet. + * @param file included File. + */ + private void accountForIncludedFile(TokenizedPath name, File file) { + processIncluded(name, file, filesIncluded, filesExcluded, + filesDeselected); + } + + /** + * Process included directory. + * + * @param name path of the directory relative to the directory of the FileSet. + * @param file directory as File. + * @param fast whether to perform fast scans. + */ + private void accountForIncludedDir(TokenizedPath name, File file, + boolean fast) { + processIncluded(name, file, dirsIncluded, dirsExcluded, dirsDeselected); + if (fast && couldHoldIncluded(name) && !contentsExcluded(name)) { + scandir(file, name, fast); + } + } + + private void accountForIncludedDir(TokenizedPath name, + File file, boolean fast, + String[] children, + LinkedList directoryNamesFollowed) { + processIncluded(name, file, dirsIncluded, dirsExcluded, dirsDeselected); + if (fast && couldHoldIncluded(name) && !contentsExcluded(name)) { + scandir(file, name, fast, children, directoryNamesFollowed); + } + } + + private void accountForNotFollowedSymlink(String name, File file) { + accountForNotFollowedSymlink(new TokenizedPath(name), file); + } + + private void accountForNotFollowedSymlink(TokenizedPath name, File file) { + if (!isExcluded(name) + && (isIncluded(name) + || (file.isDirectory() && couldHoldIncluded(name) + && !contentsExcluded(name)))) { + notFollowedSymlinks.add(file.getAbsolutePath()); + } + } + + private void processIncluded(TokenizedPath path, + File file, Vector inc, Vector exc, + Vector des) { + String name = path.toString(); + if (inc.contains(name) || exc.contains(name) || des.contains(name)) { + return; + } + + boolean included = false; + if (isExcluded(path)) { + exc.add(name); + } else if (isSelected(name, file)) { + included = true; + inc.add(name); + } else { + des.add(name); + } + everythingIncluded &= included; + } + + /** + * Test whether or not a name matches against at least one include pattern. + * + * @param name The name to match. Must not be null. + * @return true when the name matches against at least one include pattern, or false + * otherwise. + */ + protected boolean isIncluded(String name) { + return isIncluded(new TokenizedPath(name)); + } + + /** + * Test whether or not a name matches against at least one include pattern. + * + * @param name The name to match. Must not be null. + * @return true when the name matches against at least one include pattern, or false + * otherwise. + */ + private boolean isIncluded(TokenizedPath path) { + ensureNonPatternSetsReady(); + + if (isCaseSensitive() + ? includeNonPatterns.containsKey(path.toString()) + : includeNonPatterns.containsKey(path.toString().toUpperCase())) { + return true; + } + for (int i = 0; i < includePatterns.length; i++) { + if (includePatterns[i].matchPath(path, isCaseSensitive())) { + return true; + } + } + return false; + } + + /** + * Test whether or not a name matches the start of at least one include pattern. + * + * @param name The name to match. Must not be null. + * @return true when the name matches against the start of at least one include pattern, or + * false otherwise. + */ + protected boolean couldHoldIncluded(String name) { + return couldHoldIncluded(new TokenizedPath(name)); + } + + /** + * Test whether or not a name matches the start of at least one include pattern. + * + * @param tokenizedName The name to match. Must not be null. + * @return true when the name matches against the start of at least one include pattern, or + * false otherwise. + */ + private boolean couldHoldIncluded(TokenizedPath tokenizedName) { + for (int i = 0; i < includePatterns.length; i++) { + if (couldHoldIncluded(tokenizedName, includePatterns[i])) { + return true; + } + } + for (Iterator iter = includeNonPatterns.values().iterator(); + iter.hasNext();) { + if (couldHoldIncluded(tokenizedName, + iter.next().toPattern())) { + return true; + } + } + return false; + } + + /** + * Test whether or not a name matches the start of the given include pattern. + * + * @param tokenizedName The name to match. Must not be null. + * @return true when the name matches against the start of the include pattern, or false + * otherwise. + */ + private boolean couldHoldIncluded(TokenizedPath tokenizedName, + TokenizedPattern tokenizedInclude) { + return tokenizedInclude.matchStartOf(tokenizedName, isCaseSensitive()) + && isMorePowerfulThanExcludes(tokenizedName.toString()) + && isDeeper(tokenizedInclude, tokenizedName); + } + + /** + * Verify that a pattern specifies files deeper than the level of the specified file. + * + * @param pattern the pattern to check. + * @param name the name to check. + * @return whether the pattern is deeper than the name. + * @since Ant 1.6.3 + */ + private boolean isDeeper(TokenizedPattern pattern, TokenizedPath name) { + return pattern.containsPattern(SelectorUtils.DEEP_TREE_MATCH) + || pattern.depth() > name.depth(); + } + + /** + * Find out whether one particular include pattern is more powerful than all the excludes. Note: the power + * comparison is based on the length of the include pattern and of the exclude patterns without the wildcards. + * Ideally the comparison should be done based on the depth of the match; that is to say how many file separators + * have been matched before the first ** or the end of the pattern. + * + * IMPORTANT : this function should return false "with care". + * + * @param name the relative path to test. + * @return true if there is no exclude pattern more powerful than this include pattern. + * @since Ant 1.6 + */ + private boolean isMorePowerfulThanExcludes(String name) { + final String soughtexclude + = name + File.separatorChar + SelectorUtils.DEEP_TREE_MATCH; + for (int counter = 0; counter < excludePatterns.length; counter++) { + if (excludePatterns[counter].toString().equals(soughtexclude)) { + return false; + } + } + return true; + } + + /** + * Test whether all contents of the specified directory must be excluded. + * + * @param path the path to check. + * @return whether all the specified directory's contents are excluded. + */ + /* package */ boolean contentsExcluded(TokenizedPath path) { + for (int i = 0; i < excludePatterns.length; i++) { + if (excludePatterns[i].endsWith(SelectorUtils.DEEP_TREE_MATCH) + && excludePatterns[i].withoutLastToken() + .matchPath(path, isCaseSensitive())) { + return true; + } + } + return false; + } + + /** + * Test whether or not a name matches against at least one exclude pattern. + * + * @param name The name to match. Must not be null. + * @return true when the name matches against at least one exclude pattern, or false + * otherwise. + */ + protected boolean isExcluded(String name) { + return isExcluded(new TokenizedPath(name)); + } + + /** + * Test whether or not a name matches against at least one exclude pattern. + * + * @param name The name to match. Must not be null. + * @return true when the name matches against at least one exclude pattern, or false + * otherwise. + */ + private boolean isExcluded(TokenizedPath name) { + ensureNonPatternSetsReady(); + + if (isCaseSensitive() + ? excludeNonPatterns.containsKey(name.toString()) + : excludeNonPatterns.containsKey(name.toString().toUpperCase())) { + return true; + } + for (int i = 0; i < excludePatterns.length; i++) { + if (excludePatterns[i].matchPath(name, isCaseSensitive())) { + return true; + } + } + return false; + } + + /** + * Test whether a file should be selected. + * + * @param name the filename to check for selecting. + * @param file the java.io.File object for this filename. + * @return false when the selectors says that the file should not be selected, true + * otherwise. + */ + protected boolean isSelected(String name, File file) { + if (selectors != null) { + for (int i = 0; i < selectors.length; i++) { + if (!selectors[i].isSelected(basedir, name, file)) { + return false; + } + } + } + return true; + } + + /** + * Return the names of the files which matched at least one of the include patterns and none of the exclude + * patterns. The names are relative to the base directory. + * + * @return the names of the files which matched at least one of the include patterns and none of the exclude + * patterns. + */ + public String[] getIncludedFiles() { + String[] files; + synchronized (this) { + if (filesIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + files = new String[filesIncluded.size()]; + filesIncluded.copyInto(files); + } + Arrays.sort(files); + return files; + } + + /** + * Return the count of included files. + * + * @return int. + * @since Ant 1.6.3 + */ + public synchronized int getIncludedFilesCount() { + if (filesIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + return filesIncluded.size(); + } + + /** + * Return the names of the files which matched none of the include patterns. The names are relative to the base + * directory. This involves performing a slow scan if one has not already been completed. + * + * @return the names of the files which matched none of the include patterns. + * + * @see #slowScan + */ + public synchronized String[] getNotIncludedFiles() { + slowScan(); + String[] files = new String[filesNotIncluded.size()]; + filesNotIncluded.copyInto(files); + return files; + } + + /** + * Return the names of the files which matched at least one of the include patterns and at least one of the exclude + * patterns. The names are relative to the base directory. This involves performing a slow scan if one has not + * already been completed. + * + * @return the names of the files which matched at least one of the include patterns and at least one of the exclude + * patterns. + * + * @see #slowScan + */ + public synchronized String[] getExcludedFiles() { + slowScan(); + String[] files = new String[filesExcluded.size()]; + filesExcluded.copyInto(files); + return files; + } + + /** + *

    + * Return the names of the files which were selected out and therefore not ultimately included.

    + * + *

    + * The names are relative to the base directory. This involves performing a slow scan if one has not already been + * completed.

    + * + * @return the names of the files which were deselected. + * + * @see #slowScan + */ + public synchronized String[] getDeselectedFiles() { + slowScan(); + String[] files = new String[filesDeselected.size()]; + filesDeselected.copyInto(files); + return files; + } + + /** + * Return the names of the directories which matched at least one of the include patterns and none of the exclude + * patterns. The names are relative to the base directory. + * + * @return the names of the directories which matched at least one of the include patterns and none of the exclude + * patterns. + */ + public String[] getIncludedDirectories() { + String[] directories; + synchronized (this) { + if (dirsIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + directories = new String[dirsIncluded.size()]; + dirsIncluded.copyInto(directories); + } + Arrays.sort(directories); + return directories; + } + + /** + * Return the count of included directories. + * + * @return int. + * @since Ant 1.6.3 + */ + public synchronized int getIncludedDirsCount() { + if (dirsIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + return dirsIncluded.size(); + } + + /** + * Return the names of the directories which matched none of the include patterns. The names are relative to the + * base directory. This involves performing a slow scan if one has not already been completed. + * + * @return the names of the directories which matched none of the include patterns. + * + * @see #slowScan + */ + public synchronized String[] getNotIncludedDirectories() { + slowScan(); + String[] directories = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto(directories); + return directories; + } + + /** + * Return the names of the directories which matched at least one of the include patterns and at least one of the + * exclude patterns. The names are relative to the base directory. This involves performing a slow scan if one has + * not already been completed. + * + * @return the names of the directories which matched at least one of the include patterns and at least one of the + * exclude patterns. + * + * @see #slowScan + */ + public synchronized String[] getExcludedDirectories() { + slowScan(); + String[] directories = new String[dirsExcluded.size()]; + dirsExcluded.copyInto(directories); + return directories; + } + + /** + *

    + * Return the names of the directories which were selected out and therefore not ultimately included.

    + * + *

    + * The names are relative to the base directory. This involves performing a slow scan if one has not already been + * completed.

    + * + * @return the names of the directories which were deselected. + * + * @see #slowScan + */ + public synchronized String[] getDeselectedDirectories() { + slowScan(); + String[] directories = new String[dirsDeselected.size()]; + dirsDeselected.copyInto(directories); + return directories; + } + + /** + * Absolute paths of all symbolic links that haven't been followed but would have been followed had followsymlinks + * been true or maxLevelsOfSymlinks been bigger. + * + * @return sorted array of not followed symlinks + * @since Ant 1.8.0 + * @see #notFollowedSymlinks + */ + public synchronized String[] getNotFollowedSymlinks() { + String[] links; + synchronized (this) { + links = (String[]) notFollowedSymlinks + .toArray(new String[notFollowedSymlinks.size()]); + } + Arrays.sort(links); + return links; + } + + /** + * Add default exclusions to the current exclusions set. + */ + public synchronized void addDefaultExcludes() { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + String[] defaultExcludesTemp = getDefaultExcludes(); + newExcludes = new String[excludesLength + defaultExcludesTemp.length]; + if (excludesLength > 0) { + System.arraycopy(excludes, 0, newExcludes, 0, excludesLength); + } + for (int i = 0; i < defaultExcludesTemp.length; i++) { + newExcludes[i + excludesLength] + = defaultExcludesTemp[i].replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + } + excludes = newExcludes; + } + + /** + * Get the named resource. + * + * @param name path name of the file relative to the dir attribute. + * + * @return the resource with the given name. + * @since Ant 1.5.2 + */ + public synchronized Resource getResource(String name) { + return new FileResource(basedir, name); + } + + /** + * Has the directory with the given path relative to the base directory already been scanned? + * + *

    + * Registers the given directory as scanned as a side effect.

    + * + * @since Ant 1.6 + */ + private boolean hasBeenScanned(String vpath) { + return !scannedDirs.add(vpath); + } + + /** + * This method is of interest for testing purposes. The returned Set is live and should not be modified. + * + * @return the Set of relative directory names that have been scanned. + */ + /* package-private */ Set getScannedDirs() { + return scannedDirs; + } + + /** + * Clear internal caches. + * + * @since Ant 1.6 + */ + private synchronized void clearCaches() { + includeNonPatterns.clear(); + excludeNonPatterns.clear(); + includePatterns = null; + excludePatterns = null; + areNonPatternSetsReady = false; + } + + /** + * Ensure that the in|exclude "patterns" have been properly divided up. + * + * @since Ant 1.6.3 + */ + /* package */ synchronized void ensureNonPatternSetsReady() { + if (!areNonPatternSetsReady) { + includePatterns = fillNonPatternSet(includeNonPatterns, includes); + excludePatterns = fillNonPatternSet(excludeNonPatterns, excludes); + areNonPatternSetsReady = true; + } + } + + /** + * Add all patterns that are not real patterns (do not contain wildcards) to the set and returns the real patterns. + * + * @param map Map to populate. + * @param patterns String[] of patterns. + * @since Ant 1.8.0 + */ + private TokenizedPattern[] fillNonPatternSet(Map map, String[] patterns) { + ArrayList al = new ArrayList(patterns.length); + for (int i = 0; i < patterns.length; i++) { + if (!SelectorUtils.hasWildcards(patterns[i])) { + String s = isCaseSensitive() + ? patterns[i] : patterns[i].toUpperCase(); + map.put(s, new TokenizedPath(s)); + } else { + al.add(new TokenizedPattern(patterns[i])); + } + } + return (TokenizedPattern[]) al.toArray(new TokenizedPattern[al.size()]); + } + + /** + * Would following the given directory cause a loop of symbolic links deeper than allowed? + * + *

    + * Can only happen if the given directory has been seen at least more often than allowed during the current scan and + * it is a symbolic link and enough other occurrences of the same name higher up are symbolic links that point to + * the same place.

    + * + * @since Ant 1.8.0 + */ + private boolean causesIllegalSymlinkLoop(String dirName, File parent, + LinkedList directoryNamesFollowed) { + try { + if (directoryNamesFollowed.size() >= maxLevelsOfSymlinks + && CollectionUtils.frequency(directoryNamesFollowed, dirName) + >= maxLevelsOfSymlinks + && SYMLINK_UTILS.isSymbolicLink(parent, dirName)) { + + ArrayList files = new ArrayList(); + File f = FILE_UTILS.resolveFile(parent, dirName); + String target = f.getCanonicalPath(); + files.add(target); + + String relPath = ""; + for (String dir : directoryNamesFollowed) { + relPath += "../"; + if (dirName.equals(dir)) { + f = FILE_UTILS.resolveFile(parent, relPath + dir); + files.add(f.getCanonicalPath()); + if (files.size() > maxLevelsOfSymlinks + && CollectionUtils.frequency(files, target) + > maxLevelsOfSymlinks) { + return true; + } + } + } + + } + return false; + } catch (IOException ex) { + throw new BuildException("Caught error while checking for" + + " symbolic links", ex); + } + } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/FileScanner.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/FileScanner.java new file mode 100644 index 000000000..2fdd654f5 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/FileScanner.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant; + +import java.io.File; + +/** + * An interface used to describe the actions required of any type of + * directory scanner. + * + */ +public interface FileScanner { + /** + * Adds default exclusions to the current exclusions set. + */ + void addDefaultExcludes(); + + /** + * Returns the base directory to be scanned. + * This is the directory which is scanned recursively. + * + * @return the base directory to be scanned + */ + File getBasedir(); + + /** + * Returns the names of the directories which matched at least one of the + * include patterns and at least one of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the directories which matched at least one of the + * include patterns and at least one of the exclude patterns. + */ + String[] getExcludedDirectories(); + + /** + * Returns the names of the files which matched at least one of the + * include patterns and at least one of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the files which matched at least one of the + * include patterns and at least one of the exclude patterns. + * + */ + String[] getExcludedFiles(); + + /** + * Returns the names of the directories which matched at least one of the + * include patterns and none of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the directories which matched at least one of the + * include patterns and none of the exclude patterns. + */ + String[] getIncludedDirectories(); + + /** + * Returns the names of the files which matched at least one of the + * include patterns and none of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the files which matched at least one of the + * include patterns and none of the exclude patterns. + */ + String[] getIncludedFiles(); + + /** + * Returns the names of the directories which matched none of the include + * patterns. The names are relative to the base directory. + * + * @return the names of the directories which matched none of the include + * patterns. + */ + String[] getNotIncludedDirectories(); + + /** + * Returns the names of the files which matched none of the include + * patterns. The names are relative to the base directory. + * + * @return the names of the files which matched none of the include + * patterns. + */ + String[] getNotIncludedFiles(); + + /** + * Scans the base directory for files which match at least one include + * pattern and don't match any exclude patterns. + * + * @exception IllegalStateException if the base directory was set + * incorrectly (i.e. if it is null, doesn't exist, + * or isn't a directory). + */ + void scan() throws IllegalStateException; + + /** + * Sets the base directory to be scanned. This is the directory which is + * scanned recursively. All '/' and '\' characters should be replaced by + * File.separatorChar, so the separator used need not match + * File.separatorChar. + * + * @param basedir The base directory to scan. + * Must not be null. + */ + void setBasedir(String basedir); + + /** + * Sets the base directory to be scanned. This is the directory which is + * scanned recursively. + * + * @param basedir The base directory for scanning. + * Should not be null. + */ + void setBasedir(File basedir); + + /** + * Sets the list of exclude patterns to use. + * + * @param excludes A list of exclude patterns. + * May be null, indicating that no files + * should be excluded. If a non-null list is + * given, all elements must be non-null. + */ + void setExcludes(String[] excludes); + + /** + * Sets the list of include patterns to use. + * + * @param includes A list of include patterns. + * May be null, indicating that all files + * should be included. If a non-null + * list is given, all elements must be + * non-null. + */ + void setIncludes(String[] includes); + + /** + * Sets whether or not the file system should be regarded as case sensitive. + * + * @param isCaseSensitive whether or not the file system should be + * regarded as a case sensitive one + */ + void setCaseSensitive(boolean isCaseSensitive); +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/Location.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/Location.java new file mode 100644 index 000000000..44137ab5b --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/Location.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant; + +import java.io.Serializable; +import org.owasp.dependencycheck.org.apache.tools.ant.util.FileUtils; +import org.xml.sax.Locator; + +/** + * Stores the location of a piece of text within a file (file name, + * line number and column number). Note that the column number is + * currently ignored. + * + */ +public class Location implements Serializable { + private static final long serialVersionUID = 1L; + + /** Name of the file. */ + private final String fileName; + /** Line number within the file. */ + private final int lineNumber; + /** Column number within the file. */ + private final int columnNumber; + + /** Location to use when one is needed but no information is available */ + public static final Location UNKNOWN_LOCATION = new Location(); + + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Creates an "unknown" location. + */ + private Location() { + this(null, 0, 0); + } + + /** + * Creates a location consisting of a file name but no line number or + * column number. + * + * @param fileName The name of the file. May be null, + * in which case the location is equivalent to + * {@link #UNKNOWN_LOCATION UNKNOWN_LOCATION}. + */ + public Location(String fileName) { + this(fileName, 0, 0); + } + + /** + * Creates a location from the SAX locator using the system ID as + * the filename. + * + * @param loc Must not be null. + * + * @since Ant 1.6 + */ + public Location(Locator loc) { + this(loc.getSystemId(), loc.getLineNumber(), loc.getColumnNumber()); + } + + /** + * Creates a location consisting of a file name, line number and + * column number. + * + * @param fileName The name of the file. May be null, + * in which case the location is equivalent to + * {@link #UNKNOWN_LOCATION UNKNOWN_LOCATION}. + * + * @param lineNumber Line number within the file. Use 0 for unknown + * positions within a file. + * @param columnNumber Column number within the line. + */ + public Location(String fileName, int lineNumber, int columnNumber) { + if (fileName != null && fileName.startsWith("file:")) { + this.fileName = FILE_UTILS.fromURI(fileName); + } else { + this.fileName = fileName; + } + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + /** + * @return the filename portion of the location + * @since Ant 1.6 + */ + public String getFileName() { + return fileName; + } + + /** + * @return the line number + * @since Ant 1.6 + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * @return the column number + * @since Ant 1.7 + */ + public int getColumnNumber() { + return columnNumber; + } + + /** + * Returns the file name, line number, a colon and a trailing space. + * An error message can be appended easily. For unknown locations, an + * empty string is returned. + * + * @return a String of the form "fileName:lineNumber: " + * if both file name and line number are known, + * "fileName: " if only the file name is known, + * and the empty string for unknown locations. + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + + if (fileName != null) { + buf.append(fileName); + + if (lineNumber != 0) { + buf.append(":"); + buf.append(lineNumber); + } + + buf.append(": "); + } + + return buf.toString(); + } + + /** + * Equality operation. + * @param other the object to compare to. + * @return true if the other object contains the same information + * as this object. + * @since Ant 1.6.3 + */ + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (!(other.getClass() == getClass())) { + return false; + } + return toString().equals(other.toString()); + } + + /** + * Hash operation. + * @return a hash code value for this location. + * @since Ant 1.6.3 + */ + public int hashCode() { + return toString().hashCode(); + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/PathTokenizer.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/PathTokenizer.java new file mode 100644 index 000000000..5d1a9a47b --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/PathTokenizer.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant; + +import java.io.File; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition.Os; + +/** + * A Path tokenizer takes a path and returns the components that make up + * that path. + * + * The path can use path separators of either ':' or ';' and file separators + * of either '/' or '\'. + * + */ +public class PathTokenizer { + /** + * A tokenizer to break the string up based on the ':' or ';' separators. + */ + private StringTokenizer tokenizer; + + /** + * A String which stores any path components which have been read ahead + * due to DOS filesystem compensation. + */ + private String lookahead = null; + + /** + * A boolean that determines if we are running on Novell NetWare, which + * exhibits slightly different path name characteristics (multi-character + * volume / drive names) + */ + private boolean onNetWare = Os.isFamily("netware"); + + /** + * Flag to indicate whether or not we are running on a platform with a + * DOS style filesystem + */ + private boolean dosStyleFilesystem; + + /** + * Constructs a path tokenizer for the specified path. + * + * @param path The path to tokenize. Must not be null. + */ + public PathTokenizer(String path) { + if (onNetWare) { + // For NetWare, use the boolean=true mode, so we can use delimiter + // information to make a better decision later. + tokenizer = new StringTokenizer(path, ":;", true); + } else { + // on Windows and Unix, we can ignore delimiters and still have + // enough information to tokenize correctly. + tokenizer = new StringTokenizer(path, ":;", false); + } + dosStyleFilesystem = File.pathSeparatorChar == ';'; + } + + /** + * Tests if there are more path elements available from this tokenizer's + * path. If this method returns true, then a subsequent call + * to nextToken will successfully return a token. + * + * @return true if and only if there is at least one token + * in the string after the current position; false otherwise. + */ + public boolean hasMoreTokens() { + if (lookahead != null) { + return true; + } + + return tokenizer.hasMoreTokens(); + } + + /** + * Returns the next path element from this tokenizer. + * + * @return the next path element from this tokenizer. + * + * @exception NoSuchElementException if there are no more elements in this + * tokenizer's path. + */ + public String nextToken() throws NoSuchElementException { + String token = null; + if (lookahead != null) { + token = lookahead; + lookahead = null; + } else { + token = tokenizer.nextToken().trim(); + } + + if (!onNetWare) { + if (token.length() == 1 && Character.isLetter(token.charAt(0)) + && dosStyleFilesystem + && tokenizer.hasMoreTokens()) { + // we are on a dos style system so this path could be a drive + // spec. We look at the next token + String nextToken = tokenizer.nextToken().trim(); + if (nextToken.startsWith("\\") || nextToken.startsWith("/")) { + // we know we are on a DOS style platform and the next path + // starts with a slash or backslash, so we know this is a + // drive spec + token += ":" + nextToken; + } else { + // store the token just read for next time + lookahead = nextToken; + } + } + } else { + // we are on NetWare, tokenizing is handled a little differently, + // due to the fact that NetWare has multiple-character volume names. + if (token.equals(File.pathSeparator) || token.equals(":")) { + // ignore ";" and get the next token + token = tokenizer.nextToken().trim(); + } + + if (tokenizer.hasMoreTokens()) { + // this path could be a drive spec, so look at the next token + String nextToken = tokenizer.nextToken().trim(); + + // make sure we aren't going to get the path separator next + if (!nextToken.equals(File.pathSeparator)) { + if (nextToken.equals(":")) { + if (!token.startsWith("/") && !token.startsWith("\\") + && !token.startsWith(".") + && !token.startsWith("..")) { + // it indeed is a drive spec, get the next bit + String oneMore = tokenizer.nextToken().trim(); + if (!oneMore.equals(File.pathSeparator)) { + token += ":" + oneMore; + } else { + token += ":"; + lookahead = oneMore; + } + } + // implicit else: ignore the ':' since we have either a + // UNIX or a relative path + } else { + // store the token just read for next time + lookahead = nextToken; + } + } + } + } + return token; + } +} + diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/ProjectComponent.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/ProjectComponent.java new file mode 100644 index 000000000..7b57e4956 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/ProjectComponent.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant; + +/** + * Base class for components of a project, including tasks and data types. Provides common facilities. + * + */ +public abstract class ProjectComponent implements Cloneable { + +// // CheckStyle:VisibilityModifier OFF - bc +// /** +// * Project object of this component. +// * @deprecated since 1.6.x. +// * You should not be directly accessing this variable directly. +// * You should access project object via the getProject() +// * or setProject() accessor/mutators. +// */ +// protected Project project; + /** + * Location within the build file of this task definition. + * + * @deprecated since 1.6.x. You should not be accessing this variable directly. Please use the + * {@link #getLocation()} method. + */ + protected Location location = Location.UNKNOWN_LOCATION; + + /** + * Description of this component, if any. + * + * @deprecated since 1.6.x. You should not be accessing this variable directly. + */ + protected String description; + // CheckStyle:VisibilityModifier ON + + /** + * Sole constructor. + */ + public ProjectComponent() { + } + +// /** +// * Sets the project object of this component. This method is used by +// * Project when a component is added to it so that the component has +// * access to the functions of the project. It should not be used +// * for any other purpose. +// * +// * @param project Project in whose scope this component belongs. +// * Must not be null. +// */ +// public void setProject(Project project) { +// this.project = project; +// } +// +// /** +// * Returns the project to which this component belongs. +// * +// * @return the components's project. +// */ +// public Project getProject() { +// return project; +// } + /** + * Returns the file/location where this task was defined. + * + * @return the file/location where this task was defined. Should not return null. + * Location.UNKNOWN_LOCATION is used for unknown locations. + * + * @see Location#UNKNOWN_LOCATION + */ + public Location getLocation() { + return location; + } + + /** + * Sets the file/location where this task was defined. + * + * @param location The file/location where this task was defined. Should not be null--use + * Location.UNKNOWN_LOCATION if the location isn't known. + * + * @see Location#UNKNOWN_LOCATION + */ + public void setLocation(Location location) { + this.location = location; + } + + /** + * Sets a description of the current action. This may be used for logging purposes. + * + * @param desc Description of the current action. May be null, indicating that no description is + * available. + * + */ + public void setDescription(String desc) { + description = desc; + } + + /** + * Returns the description of the current action. + * + * @return the description of the current action, or null if no description is available. + */ + public String getDescription() { + return description; + } + + /** + * Logs a message with the default (INFO) priority. + * + * @param msg The message to be logged. Should not be null. + */ + public void log(String msg) { +// log(msg, Project.MSG_INFO); + } + + /** + * Logs a message with the given priority. + * + * @param msg The message to be logged. Should not be null. + * @param msgLevel the message priority at which this message is to be logged. + */ + public void log(String msg, int msgLevel) { +// if (getProject() != null) { +// getProject().log(msg, msgLevel); +// } else { +// // 'reasonable' default, if the component is used without +// // a Project ( for example as a standalone Bean ). +// // Most ant components can be used this way. +// if (msgLevel <= Project.MSG_INFO) { +// System.err.println(msg); +// } +// } + } + + /** + * @since Ant 1.7 + * @return a shallow copy of this projectcomponent. + * @throws CloneNotSupportedException does not happen, but is declared to allow subclasses to do so. + */ + public Object clone() throws CloneNotSupportedException { + ProjectComponent pc = (ProjectComponent) super.clone(); + pc.setLocation(getLocation()); + //pc.setProject(getProject()); + return pc; + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/launch/Locator.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/launch/Locator.java new file mode 100644 index 000000000..dca690640 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/launch/Locator.java @@ -0,0 +1,530 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.launch; + +import java.net.MalformedURLException; +import java.net.URL; +import java.io.File; +import java.io.FilenameFilter; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.Locale; + +import org.owasp.dependencycheck.org.apache.tools.ant.util.FileUtils; + +// CheckStyle:LineLengthCheck OFF - urls are long! +/** + * The Locator is a utility class which is used to find certain items + * in the environment. + * + * It is used at boot time in the launcher, and cannot make use of any of Ant's other classes. + * + * This is a surprisingly brittle piece of code, and has had lots of bugs filed against it. + * {@link running ant off a network share can cause Ant to fail} + * {@link use File.toURI().toURL().toExternalForm()} + * {@link Locator implementation not encoding URI strings properly: spaces in paths} + * It also breaks Eclipse 3.3 Betas + * {@link Exception if installation path has spaces} + * + * Be very careful when making changes to this class, as a break will upset a lot of people. + * @since Ant 1.6 + */ +// CheckStyle:LineLengthCheck ON - urls are long! +public final class Locator { + + private static final int NIBBLE = 4; + private static final int NIBBLE_MASK = 0xF; + + private static final int ASCII_SIZE = 128; + + private static final int BYTE_SIZE = 256; + + private static final int WORD = 16; + + private static final int SPACE = 0x20; + private static final int DEL = 0x7F; + + /** + * encoding used to represent URIs + */ + public static final String URI_ENCODING = "UTF-8"; + // stolen from org.apache.xerces.impl.XMLEntityManager#getUserDir() + // of the Xerces-J team + // which ASCII characters need to be escaped + private static boolean[] gNeedEscaping = new boolean[ASCII_SIZE]; + // the first hex character if a character needs to be escaped + private static char[] gAfterEscaping1 = new char[ASCII_SIZE]; + // the second hex character if a character needs to be escaped + private static char[] gAfterEscaping2 = new char[ASCII_SIZE]; + private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + /** Error string used when an invalid uri is seen */ + public static final String ERROR_NOT_FILE_URI + = "Can only handle valid file: URIs, not "; + + // initialize the above 3 arrays + static { + for (int i = 0; i < SPACE; i++) { + gNeedEscaping[i] = true; + gAfterEscaping1[i] = gHexChs[i >> NIBBLE]; + gAfterEscaping2[i] = gHexChs[i & NIBBLE_MASK]; + } + gNeedEscaping[DEL] = true; + gAfterEscaping1[DEL] = '7'; + gAfterEscaping2[DEL] = 'F'; + char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}', + '|', '\\', '^', '~', '[', ']', '`'}; + int len = escChs.length; + char ch; + for (int i = 0; i < len; i++) { + ch = escChs[i]; + gNeedEscaping[ch] = true; + gAfterEscaping1[ch] = gHexChs[ch >> NIBBLE]; + gAfterEscaping2[ch] = gHexChs[ch & NIBBLE_MASK]; + } + } + /** + * Not instantiable + */ + private Locator() { + } + + /** + * Find the directory or jar file the class has been loaded from. + * + * @param c the class whose location is required. + * @return the file or jar with the class or null if we cannot + * determine the location. + * + * @since Ant 1.6 + */ + public static File getClassSource(Class c) { + String classResource = c.getName().replace('.', '/') + ".class"; + return getResourceSource(c.getClassLoader(), classResource); + } + + /** + * Find the directory or jar a given resource has been loaded from. + * + * @param c the classloader to be consulted for the source. + * @param resource the resource whose location is required. + * + * @return the file with the resource source or null if + * we cannot determine the location. + * + * @since Ant 1.6 + */ + public static File getResourceSource(ClassLoader c, String resource) { + if (c == null) { + c = Locator.class.getClassLoader(); + } + URL url = null; + if (c == null) { + url = ClassLoader.getSystemResource(resource); + } else { + url = c.getResource(resource); + } + if (url != null) { + String u = url.toString(); + try { + if (u.startsWith("jar:file:")) { + return new File(fromJarURI(u)); + } else if (u.startsWith("file:")) { + int tail = u.indexOf(resource); + String dirName = u.substring(0, tail); + return new File(fromURI(dirName)); + } + } catch (IllegalArgumentException e) { + //unable to determine the URI for reasons unknown. + return null; + } + } + return null; + } + + + + /** + * Constructs a file path from a file: URI. + * + *

    Will be an absolute path if the given URI is absolute.

    + * + *

    Prior to Java 1.4, + * swallows '%' that are not followed by two characters.

    + * + * See dt-sysid + * which makes some mention of how + * characters not supported by URI Reference syntax should be escaped. + * + * @param uri the URI designating a file in the local filesystem. + * @return the local file system path for the file. + * @throws IllegalArgumentException if the URI is malformed or not a legal file: URL + * @since Ant 1.6 + */ + public static String fromURI(String uri) { + return fromURIJava13(uri); + // #buzilla8031: first try Java 1.4. + // TODO should use java.net.URI now that we can rely on 1.4... + // but check for UNC-related regressions, e.g. #42275 + // (and remember that \\server\share\file -> file:////server/share/file + // rather than -> file://server/share/file as it should; + // fixed only in JDK 7's java.nio.file.Path.toUri) + // return fromUriJava14(uri); + } + + /** + * Java1.4+ code to extract the path from the URI. + * @param uri + * @return null if a conversion was not possible + */ + /* currently unused: + private static String fromUriJava14(String uri) { + // Also check for properly formed URIs. Ant formerly recommended using + // nonsense URIs such as "file:./foo.xml" in XML includes. You shouldn't + // do that (just "foo.xml" is correct) but for compatibility we special-case + // things when the path is not absolute, and fall back to the old parsing behavior. + if (uri.startsWith("file:/")) { + try { + File f = new File(URI.create(encodeURI(uri))); + //bug #42227 forgot to decode before returning + return decodeUri(f.getAbsolutePath()); + } catch (IllegalArgumentException e) { + // Bad URI, pass this on. + // no, this is downgraded to a warning after various + // JRE bugs surfaced. Hand off + // to our built in code on a failure + //throw new IllegalArgumentException( + // "Bad URI " + uri + ":" + e.getMessage(), e); + e.printStackTrace(); + } catch (Exception e) { + // Unexpected exception? Should not happen. + e.printStackTrace(); + } + } + return null; + } + */ + + /** + * @param uri uri to expand + * @return the decoded URI + * @since Ant1.7.1 + */ + private static String fromURIJava13(String uri) { + // Fallback method for Java 1.3 or earlier. + + URL url = null; + try { + url = new URL(uri); + } catch (MalformedURLException emYouEarlEx) { + // Ignore malformed exception + } + if (url == null || !("file".equals(url.getProtocol()))) { + throw new IllegalArgumentException(ERROR_NOT_FILE_URI + uri); + } + StringBuffer buf = new StringBuffer(url.getHost()); + if (buf.length() > 0) { + buf.insert(0, File.separatorChar).insert(0, File.separatorChar); + } + String file = url.getFile(); + int queryPos = file.indexOf('?'); + buf.append((queryPos < 0) ? file : file.substring(0, queryPos)); + + uri = buf.toString().replace('/', File.separatorChar); + + if (File.pathSeparatorChar == ';' && uri.startsWith("\\") && uri.length() > 2 + && Character.isLetter(uri.charAt(1)) && uri.lastIndexOf(':') > -1) { + uri = uri.substring(1); + } + String path = null; + try { + path = decodeUri(uri); + //consider adding the current directory. This is not done when + //the path is a UNC name + String cwd = System.getProperty("user.dir"); + int posi = cwd.indexOf(':'); + boolean pathStartsWithFileSeparator = path.startsWith(File.separator); + boolean pathStartsWithUNC = path.startsWith("" + File.separator + File.separator); + if ((posi > 0) && pathStartsWithFileSeparator && !pathStartsWithUNC) { + path = cwd.substring(0, posi + 1) + path; + } + } catch (UnsupportedEncodingException exc) { + // not sure whether this is clean, but this method is + // declared not to throw exceptions. + throw new IllegalStateException( + "Could not convert URI " + uri + " to path: " + + exc.getMessage()); + } + return path; + } + + /** + * Crack a JAR URI. + * This method is public for testing; we may delete it without any warning -it is not part of Ant's stable API. + * @param uri uri to expand; contains jar: somewhere in it + * @return the decoded URI + * @since Ant1.7.1 + */ + public static String fromJarURI(String uri) { + int pling = uri.indexOf("!/"); + String jarName = uri.substring("jar:".length(), pling); + return fromURI(jarName); + } + + /** + * Decodes an Uri with % characters. + * The URI is escaped + * @param uri String with the uri possibly containing % characters. + * @return The decoded Uri + * @throws UnsupportedEncodingException if UTF-8 is not available + * @since Ant 1.7 + */ + public static String decodeUri(String uri) throws UnsupportedEncodingException { + if (uri.indexOf('%') == -1) { + return uri; + } + ByteArrayOutputStream sb = new ByteArrayOutputStream(uri.length()); + CharacterIterator iter = new StringCharacterIterator(uri); + for (char c = iter.first(); c != CharacterIterator.DONE; + c = iter.next()) { + if (c == '%') { + char c1 = iter.next(); + if (c1 != CharacterIterator.DONE) { + int i1 = Character.digit(c1, WORD); + char c2 = iter.next(); + if (c2 != CharacterIterator.DONE) { + int i2 = Character.digit(c2, WORD); + sb.write((char) ((i1 << NIBBLE) + i2)); + } + } + } else if (c >= 0x0000 && c < 0x0080) { + sb.write(c); + } else { // #50543 + byte[] bytes = String.valueOf(c).getBytes(URI_ENCODING); + sb.write(bytes, 0, bytes.length); + } + } + return sb.toString(URI_ENCODING); + } + + /** + * Encodes an Uri with % characters. + * The URI is escaped + * @param path String to encode. + * @return The encoded string, according to URI norms + * @throws UnsupportedEncodingException if UTF-8 is not available + * @since Ant 1.7 + */ + public static String encodeURI(String path) throws UnsupportedEncodingException { + int i = 0; + int len = path.length(); + int ch = 0; + StringBuffer sb = null; + for (; i < len; i++) { + ch = path.charAt(i); + // if it's not an ASCII character, break here, and use UTF-8 encoding + if (ch >= ASCII_SIZE) { + break; + } + if (gNeedEscaping[ch]) { + if (sb == null) { + sb = new StringBuffer(path.substring(0, i)); + } + sb.append('%'); + sb.append(gAfterEscaping1[ch]); + sb.append(gAfterEscaping2[ch]); + // record the fact that it's escaped + } else if (sb != null) { + sb.append((char) ch); + } + } + + // we saw some non-ascii character + if (i < len) { + if (sb == null) { + sb = new StringBuffer(path.substring(0, i)); + } + // get UTF-8 bytes for the remaining sub-string + byte[] bytes = null; + byte b; + bytes = path.substring(i).getBytes(URI_ENCODING); + len = bytes.length; + + // for each byte + for (i = 0; i < len; i++) { + b = bytes[i]; + // for non-ascii character: make it positive, then escape + if (b < 0) { + ch = b + BYTE_SIZE; + sb.append('%'); + sb.append(gHexChs[ch >> NIBBLE]); + sb.append(gHexChs[ch & NIBBLE_MASK]); + } else if (gNeedEscaping[b]) { + sb.append('%'); + sb.append(gAfterEscaping1[b]); + sb.append(gAfterEscaping2[b]); + } else { + sb.append((char) b); + } + } + } + return sb == null ? path : sb.toString(); + } + + /** + * Convert a File to a URL. + * File.toURL() does not encode characters like #. + * File.toURI() has been introduced in java 1.4, so + * Ant cannot use it (except by reflection) + * FileUtils.toURI() cannot be used by Locator.java + * Implemented this way. + * File.toURL() adds file: and changes '\' to '/' for dos OSes + * encodeURI converts characters like ' ' and '#' to %DD + * @param file the file to convert + * @return URL the converted File + * @throws MalformedURLException on error + * @deprecated since 1.9, use {@link FileUtils#getFileURL(File)} + */ + @Deprecated + public static URL fileToURL(File file) + throws MalformedURLException { + return new URL(file.toURI().toASCIIString()); + } + + /** + * Get the File necessary to load the Sun compiler tools. If the classes + * are available to this class, then no additional URL is required and + * null is returned. This may be because the classes are explicitly in the + * class path or provided by the JVM directly. + * + * @return the tools jar as a File if required, null otherwise. + */ + public static File getToolsJar() { + // firstly check if the tools jar is already in the classpath + boolean toolsJarAvailable = false; + try { + // just check whether this throws an exception + Class.forName("com.sun.tools.javac.Main"); + toolsJarAvailable = true; + } catch (Exception e) { + try { + Class.forName("sun.tools.javac.Main"); + toolsJarAvailable = true; + } catch (Exception e2) { + // ignore + } + } + if (toolsJarAvailable) { + return null; + } + // couldn't find compiler - try to find tools.jar + // based on java.home setting + String libToolsJar + = File.separator + "lib" + File.separator + "tools.jar"; + String javaHome = System.getProperty("java.home"); + File toolsJar = new File(javaHome + libToolsJar); + if (toolsJar.exists()) { + // Found in java.home as given + return toolsJar; + } + if (javaHome.toLowerCase(Locale.ENGLISH).endsWith(File.separator + "jre")) { + javaHome = javaHome.substring( + 0, javaHome.length() - "/jre".length()); + toolsJar = new File(javaHome + libToolsJar); + } + if (!toolsJar.exists()) { + System.out.println("Unable to locate tools.jar. " + + "Expected to find it in " + toolsJar.getPath()); + return null; + } + return toolsJar; + } + + /** + * Get an array of URLs representing all of the jar files in the + * given location. If the location is a file, it is returned as the only + * element of the array. If the location is a directory, it is scanned for + * jar files. + * + * @param location the location to scan for Jars. + * + * @return an array of URLs for all jars in the given location. + * + * @exception MalformedURLException if the URLs for the jars cannot be + * formed. + */ + public static URL[] getLocationURLs(File location) + throws MalformedURLException { + return getLocationURLs(location, new String[]{".jar"}); + } + + /** + * Get an array of URLs representing all of the files of a given set of + * extensions in the given location. If the location is a file, it is + * returned as the only element of the array. If the location is a + * directory, it is scanned for matching files. + * + * @param location the location to scan for files. + * @param extensions an array of extension that are to match in the + * directory search. + * + * @return an array of URLs of matching files. + * @exception MalformedURLException if the URLs for the files cannot be + * formed. + */ + public static URL[] getLocationURLs(File location, + final String[] extensions) + throws MalformedURLException { + URL[] urls = new URL[0]; + + if (!location.exists()) { + return urls; + } + if (!location.isDirectory()) { + urls = new URL[1]; + String path = location.getPath(); + String littlePath = path.toLowerCase(Locale.ENGLISH); + for (int i = 0; i < extensions.length; ++i) { + if (littlePath.endsWith(extensions[i])) { + urls[0] = fileToURL(location); + break; + } + } + return urls; + } + File[] matches = location.listFiles( + new FilenameFilter() { + public boolean accept(File dir, String name) { + String littleName = name.toLowerCase(Locale.ENGLISH); + for (int i = 0; i < extensions.length; ++i) { + if (littleName.endsWith(extensions[i])) { + return true; + } + } + return false; + } + }); + urls = new URL[matches.length]; + for (int i = 0; i < matches.length; ++i) { + urls[i] = fileToURL(matches[i]); + } + return urls; + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/launch/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/launch/package-info.java new file mode 100644 index 000000000..115f3b29e --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/launch/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant.launch + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.launch; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/package-info.java new file mode 100644 index 000000000..cb3c12962 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/Condition.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/Condition.java new file mode 100644 index 000000000..d6e0351d3 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/Condition.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition; + +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; + +/** + * Interface for conditions to use inside the <condition> task. + * + */ +public interface Condition { + /** + * Is this condition true? + * @return true if the condition is true + * @exception BuildException if an error occurs + */ + boolean eval() throws BuildException; +} + diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/Os.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/Os.java new file mode 100644 index 000000000..4ea20554e --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/Os.java @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition; + +import java.util.Locale; + +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; + +/** + * Condition that tests the OS type. + * + * @since Ant 1.4 + */ +public class Os implements Condition { + private static final String OS_NAME = + System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + private static final String OS_ARCH = + System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); + private static final String OS_VERSION = + System.getProperty("os.version").toLowerCase(Locale.ENGLISH); + private static final String PATH_SEP = + System.getProperty("path.separator"); + + /** + * OS family to look for + */ + private String family; + /** + * Name of OS + */ + private String name; + /** + * version of OS + */ + private String version; + /** + * OS architecture + */ + private String arch; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_WINDOWS = "windows"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_9X = "win9x"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_NT = "winnt"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_OS2 = "os/2"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_NETWARE = "netware"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_DOS = "dos"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_MAC = "mac"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_TANDEM = "tandem"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_UNIX = "unix"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_VMS = "openvms"; + /** + * OS family that can be tested for. {@value} + */ + public static final String FAMILY_ZOS = "z/os"; + /** OS family that can be tested for. {@value} */ + public static final String FAMILY_OS400 = "os/400"; + + /** + * OpenJDK is reported to call MacOS X "Darwin" + * @see https://issues.apache.org/bugzilla/show_bug.cgi?id=44889 + * @see https://issues.apache.org/jira/browse/HADOOP-3318 + */ + private static final String DARWIN = "darwin"; + + /** + * Default constructor + * + */ + public Os() { + //default + } + + /** + * Constructor that sets the family attribute + * @param family a String value + */ + public Os(String family) { + setFamily(family); + } + + /** + * Sets the desired OS family type + * + * @param f The OS family type desired
    + * Possible values:
    + *
      + *
    • dos
    • + *
    • mac
    • + *
    • netware
    • + *
    • os/2
    • + *
    • tandem
    • + *
    • unix
    • + *
    • windows
    • + *
    • win9x
    • + *
    • z/os
    • + *
    • os/400
    • + *
    + */ + public void setFamily(String f) { + family = f.toLowerCase(Locale.ENGLISH); + } + + /** + * Sets the desired OS name + * + * @param name The OS name + */ + public void setName(String name) { + this.name = name.toLowerCase(Locale.ENGLISH); + } + + /** + * Sets the desired OS architecture + * + * @param arch The OS architecture + */ + public void setArch(String arch) { + this.arch = arch.toLowerCase(Locale.ENGLISH); + } + + /** + * Sets the desired OS version + * + * @param version The OS version + */ + public void setVersion(String version) { + this.version = version.toLowerCase(Locale.ENGLISH); + } + + /** + * Determines if the OS on which Ant is executing matches the type of + * that set in setFamily. + * @return true if the os matches. + * @throws BuildException if there is an error. + * @see Os#setFamily(String) + */ + public boolean eval() throws BuildException { + return isOs(family, name, arch, version); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS family. + * @param family the family to check for + * @return true if the OS matches + * @since 1.5 + */ + public static boolean isFamily(String family) { + return isOs(family, null, null, null); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS name. + * + * @param name the OS name to check for + * @return true if the OS matches + * @since 1.7 + */ + public static boolean isName(String name) { + return isOs(null, name, null, null); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS architecture. + * + * @param arch the OS architecture to check for + * @return true if the OS matches + * @since 1.7 + */ + public static boolean isArch(String arch) { + return isOs(null, null, arch, null); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS version. + * + * @param version the OS version to check for + * @return true if the OS matches + * @since 1.7 + */ + public static boolean isVersion(String version) { + return isOs(null, null, null, version); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS family, name, architecture and version + * + * @param family The OS family + * @param name The OS name + * @param arch The OS architecture + * @param version The OS version + * @return true if the OS matches + * @since 1.7 + */ + public static boolean isOs(String family, String name, String arch, + String version) { + boolean retValue = false; + + if (family != null || name != null || arch != null + || version != null) { + + boolean isFamily = true; + boolean isName = true; + boolean isArch = true; + boolean isVersion = true; + + if (family != null) { + + //windows probing logic relies on the word 'windows' in + //the OS + boolean isWindows = OS_NAME.indexOf(FAMILY_WINDOWS) > -1; + boolean is9x = false; + boolean isNT = false; + if (isWindows) { + //there are only four 9x platforms that we look for + is9x = (OS_NAME.indexOf("95") >= 0 + || OS_NAME.indexOf("98") >= 0 + || OS_NAME.indexOf("me") >= 0 + //wince isn't really 9x, but crippled enough to + //be a muchness. Ant doesnt run on CE, anyway. + || OS_NAME.indexOf("ce") >= 0); + isNT = !is9x; + } + if (family.equals(FAMILY_WINDOWS)) { + isFamily = isWindows; + } else if (family.equals(FAMILY_9X)) { + isFamily = isWindows && is9x; + } else if (family.equals(FAMILY_NT)) { + isFamily = isWindows && isNT; + } else if (family.equals(FAMILY_OS2)) { + isFamily = OS_NAME.indexOf(FAMILY_OS2) > -1; + } else if (family.equals(FAMILY_NETWARE)) { + isFamily = OS_NAME.indexOf(FAMILY_NETWARE) > -1; + } else if (family.equals(FAMILY_DOS)) { + isFamily = PATH_SEP.equals(";") && !isFamily(FAMILY_NETWARE); + } else if (family.equals(FAMILY_MAC)) { + isFamily = OS_NAME.indexOf(FAMILY_MAC) > -1 + || OS_NAME.indexOf(DARWIN) > -1; + } else if (family.equals(FAMILY_TANDEM)) { + isFamily = OS_NAME.indexOf("nonstop_kernel") > -1; + } else if (family.equals(FAMILY_UNIX)) { + isFamily = PATH_SEP.equals(":") + && !isFamily(FAMILY_VMS) + && (!isFamily(FAMILY_MAC) || OS_NAME.endsWith("x") + || OS_NAME.indexOf(DARWIN) > -1); + } else if (family.equals(FAMILY_ZOS)) { + isFamily = OS_NAME.indexOf(FAMILY_ZOS) > -1 + || OS_NAME.indexOf("os/390") > -1; + } else if (family.equals(FAMILY_OS400)) { + isFamily = OS_NAME.indexOf(FAMILY_OS400) > -1; + } else if (family.equals(FAMILY_VMS)) { + isFamily = OS_NAME.indexOf(FAMILY_VMS) > -1; + } else { + throw new BuildException( + "Don\'t know how to detect os family \"" + + family + "\""); + } + } + if (name != null) { + isName = name.equals(OS_NAME); + } + if (arch != null) { + isArch = arch.equals(OS_ARCH); + } + if (version != null) { + isVersion = version.equals(OS_VERSION); + } + retValue = isFamily && isName && isArch && isVersion; + } + return retValue; + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/package-info.java new file mode 100644 index 000000000..58f73450b --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/taskdefs/condition/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/DataType.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/DataType.java new file mode 100644 index 000000000..d97367c90 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/DataType.java @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types; + +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; +import org.owasp.dependencycheck.org.apache.tools.ant.ProjectComponent; + +/** + * Base class for those classes that can appear inside the build file as stand alone data types. + * + *

    + * This class handles the common description attribute and provides a default implementation for reference handling and + * checking for circular references that is appropriate for types that can not be nested inside elements of the same + * type (i.e. <patternset> but not <path>).

    + * + */ +public abstract class DataType extends ProjectComponent implements Cloneable { + // CheckStyle:VisibilityModifier OFF + + /** + * Value to the refid attribute. + * + * @deprecated since 1.7. The user should not be directly referencing variable. Please use {@link #getRefid} + * instead. + */ + protected Reference ref; + + /** + * Are we sure we don't hold circular references? + * + *

    + * Subclasses are responsible for setting this value to false if we'd need to investigate this condition (usually + * because a child element has been added that is a subclass of DataType).

    + * + * @deprecated since 1.7. The user should not be directly referencing variable. Please use {@link #setChecked} or + * {@link #isChecked} instead. + */ + protected boolean checked = true; + // CheckStyle:VisibilityModifier ON + + /** + * Has the refid attribute of this element been set? + * + * @return true if the refid attribute has been set + */ + public boolean isReference() { + return ref != null; + } + + /** + * Set the value of the refid attribute. + * + *

    + * Subclasses may need to check whether any other attributes have been set as well or child elements have been + * created and thus override this method. if they do the must call super.setRefid.

    + * + * @param ref the reference to use + */ + public void setRefid(final Reference ref) { + this.ref = ref; + checked = false; + } + +// /** +// * Gets as descriptive as possible a name used for this datatype instance. +// * +// * @return String name. +// */ +// protected String getDataTypeName() { +// return ComponentHelper.getElementName(getProject(), this, true); +// } +// /** +// * Convenience method. +// * @since Ant 1.7 +// */ +// protected void dieOnCircularReference() { +// dieOnCircularReference(getProject()); +// } +// +// /** +// * Convenience method. +// * @param p the Ant Project instance against which to resolve references. +// * @since Ant 1.7 +// */ +// protected void dieOnCircularReference(Project p) { +// if (checked || !isReference()) { +// return; +// } +// dieOnCircularReference(new IdentityStack(this), p); +// } +// +// /** +// * Check to see whether any DataType we hold references to is +// * included in the Stack (which holds all DataType instances that +// * directly or indirectly reference this instance, including this +// * instance itself). +// * +// *

    If one is included, throw a BuildException created by {@link +// * #circularReference circularReference}.

    +// * +// *

    This implementation is appropriate only for a DataType that +// * cannot hold other DataTypes as children.

    +// * +// *

    The general contract of this method is that it shouldn't do +// * anything if {@link #checked checked} is true and +// * set it to true on exit.

    +// * @param stack the stack of references to check. +// * @param project the project to use to dereference the references. +// * @throws BuildException on error. +// */ +// protected void dieOnCircularReference(final Stack stack, +// final Project project) +// throws BuildException { +// +// if (checked || !isReference()) { +// return; +// } +// Object o = ref.getReferencedObject(project); +// +// if (o instanceof DataType) { +// IdentityStack id = IdentityStack.getInstance(stack); +// +// if (id.contains(o)) { +// throw circularReference(); +// } else { +// id.push(o); +// ((DataType) o).dieOnCircularReference(id, project); +// id.pop(); +// } +// } +// checked = true; +// } +// /** +// * Allow DataTypes outside org.apache.tools.ant.types to indirectly call dieOnCircularReference on nested DataTypes. +// * +// * @param dt the DataType to check. +// * @param stk the stack of references to check. +// * @param p the project to use to dereference the references. +// * @throws BuildException on error. +// * @since Ant 1.7 +// */ +// public static void invokeCircularReferenceCheck(DataType dt, Stack stk, +// Project p) { +// dt.dieOnCircularReference(stk, p); +// } +// +// /** +// * Allow DataTypes outside org.apache.tools.ant.types to indirectly call dieOnCircularReference on nested DataTypes. +// * +// *

    +// * Pushes dt on the stack, runs dieOnCircularReference and pops it again.

    +// * +// * @param dt the DataType to check. +// * @param stk the stack of references to check. +// * @param p the project to use to dereference the references. +// * @throws BuildException on error. +// * @since Ant 1.8.0 +// */ +// public static void pushAndInvokeCircularReferenceCheck(DataType dt, +// Stack stk, +// Project p) { +// stk.push(dt); +// dt.dieOnCircularReference(stk, p); +// stk.pop(); +// } +// /** +// * Performs the check for circular references and returns the referenced object. +// * +// * @return the dereferenced object. +// * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). +// * @since Ant 1.7 +// */ +// protected Object getCheckedRef() { +// return getCheckedRef(getProject()); +// } +// +// /** +// * Performs the check for circular references and returns the referenced object. +// * +// * @param p the Ant Project instance against which to resolve references. +// * @return the dereferenced object. +// * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). +// * @since Ant 1.7 +// */ +// protected Object getCheckedRef(Project p) { +// return getCheckedRef(getClass(), getDataTypeName(), p); +// } +// +// /** +// * Performs the check for circular references and returns the referenced object. +// * +// * @param requiredClass the class that this reference should be a subclass of. +// * @param dataTypeName the name of the datatype that the reference should be (error message use only). +// * @return the dereferenced object. +// * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). +// */ +// protected T getCheckedRef(final Class requiredClass, +// final String dataTypeName) { +// return getCheckedRef(requiredClass, dataTypeName, getProject()); +// } +// +// /** +// * Performs the check for circular references and returns the referenced object. This version allows the fallback +// * Project instance to be specified. +// * +// * @param requiredClass the class that this reference should be a subclass of. +// * @param dataTypeName the name of the datatype that the reference should be (error message use only). +// * @param project the fallback Project instance for dereferencing. +// * @return the dereferenced object. +// * @throws BuildException if the reference is invalid (circular ref, wrong class, etc), or if project +// * is null. +// * @since Ant 1.7 +// */ +// protected T getCheckedRef(final Class requiredClass, +// final String dataTypeName, final Project project) { +// if (project == null) { +// throw new BuildException("No Project specified"); +// } +// dieOnCircularReference(project); +// Object o = ref.getReferencedObject(project); +// if (!(requiredClass.isAssignableFrom(o.getClass()))) { +// log("Class " + o.getClass() + " is not a subclass of " + requiredClass, +// Project.MSG_VERBOSE); +// String msg = ref.getRefId() + " doesn\'t denote a " + dataTypeName; +// throw new BuildException(msg); +// } +// @SuppressWarnings("unchecked") +// final T result = (T) o; +// return result; +// } + /** + * Creates an exception that indicates that refid has to be the only attribute if it is set. + * + * @return the exception to throw + */ + protected BuildException tooManyAttributes() { + return new BuildException("You must not specify more than one " + + "attribute when using refid"); + } + + /** + * Creates an exception that indicates that this XML element must not have child elements if the refid attribute is + * set. + * + * @return the exception to throw + */ + protected BuildException noChildrenAllowed() { + return new BuildException("You must not specify nested elements " + + "when using refid"); + } + + /** + * Creates an exception that indicates the user has generated a loop of data types referencing each other. + * + * @return the exception to throw + */ + protected BuildException circularReference() { + return new BuildException("This data type contains a circular " + + "reference."); + } + + /** + * The flag that is used to indicate that circular references have been checked. + * + * @return true if circular references have been checked + */ + protected boolean isChecked() { + return checked; + } + + /** + * Set the flag that is used to indicate that circular references have been checked. + * + * @param checked if true, if circular references have been checked + */ + protected void setChecked(final boolean checked) { + this.checked = checked; + } + + /** + * get the reference set on this object + * + * @return the reference or null + */ + public Reference getRefid() { + return ref; + } + + /** + * check that it is ok to set attributes, i.e that no reference is defined + * + * @since Ant 1.6 + * @throws BuildException if not allowed + */ + protected void checkAttributesAllowed() { + if (isReference()) { + throw tooManyAttributes(); + } + } + + /** + * check that it is ok to add children, i.e that no reference is defined + * + * @since Ant 1.6 + * @throws BuildException if not allowed + */ + protected void checkChildrenAllowed() { + if (isReference()) { + throw noChildrenAllowed(); + } + } + + /** + * Basic DataType toString(). + * + * @return this DataType formatted as a String. + */ + public String toString() { + String d = getDescription(); + //return d == null ? getDataTypeName() : getDataTypeName() + " " + d; + return d == null ? "DataType" : d; + } + + /** + * @since Ant 1.7 + * @return a shallow copy of this DataType. + * @throws CloneNotSupportedException if there is a problem. + */ + public Object clone() throws CloneNotSupportedException { + DataType dt = (DataType) super.clone(); + dt.setDescription(getDescription()); + if (getRefid() != null) { + dt.setRefid(getRefid()); + } + dt.setChecked(isChecked()); + return dt; + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/Reference.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/Reference.java new file mode 100644 index 000000000..1150a38f5 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/Reference.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types; + +/** + * Class to hold a reference to another object in the project. + * + */ +public class Reference { + + private String refid; + //private Project project; + + /** + * Create a reference. + * + * @deprecated since 1.7. Please use {@link Reference#Reference(Project,String)} instead. + */ + public Reference() { + } + + /** + * Create a reference to a named ID. + * + * @param id the name of this reference + * @deprecated since 1.7. Please use {@link Reference#Reference(Project,String)} instead. + */ + public Reference(String id) { + setRefId(id); + } +// +// /** +// * Create a reference to a named ID in a particular project. +// * @param p the project this reference is associated with +// * @param id the name of this reference +// * @since Ant 1.6.3 +// */ +// public Reference(Project p, String id) { +// setRefId(id); +// setProject(p); +// } + + /** + * Set the reference id. Should not normally be necessary; use {@link Reference#Reference(Project, String)}. + * + * @param id the reference id to use + */ + public void setRefId(String id) { + refid = id; + } + + /** + * Get the reference id of this reference. + * + * @return the reference id + */ + public String getRefId() { + return refid; + } + +// /** +// * Set the associated project. Should not normally be necessary; +// * use {@link Reference#Reference(Project,String)}. +// * @param p the project to use +// * @since Ant 1.6.3 +// */ +// public void setProject(Project p) { +// this.project = p; +// } +// +// /** +// * Get the associated project, if any; may be null. +// * @return the associated project +// * @since Ant 1.6.3 +// */ +// public Project getProject() { +// return project; +// } +// /** +// * Resolve the reference, using the associated project if +// * it set, otherwise use the passed in project. +// * @param fallback the fallback project to use if the project attribute of +// * reference is not set. +// * @return the dereferenced object. +// * @throws BuildException if the reference cannot be dereferenced. +// */ +// public Object getReferencedObject(Project fallback) throws BuildException { +// if (refid == null) { +// throw new BuildException("No reference specified"); +// } +// +// Object o = project == null ? fallback.getReference(refid) : project.getReference(refid); +// if (o == null) { +// throw new BuildException("Reference " + refid + " not found."); +// } +// return o; +// } +// /** +// * Resolve the reference, looking in the associated project. +// * @see Project#getReference +// * @return the dereferenced object. +// * @throws BuildException if the project is null or the reference cannot be dereferenced +// * @since Ant 1.6.3 +// */ +// public Object getReferencedObject() throws BuildException { +// if (project == null) { +// throw new BuildException("No project set on reference to " + refid); +// } +// return getReferencedObject(project); +// } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/Resource.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/Resource.java new file mode 100644 index 000000000..f8379619a --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/Resource.java @@ -0,0 +1,462 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.owasp.dependencycheck.org.apache.tools.ant.types.resources.FileProvider; + +/** + * Describes a "File-like" resource (File, ZipEntry, etc.). + * + * This class is meant to be used by classes needing to record path and date/time information about a file, a zip entry + * or some similar resource (URL, archive in a version control repository, ...). + * + * @since Ant 1.5.2 + * @see org.apache.tools.ant.types.resources.Touchable + */ +public class Resource extends DataType implements Comparable, ResourceCollection { + + /** + * Constant unknown size + */ + public static final long UNKNOWN_SIZE = -1; + + /** + * Constant unknown datetime for getLastModified + */ + public static final long UNKNOWN_DATETIME = 0L; + + /** + * Magic number + */ + protected static final int MAGIC = getMagicNumber("Resource".getBytes()); + + private static final int NULL_NAME = getMagicNumber("null name".getBytes()); + + /** + * Create a "magic number" for use in hashCode calculations. + * + * @param seed byte[] to seed with. + * @return a magic number as int. + */ + protected static int getMagicNumber(byte[] seed) { + return new BigInteger(seed).intValue(); + } + + private String name = null; + private Boolean exists = null; + private Long lastmodified = null; + private Boolean directory = null; + private Long size = null; + + /** + * Default constructor. + */ + public Resource() { + } + + /** + * Only sets the name. + * + *

    + * This is a dummy, used for not existing resources.

    + * + * @param name relative path of the resource. Expects "/" to be used as the directory separator. + */ + public Resource(String name) { + this(name, false, 0, false); + } + + /** + * Sets the name, lastmodified flag, and exists flag. + * + * @param name relative path of the resource. Expects "/" to be used as the directory separator. + * @param exists if true, this resource exists. + * @param lastmodified the last modification time of this resource. + */ + public Resource(String name, boolean exists, long lastmodified) { + this(name, exists, lastmodified, false); + } + + /** + * Sets the name, lastmodified flag, exists flag, and directory flag. + * + * @param name relative path of the resource. Expects "/" to be used as the directory separator. + * @param exists if true the resource exists + * @param lastmodified the last modification time of the resource + * @param directory if true, this resource is a directory + */ + public Resource(String name, boolean exists, long lastmodified, boolean directory) { + this(name, exists, lastmodified, directory, UNKNOWN_SIZE); + } + + /** + * Sets the name, lastmodified flag, exists flag, directory flag, and size. + * + * @param name relative path of the resource. Expects "/" to be used as the directory separator. + * @param exists if true the resource exists + * @param lastmodified the last modification time of the resource + * @param directory if true, this resource is a directory + * @param size the size of this resource. + */ + public Resource(String name, boolean exists, long lastmodified, boolean directory, long size) { + this.name = name; + setName(name); + setExists(exists); + setLastModified(lastmodified); + setDirectory(directory); + setSize(size); + } + + /** + * Name attribute will contain the path of a file relative to the root directory of its fileset or the recorded path + * of a zip entry. + * + *

    + * example for a file with fullpath /var/opt/adm/resource.txt in a file set with root dir /var/opt it will be + * adm/resource.txt.

    + * + *

    + * "/" will be used as the directory separator.

    + * + * @return the name of this resource. + */ + public String getName() { + //return isReference() ? ((Resource) getCheckedRef()).getName() : name; + return name; + } + + /** + * Set the name of this Resource. + * + * @param name relative path of the resource. Expects "/" to be used as the directory separator. + */ + public void setName(String name) { + checkAttributesAllowed(); + this.name = name; + } + + /** + * The exists attribute tells whether a resource exists. + * + * @return true if this resource exists. + */ + public boolean isExists() { +// if (isReference()) { +// return ((Resource) getCheckedRef()).isExists(); +// } + //default true: + return exists == null || exists.booleanValue(); + } + + /** + * Set the exists attribute. + * + * @param exists if true, this resource exists. + */ + public void setExists(boolean exists) { + checkAttributesAllowed(); + this.exists = exists ? Boolean.TRUE : Boolean.FALSE; + } + + /** + * Tells the modification time in milliseconds since 01.01.1970 (the "epoch"). + * + * @return the modification time, if that is meaningful (e.g. for a file resource which exists); 0 if the resource + * does not exist, to mirror the behavior of {@link java.io.File#lastModified}; or 0 if the notion of modification + * time is meaningless for this class of resource (e.g. an inline string) + */ + public long getLastModified() { +// if (isReference()) { +// return ((Resource) getCheckedRef()).getLastModified(); +// } + if (!isExists() || lastmodified == null) { + return UNKNOWN_DATETIME; + } + long result = lastmodified.longValue(); + return result < UNKNOWN_DATETIME ? UNKNOWN_DATETIME : result; + } + + /** + * Set the last modification attribute. + * + * @param lastmodified the modification time in milliseconds since 01.01.1970. + */ + public void setLastModified(long lastmodified) { + checkAttributesAllowed(); + this.lastmodified = new Long(lastmodified); + } + + /** + * Tells if the resource is a directory. + * + * @return boolean flag indicating if the resource is a directory. + */ + public boolean isDirectory() { +// if (isReference()) { +// return ((Resource) getCheckedRef()).isDirectory(); +// } + //default false: + return directory != null && directory.booleanValue(); + } + + /** + * Set the directory attribute. + * + * @param directory if true, this resource is a directory. + */ + public void setDirectory(boolean directory) { + checkAttributesAllowed(); + this.directory = directory ? Boolean.TRUE : Boolean.FALSE; + } + + /** + * Set the size of this Resource. + * + * @param size the size, as a long. + * @since Ant 1.6.3 + */ + public void setSize(long size) { + checkAttributesAllowed(); + this.size = new Long(size > UNKNOWN_SIZE ? size : UNKNOWN_SIZE); + } + + /** + * Get the size of this Resource. + * + * @return the size, as a long, 0 if the Resource does not exist (for compatibility with java.io.File), or + * UNKNOWN_SIZE if not known. + * @since Ant 1.6.3 + */ + public long getSize() { +// if (isReference()) { +// return ((Resource) getCheckedRef()).getSize(); +// } + return isExists() + ? (size != null ? size.longValue() : UNKNOWN_SIZE) + : 0L; + } + + /** + * Clone this Resource. + * + * @return copy of this. + */ + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new UnsupportedOperationException( + "CloneNotSupportedException for a Resource caught. " + + "Derived classes must support cloning."); + } + } + + /** + * Delegates to a comparison of names. + * + * @param other the object to compare to. + * @return a negative integer, zero, or a positive integer as this Resource is less than, equal to, or greater than + * the specified Resource. + * @since Ant 1.6 + */ + public int compareTo(Resource other) { +// if (isReference()) { +// return ((Resource) getCheckedRef()).compareTo(other); +// } + return toString().compareTo(other.toString()); + } + + /** + * Implement basic Resource equality. + * + * @param other the object to check against. + * @return true if the specified Object is equal to this Resource. + * @since Ant 1.7 + */ + public boolean equals(Object other) { +// if (isReference()) { +// return getCheckedRef().equals(other); +// } + return other != null && other.getClass().equals(getClass()) + && compareTo((Resource) other) == 0; + } + + /** + * Get the hash code for this Resource. + * + * @return hash code as int. + * @since Ant 1.7 + */ + public int hashCode() { +// if (isReference()) { +// return getCheckedRef().hashCode(); +// } + String name = getName(); + return MAGIC * (name == null ? NULL_NAME : name.hashCode()); + } + + /** + * Get an InputStream for the Resource. + * + * @return an InputStream containing this Resource's content. + * @throws IOException if unable to provide the content of this Resource as a stream. + * @throws UnsupportedOperationException if InputStreams are not supported for this Resource type. + * @since Ant 1.7 + */ + public InputStream getInputStream() throws IOException { +// if (isReference()) { +// return ((Resource) getCheckedRef()).getInputStream(); +// } + throw new UnsupportedOperationException(); + } + + /** + * Get an OutputStream for the Resource. + * + * @return an OutputStream to which content can be written. + * @throws IOException if unable to provide the content of this Resource as a stream. + * @throws UnsupportedOperationException if OutputStreams are not supported for this Resource type. + * @since Ant 1.7 + */ + public OutputStream getOutputStream() throws IOException { +// if (isReference()) { +// return ((Resource) getCheckedRef()).getOutputStream(); +// } + throw new UnsupportedOperationException(); + } + + /** + * Fulfill the ResourceCollection contract. + * + * @return an Iterator of Resources. + * @since Ant 1.7 + */ + public Iterator iterator() { + //return isReference() ? ((Resource) getCheckedRef()).iterator() + // : new Iterator() { + return new Iterator() { + private boolean done = false; + + public boolean hasNext() { + return !done; + } + + public Resource next() { + if (done) { + throw new NoSuchElementException(); + } + done = true; + return Resource.this; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Fulfill the ResourceCollection contract. + * + * @return the size of this ResourceCollection. + * @since Ant 1.7 + */ + public int size() { + //return isReference() ? ((Resource) getCheckedRef()).size() : 1; + return 1; + } + + /** + * Fulfill the ResourceCollection contract. + * + * @return whether this Resource is a FileProvider. + * @since Ant 1.7 + */ + public boolean isFilesystemOnly() { +// return (isReference() && ((Resource) getCheckedRef()).isFilesystemOnly()) +// || this.as(FileProvider.class) != null; + return this.as(FileProvider.class) != null; + } + + /** + * Get the string representation of this Resource. + * + * @return this Resource formatted as a String. + * @since Ant 1.7 + */ + public String toString() { +// if (isReference()) { +// return getCheckedRef().toString(); +// } + String n = getName(); + return n == null ? "(anonymous)" : n; + } + + /** + * Get a long String representation of this Resource. This typically should be the value of toString() + * prefixed by a type description. + * + * @return this Resource formatted as a long String. + * @since Ant 1.7 + */ + public final String toLongString() { +// return isReference() ? ((Resource) getCheckedRef()).toLongString() +// : getDataTypeName() + " \"" + toString() + '"'; + return toString(); + } + + /** + * Overrides the base version. + * + * @param r the Reference to set. + */ + public void setRefid(Reference r) { + if (name != null + || exists != null + || lastmodified != null + || directory != null + || size != null) { + throw tooManyAttributes(); + } + super.setRefid(r); + } + + /** + * Returns a view of this resource that implements the interface given as the argument or null if there is no such + * view. + * + *

    + * This allows extension interfaces to be added to resources without growing the number of permutations of + * interfaces decorators/adapters need to implement.

    + * + *

    + * This implementation of the method will return the current instance itself if it can be assigned to the given + * class.

    + * + * @since Ant 1.8.0 + */ + public T as(Class clazz) { + return clazz.isAssignableFrom(getClass()) ? clazz.cast(this) : null; + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/ResourceCollection.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/ResourceCollection.java new file mode 100644 index 000000000..3aed793fe --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/ResourceCollection.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types; + +import java.util.Iterator; +import org.owasp.dependencycheck.org.apache.tools.ant.types.resources.FileProvider; + +/** + * Interface describing a collection of Resources. + * @since Ant 1.7 + */ +public interface ResourceCollection extends Iterable { + + /** + * Gets the contents of this collection. + * @return all resources in the collection + */ + Iterator iterator(); + + /** + * Learn the number of contained Resources. + * @return number of elements as int. + */ + int size(); + + /** + * Indicate whether this ResourceCollection is composed entirely of + * Resources accessible via local filesystem conventions. If true, + * all resources returned from this collection should + * respond with a {@link FileProvider} when asked via {@link Resource#as}. + * @return whether this is a filesystem-only resource collection. + */ + boolean isFilesystemOnly(); + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/ResourceFactory.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/ResourceFactory.java new file mode 100644 index 000000000..552e2ee73 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/ResourceFactory.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types; + +/** + * this interface should be implemented by classes (Scanners) needing + * to deliver information about resources. + * + * @since Ant 1.5.2 + */ +public interface ResourceFactory { + + /** + * Query a resource (file, zipentry, ...) by name + * + * @param name relative path of the resource about which + * information is sought. Expects "/" to be used as the + * directory separator. + * @return instance of Resource; the exists attribute of Resource + * will tell whether the sought resource exists + */ + Resource getResource(String name); +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/package-info.java new file mode 100644 index 000000000..6834fbfc2 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant.types + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/Appendable.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/Appendable.java new file mode 100644 index 000000000..076912049 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/Appendable.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types.resources; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Interface to be implemented by "appendable" resources. + * @since Ant 1.8 + */ +public interface Appendable { + + /** + * Get an appending OutputStream. + * @return OutputStream + * @throws IOException if anything goes wrong + */ + OutputStream getAppendOutputStream() throws IOException; +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/FileProvider.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/FileProvider.java new file mode 100644 index 000000000..fdfac2598 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/FileProvider.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.types.resources; + +import java.io.File; + +/** + * This is an interface that resources that can provide a file should implement. + * This is a refactoring of {@link FileResource}, to allow other resources + * to act as sources of files (and to make components that only support + * file-based resources from only support FileResource resources. + * @since Ant 1.8 + */ +public interface FileProvider { + /** + * Get the file represented by this Resource. + * @return the file. + */ + File getFile(); +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/FileResource.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/FileResource.java new file mode 100644 index 000000000..430da35c4 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/FileResource.java @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types.resources; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; +import org.owasp.dependencycheck.org.apache.tools.ant.types.Reference; +import org.owasp.dependencycheck.org.apache.tools.ant.types.Resource; +import org.owasp.dependencycheck.org.apache.tools.ant.types.ResourceFactory; +import org.owasp.dependencycheck.org.apache.tools.ant.util.FileUtils; + +/** + * A Resource representation of a File. + * + * @since Ant 1.7 + */ +public class FileResource extends Resource implements Touchable, FileProvider, + ResourceFactory, Appendable { + + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + private static final int NULL_FILE + = Resource.getMagicNumber("null file".getBytes()); + + private File file; + private File baseDir; + + /** + * Default constructor. + */ + public FileResource() { + } + + /** + * Construct a new FileResource using the specified basedir and relative name. + * + * @param b the basedir as File. + * @param name the relative filename. + */ + public FileResource(File b, String name) { + this.baseDir = b; + this.file = FILE_UTILS.resolveFile(b, name); + } + + /** + * Construct a new FileResource from a File. + * + * @param f the File represented. + */ + public FileResource(File f) { + setFile(f); + } + +// /** +// * Create a new FileResource. +// * @param p Project +// * @param f File represented +// * @since Ant 1.8 +// */ +// public FileResource(Project p, File f) { +// this(f); +// setProject(p); +// } +// +// /** +// * Constructor for Ant attribute introspection. +// * @param p the Project against which to resolve s. +// * @param s the absolute or Project-relative filename as a String. +// * @see org.apache.tools.ant.IntrospectionHelper +// */ +// public FileResource(Project p, String s) { +// this(p, p.resolveFile(s)); +// } + /** + * Set the File for this FileResource. + * + * @param f the File to be represented. + */ + public void setFile(File f) { + checkAttributesAllowed(); + file = f; + if (f != null && (getBaseDir() == null || !FILE_UTILS.isLeadingPath(getBaseDir(), f))) { + setBaseDir(f.getParentFile()); + } + } + + /** + * Get the file represented by this FileResource. + * + * @return the File. + */ + public File getFile() { +// if (isReference()) { +// return ((FileResource) getCheckedRef()).getFile(); +// } +// dieOnCircularReference(); + synchronized (this) { + if (file == null) { + //try to resolve file set via basedir/name property setters: + File d = getBaseDir(); + String n = super.getName(); + if (n != null) { + setFile(FILE_UTILS.resolveFile(d, n)); + } + } + } + return file; + } + + /** + * Set the basedir for this FileResource. + * + * @param b the basedir as File. + */ + public void setBaseDir(File b) { + checkAttributesAllowed(); + baseDir = b; + } + + /** + * Return the basedir to which the name is relative. + * + * @return the basedir as File. + */ + public File getBaseDir() { +// if (isReference()) { +// return ((FileResource) getCheckedRef()).getBaseDir(); +// } +// dieOnCircularReference(); + return baseDir; + } + + /** + * Overrides the super version. + * + * @param r the Reference to set. + */ + public void setRefid(Reference r) { + if (file != null || baseDir != null) { + throw tooManyAttributes(); + } + super.setRefid(r); + } + + /** + * Get the name of this FileResource. If the basedir is set, the name will be relative to that. Otherwise the + * basename only will be returned. + * + * @return the name of this resource. + */ + public String getName() { +// if (isReference()) { +// return ((Resource) getCheckedRef()).getName(); +// } + File b = getBaseDir(); + return b == null ? getNotNullFile().getName() + : FILE_UTILS.removeLeadingPath(b, getNotNullFile()); + } + + /** + * Learn whether this file exists. + * + * @return true if this resource exists. + */ + public boolean isExists() { +// return isReference() ? ((Resource) getCheckedRef()).isExists() +// : getNotNullFile().exists(); + return getNotNullFile().exists(); + } + + /** + * Get the modification time in milliseconds since 01.01.1970 . + * + * @return 0 if the resource does not exist. + */ + public long getLastModified() { +// return isReference() +// ? ((Resource) getCheckedRef()).getLastModified() +// : getNotNullFile().lastModified(); + return getNotNullFile().lastModified(); + } + + /** + * Learn whether the resource is a directory. + * + * @return boolean flag indicating if the resource is a directory. + */ + public boolean isDirectory() { +// return isReference() ? ((Resource) getCheckedRef()).isDirectory() +// : getNotNullFile().isDirectory(); + return getNotNullFile().isDirectory(); + } + + /** + * Get the size of this Resource. + * + * @return the size, as a long, 0 if the Resource does not exist. + */ + public long getSize() { +// return isReference() ? ((Resource) getCheckedRef()).getSize() +// : getNotNullFile().length(); + return getNotNullFile().length(); + } + + /** + * Return an InputStream for reading the contents of this Resource. + * + * @return an InputStream object. + * @throws IOException if an error occurs. + */ + public InputStream getInputStream() throws IOException { +// return isReference() +// ? ((Resource) getCheckedRef()).getInputStream() +// : new FileInputStream(getNotNullFile()); + return new FileInputStream(getNotNullFile()); + } + + /** + * Get an OutputStream for the Resource. + * + * @return an OutputStream to which content can be written. + * @throws IOException if unable to provide the content of this Resource as a stream. + * @throws UnsupportedOperationException if OutputStreams are not supported for this Resource type. + */ + public OutputStream getOutputStream() throws IOException { +// if (isReference()) { +// return ((FileResource) getCheckedRef()).getOutputStream(); +// } + return getOutputStream(false); + } + + /** + * {@inheritDoc} + */ + public OutputStream getAppendOutputStream() throws IOException { +// if (isReference()) { +// return ((FileResource) getCheckedRef()).getAppendOutputStream(); +// } + return getOutputStream(true); + } + + private OutputStream getOutputStream(boolean append) throws IOException { + File f = getNotNullFile(); + if (f.exists()) { + if (f.isFile() && !append) { + f.delete(); + } + } else { + File p = f.getParentFile(); + if (p != null && !(p.exists())) { + p.mkdirs(); + } + } + return append ? new FileOutputStream(f.getAbsolutePath(), true) : new FileOutputStream(f); + } + + /** + * Compare this FileResource to another Resource. + * + * @param another the other Resource against which to compare. + * @return a negative integer, zero, or a positive integer as this FileResource is less than, equal to, or greater + * than the specified Resource. + */ + public int compareTo(Resource another) { +// if (isReference()) { +// return ((Resource) getCheckedRef()).compareTo(another); +// } + if (this.equals(another)) { + return 0; + } + FileProvider otherFP = another.as(FileProvider.class); + if (otherFP != null) { + File f = getFile(); + if (f == null) { + return -1; + } + File of = otherFP.getFile(); + if (of == null) { + return 1; + } + return f.compareTo(of); + } + return super.compareTo(another); + } + + /** + * Compare another Object to this FileResource for equality. + * + * @param another the other Object to compare. + * @return true if another is a FileResource representing the same file. + */ + public boolean equals(Object another) { + if (this == another) { + return true; + } +// if (isReference()) { +// return getCheckedRef().equals(another); +// } + if (another == null || !(another.getClass().equals(getClass()))) { + return false; + } + FileResource otherfr = (FileResource) another; + return getFile() == null + ? otherfr.getFile() == null + : getFile().equals(otherfr.getFile()); + } + + /** + * Get the hash code for this Resource. + * + * @return hash code as int. + */ + public int hashCode() { +// if (isReference()) { +// return getCheckedRef().hashCode(); +// } + return MAGIC * (getFile() == null ? NULL_FILE : getFile().hashCode()); + } + + /** + * Get the string representation of this Resource. + * + * @return this FileResource formatted as a String. + */ + public String toString() { +// if (isReference()) { +// return getCheckedRef().toString(); +// } + if (file == null) { + return "(unbound file resource)"; + } + String absolutePath = file.getAbsolutePath(); + return FILE_UTILS.normalize(absolutePath).getAbsolutePath(); + } + + /** + * Fulfill the ResourceCollection contract. + * + * @return whether this Resource is a FileResource. + */ + public boolean isFilesystemOnly() { +// if (isReference()) { +// return ((FileResource) getCheckedRef()).isFilesystemOnly(); +// } +// dieOnCircularReference(); + return true; + } + + /** + * Implement the Touchable interface. + * + * @param modTime new last modification time. + */ + public void touch(long modTime) { +// if (isReference()) { +// ((FileResource) getCheckedRef()).touch(modTime); +// return; +// } + if (!getNotNullFile().setLastModified(modTime)) { + //log("Failed to change file modification time", Project.MSG_WARN); + } + } + + /** + * Get the file represented by this FileResource, ensuring it is not null. + * + * @return the not-null File. + * @throws BuildException if file is null. + */ + protected File getNotNullFile() { + if (getFile() == null) { + throw new BuildException("file attribute is null!"); + } +// dieOnCircularReference(); + return getFile(); + } + + /** + * Create a new resource that matches a relative or absolute path. If the current instance has a compatible baseDir + * attribute, it is copied. + * + * @param path relative/absolute path to a resource + * @return a new resource of type FileResource + * @throws BuildException if desired + * @since Ant1.8 + */ + public Resource getResource(String path) { + File newfile = FILE_UTILS.resolveFile(getFile(), path); + FileResource fileResource = new FileResource(newfile); + if (FILE_UTILS.isLeadingPath(getBaseDir(), newfile)) { + fileResource.setBaseDir(getBaseDir()); + } + return fileResource; + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/Touchable.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/Touchable.java new file mode 100644 index 000000000..fdb81f9bf --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/Touchable.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types.resources; + +/** + * Interface to be implemented by "touchable" resources; + * that is, those whose modification time can be altered. + * @since Ant 1.7 + */ +public interface Touchable { + /** + * Method called to "touch" the resource. + * @param modTime the time to set the modified "field" of the resource, + * measured in milliseconds since the epoch. + */ + void touch(long modTime); +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/package-info.java new file mode 100644 index 000000000..2e1e26783 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/resources/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant.types.resources + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types.resources; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/FileSelector.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/FileSelector.java new file mode 100644 index 000000000..787e5e928 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/FileSelector.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.types.selectors; + +import java.io.File; + +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; + +/** + * This is the interface to be used by all selectors. + * + * @since 1.5 + */ +public interface FileSelector { + + /** + * Method that each selector will implement to create their + * selection behaviour. If there is a problem with the setup + * of a selector, it can throw a BuildException to indicate + * the problem. + * + * @param basedir A java.io.File object for the base directory + * @param filename The name of the file to check + * @param file A File object for this filename + * @return whether the file should be selected or not + * @exception BuildException if the selector was not configured correctly + */ + boolean isSelected(File basedir, String filename, File file) + throws BuildException; + +} + diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/SelectorScanner.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/SelectorScanner.java new file mode 100644 index 000000000..6597bfd16 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/SelectorScanner.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types.selectors; + +/** + * An interface used to describe the actions required by any type of + * directory scanner that supports Selectors. + * + * @since 1.5 + */ +public interface SelectorScanner { + /** + * Sets the selectors the scanner should use. + * + * @param selectors the list of selectors + */ + void setSelectors(FileSelector[] selectors); + + /** + * Directories which were selected out of a scan. + * + * @return list of directories not selected + */ + String[] getDeselectedDirectories(); + + /** + * Files which were selected out of a scan. + * + * @return list of files not selected + */ + String[] getDeselectedFiles(); + + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/SelectorUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/SelectorUtils.java new file mode 100644 index 000000000..6cbcd1fbf --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/SelectorUtils.java @@ -0,0 +1,695 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.types.selectors; + +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.owasp.dependencycheck.org.apache.tools.ant.types.Resource; +import org.owasp.dependencycheck.org.apache.tools.ant.util.FileUtils; + +/** + *

    This is a utility class used by selectors and DirectoryScanner. The + * functionality more properly belongs just to selectors, but unfortunately + * DirectoryScanner exposed these as protected methods. Thus we have to + * support any subclasses of DirectoryScanner that may access these methods. + *

    + *

    This is a Singleton.

    + * + * @since 1.5 + */ +public final class SelectorUtils { + + /** + * The pattern that matches an arbitrary number of directories. + * @since Ant 1.8.0 + */ + public static final String DEEP_TREE_MATCH = "**"; + + private static final SelectorUtils instance = new SelectorUtils(); + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Private Constructor + */ + private SelectorUtils() { + } + + /** + * Retrieves the instance of the Singleton. + * @return singleton instance + */ + public static SelectorUtils getInstance() { + return instance; + } + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + *

    + * This is not a general purpose test and should only be used if you + * can live with false positives. For example, pattern=**\a + * and str=b will yield true. + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + public static boolean matchPatternStart(String pattern, String str) { + return matchPatternStart(pattern, str, true); + } + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + *

    + * This is not a general purpose test and should only be used if you + * can live with false positives. For example, pattern=**\a + * and str=b will yield true. + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + public static boolean matchPatternStart(String pattern, String str, + boolean isCaseSensitive) { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if (str.startsWith(File.separator) + != pattern.startsWith(File.separator)) { + return false; + } + + String[] patDirs = tokenizePathAsArray(pattern); + String[] strDirs = tokenizePathAsArray(str); + return matchPatternStart(patDirs, strDirs, isCaseSensitive); + } + + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + *

    + * This is not a general purpose test and should only be used if you + * can live with false positives. For example, pattern=**\a + * and str=b will yield true. + * + * @param patDirs The tokenized pattern to match against. Must not be + * null. + * @param strDirs The tokenized path to match. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + static boolean matchPatternStart(String[] patDirs, String[] strDirs, + boolean isCaseSensitive) { + int patIdxStart = 0; + int patIdxEnd = patDirs.length - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.length - 1; + + // up to first '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = patDirs[patIdxStart]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { + return false; + } + patIdxStart++; + strIdxStart++; + } + + // CheckStyle:SimplifyBooleanReturnCheck OFF + // Check turned off as the code needs the comments for the various + // code paths. + if (strIdxStart > strIdxEnd) { + // String is exhausted + return true; + } else if (patIdxStart > patIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else { + // pattern now holds ** while string is not exhausted + // this will generate false positives but we can live with that. + return true; + } + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPath + * + * @see TokenizedPath + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * + * @return true if the pattern matches against the string, + * or false otherwise. + */ + public static boolean matchPath(String pattern, String str) { + String[] patDirs = tokenizePathAsArray(pattern); + return matchPath(patDirs, tokenizePathAsArray(str), true); + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPattern + * + * @see TokenizedPattern + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return true if the pattern matches against the string, + * or false otherwise. + */ + public static boolean matchPath(String pattern, String str, + boolean isCaseSensitive) { + String[] patDirs = tokenizePathAsArray(pattern); + return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive); + } + + /** + * Core implementation of matchPath. It is isolated so that it + * can be called from TokenizedPattern. + */ + static boolean matchPath(String[] tokenizedPattern, String[] strDirs, + boolean isCaseSensitive) { + int patIdxStart = 0; + int patIdxEnd = tokenizedPattern.length - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.length - 1; + + // up to first '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = tokenizedPattern[patIdxStart]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { + return false; + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + // String is exhausted + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + return true; + } else { + if (patIdxStart > patIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = tokenizedPattern[patIdxEnd]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + // String is exhausted + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + return true; + } + + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1; + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = tokenizedPattern[patIdxStart + j + 1]; + String subStr = strDirs[strIdxStart + i + j]; + if (!match(subPat, subStr, isCaseSensitive)) { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + + return true; + } + + /** + * Tests whether or not a string matches against a pattern. + * The pattern may contain two special characters:
    + * '*' means zero or more characters
    + * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be null. + * @param str The string which must be matched against the pattern. + * Must not be null. + * + * @return true if the string matches against the pattern, + * or false otherwise. + */ + public static boolean match(String pattern, String str) { + return match(pattern, str, true); + } + + /** + * Tests whether or not a string matches against a pattern. + * The pattern may contain two special characters:
    + * '*' means zero or more characters
    + * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be null. + * @param str The string which must be matched against the pattern. + * Must not be null. + * @param caseSensitive Whether or not matching should be performed + * case sensitively. + * + * + * @return true if the string matches against the pattern, + * or false otherwise. + */ + public static boolean match(String pattern, String str, + boolean caseSensitive) { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for (int i = 0; i < patArr.length; i++) { + if (patArr[i] == '*') { + containsStar = true; + break; + } + } + + if (!containsStar) { + // No '*'s, so we make a shortcut + if (patIdxEnd != strIdxEnd) { + return false; // Pattern and string do not have the same size + } + for (int i = 0; i <= patIdxEnd; i++) { + ch = patArr[i]; + if (ch != '?') { + if (different(caseSensitive, ch, strArr[i])) { + return false; // Character mismatch + } + } + } + return true; // String matches against pattern + } + + if (patIdxEnd == 0) { + return true; // Pattern contains only '*', which matches anything + } + + // Process characters before first star + while (true) { + ch = patArr[patIdxStart]; + if (ch == '*' || strIdxStart > strIdxEnd) { + break; + } + if (ch != '?') { + if (different(caseSensitive, ch, strArr[strIdxStart])) { + return false; // Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + // Process characters after last star + while (true) { + ch = patArr[patIdxEnd]; + if (ch == '*' || strIdxStart > strIdxEnd) { + break; + } + if (ch != '?') { + if (different(caseSensitive, ch, strArr[strIdxEnd])) { + return false; // Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1; + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (patArr[i] == '*') { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + ch = patArr[patIdxStart + j + 1]; + if (ch != '?') { + if (different(caseSensitive, ch, + strArr[strIdxStart + i + j])) { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + private static boolean allStars(char[] chars, int start, int end) { + for (int i = start; i <= end; ++i) { + if (chars[i] != '*') { + return false; + } + } + return true; + } + + private static boolean different( + boolean caseSensitive, char ch, char other) { + return caseSensitive + ? ch != other + : Character.toUpperCase(ch) != Character.toUpperCase(other); + } + + /** + * Breaks a path up into a Vector of path elements, tokenizing on + * File.separator. + * + * @param path Path to tokenize. Must not be null. + * + * @return a Vector of path elements from the tokenized path + */ + public static Vector tokenizePath(String path) { + return tokenizePath(path, File.separator); + } + + /** + * Breaks a path up into a Vector of path elements, tokenizing on + * + * @param path Path to tokenize. Must not be null. + * @param separator the separator against which to tokenize. + * + * @return a Vector of path elements from the tokenized path + * @since Ant 1.6 + */ + public static Vector tokenizePath(String path, String separator) { + Vector ret = new Vector(); + if (FileUtils.isAbsolutePath(path)) { + String[] s = FILE_UTILS.dissect(path); + ret.add(s[0]); + path = s[1]; + } + StringTokenizer st = new StringTokenizer(path, separator); + while (st.hasMoreTokens()) { + ret.addElement(st.nextToken()); + } + return ret; + } + + /** + * Same as {@link #tokenizePath tokenizePath} but hopefully faster. + */ + /*package*/ static String[] tokenizePathAsArray(String path) { + String root = null; + if (FileUtils.isAbsolutePath(path)) { + String[] s = FILE_UTILS.dissect(path); + root = s[0]; + path = s[1]; + } + char sep = File.separatorChar; + int start = 0; + int len = path.length(); + int count = 0; + for (int pos = 0; pos < len; pos++) { + if (path.charAt(pos) == sep) { + if (pos != start) { + count++; + } + start = pos + 1; + } + } + if (len != start) { + count++; + } + String[] l = new String[count + ((root == null) ? 0 : 1)]; + + if (root != null) { + l[0] = root; + count = 1; + } else { + count = 0; + } + start = 0; + for (int pos = 0; pos < len; pos++) { + if (path.charAt(pos) == sep) { + if (pos != start) { + String tok = path.substring(start, pos); + l[count++] = tok; + } + start = pos + 1; + } + } + if (len != start) { + String tok = path.substring(start); + l[count/*++*/] = tok; + } + return l; + } + + /** + * Returns dependency information on these two files. If src has been + * modified later than target, it returns true. If target doesn't exist, + * it likewise returns true. Otherwise, target is newer than src and + * is not out of date, thus the method returns false. It also returns + * false if the src file doesn't even exist, since how could the + * target then be out of date. + * + * @param src the original file + * @param target the file being compared against + * @param granularity the amount in seconds of slack we will give in + * determining out of dateness + * @return whether the target is out of date + */ + public static boolean isOutOfDate(File src, File target, int granularity) { + if (!src.exists()) { + return false; + } + if (!target.exists()) { + return true; + } + if ((src.lastModified() - granularity) > target.lastModified()) { + return true; + } + return false; + } + + /** + * Returns dependency information on these two resources. If src has been + * modified later than target, it returns true. If target doesn't exist, + * it likewise returns true. Otherwise, target is newer than src and + * is not out of date, thus the method returns false. It also returns + * false if the src file doesn't even exist, since how could the + * target then be out of date. + * + * @param src the original resource + * @param target the resource being compared against + * @param granularity the int amount in seconds of slack we will give in + * determining out of dateness + * @return whether the target is out of date + */ + public static boolean isOutOfDate(Resource src, Resource target, + int granularity) { + return isOutOfDate(src, target, (long) granularity); + } + + /** + * Returns dependency information on these two resources. If src has been + * modified later than target, it returns true. If target doesn't exist, + * it likewise returns true. Otherwise, target is newer than src and + * is not out of date, thus the method returns false. It also returns + * false if the src file doesn't even exist, since how could the + * target then be out of date. + * + * @param src the original resource + * @param target the resource being compared against + * @param granularity the long amount in seconds of slack we will give in + * determining out of dateness + * @return whether the target is out of date + */ + public static boolean isOutOfDate(Resource src, Resource target, long granularity) { + long sourceLastModified = src.getLastModified(); + long targetLastModified = target.getLastModified(); + return src.isExists() + && (sourceLastModified == Resource.UNKNOWN_DATETIME + || targetLastModified == Resource.UNKNOWN_DATETIME + || (sourceLastModified - granularity) > targetLastModified); + } + + /** + * "Flattens" a string by removing all whitespace (space, tab, linefeed, + * carriage return, and formfeed). This uses StringTokenizer and the + * default set of tokens as documented in the single argument constructor. + * + * @param input a String to remove all whitespace. + * @return a String that has had all whitespace removed. + */ + public static String removeWhitespace(String input) { + StringBuffer result = new StringBuffer(); + if (input != null) { + StringTokenizer st = new StringTokenizer(input); + while (st.hasMoreTokens()) { + result.append(st.nextToken()); + } + } + return result.toString(); + } + + /** + * Tests if a string contains stars or question marks + * @param input a String which one wants to test for containing wildcard + * @return true if the string contains at least a star or a question mark + */ + public static boolean hasWildcards(String input) { + return (input.indexOf('*') != -1 || input.indexOf('?') != -1); + } + + /** + * removes from a pattern all tokens to the right containing wildcards + * @param input the input string + * @return the leftmost part of the pattern without wildcards + */ + public static String rtrimWildcardTokens(String input) { + return new TokenizedPattern(input).rtrimWildcardTokens().toString(); + } +} + diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/TokenizedPath.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/TokenizedPath.java new file mode 100644 index 000000000..d3c19b037 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/TokenizedPath.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.types.selectors; + +import java.io.File; + +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; +import org.owasp.dependencycheck.org.apache.tools.ant.util.FileUtils; +import org.owasp.dependencycheck.org.apache.tools.ant.util.SymbolicLinkUtils; + +/** + * Container for a path that has been split into its components. + * @since 1.8.0 + */ +public class TokenizedPath { + + /** + * Instance that holds no tokens at all. + */ + public static final TokenizedPath EMPTY_PATH = + new TokenizedPath("", new String[0]); + + /** Helper. */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + /** Helper. */ + private static final SymbolicLinkUtils SYMLINK_UTILS = + SymbolicLinkUtils.getSymbolicLinkUtils(); + /** iterations for case-sensitive scanning. */ + private static final boolean[] CS_SCAN_ONLY = new boolean[] {true}; + /** iterations for non-case-sensitive scanning. */ + private static final boolean[] CS_THEN_NON_CS = new boolean[] {true, false}; + + private final String path; + private final String[] tokenizedPath; + + /** + * Initialize the TokenizedPath by parsing it. + * @param path The path to tokenize. Must not be + * null. + */ + public TokenizedPath(String path) { + this(path, SelectorUtils.tokenizePathAsArray(path)); + } + + /** + * Creates a new path as a child of another path. + * + * @param parent the parent path + * @param child the child, must not contain the file separator + */ + public TokenizedPath(TokenizedPath parent, String child) { + if (parent.path.length() > 0 + && parent.path.charAt(parent.path.length() - 1) + != File.separatorChar) { + path = parent.path + File.separatorChar + child; + } else { + path = parent.path + child; + } + tokenizedPath = new String[parent.tokenizedPath.length + 1]; + System.arraycopy(parent.tokenizedPath, 0, tokenizedPath, 0, + parent.tokenizedPath.length); + tokenizedPath[parent.tokenizedPath.length] = child; + } + + /* package */ TokenizedPath(String path, String[] tokens) { + this.path = path; + this.tokenizedPath = tokens; + } + + /** + * @return The original path String + */ + public String toString() { + return path; + } + + /** + * The depth (or length) of a path. + */ + public int depth() { + return tokenizedPath.length; + } + + /* package */ String[] getTokens() { + return tokenizedPath; + } + + /** + * From base traverse the filesystem in order to find + * a file that matches the given name. + * + * @param base base File (dir). + * @param cs whether to scan case-sensitively. + * @return File object that points to the file in question or null. + */ + public File findFile(File base, final boolean cs) { + String[] tokens = tokenizedPath; + if (FileUtils.isAbsolutePath(path)) { + if (base == null) { + String[] s = FILE_UTILS.dissect(path); + base = new File(s[0]); + tokens = SelectorUtils.tokenizePathAsArray(s[1]); + } else { + File f = FILE_UTILS.normalize(path); + String s = FILE_UTILS.removeLeadingPath(base, f); + if (s.equals(f.getAbsolutePath())) { + //removing base from path yields no change; path + //not child of base + return null; + } + tokens = SelectorUtils.tokenizePathAsArray(s); + } + } + return findFile(base, tokens, cs); + } + + /** + * Do we have to traverse a symlink when trying to reach path from + * basedir? + * @param base base File (dir). + */ + public boolean isSymlink(File base) { + for (int i = 0; i < tokenizedPath.length; i++) { + try { + if ((base != null + && SYMLINK_UTILS.isSymbolicLink(base, tokenizedPath[i])) + || + (base == null + && SYMLINK_UTILS.isSymbolicLink(tokenizedPath[i])) + ) { + return true; + } + base = new File(base, tokenizedPath[i]); + } catch (java.io.IOException ioe) { + String msg = "IOException caught while checking " + + "for links, couldn't get canonical path!"; + // will be caught and redirected to Ant's logging system + System.err.println(msg); + } + } + return false; + } + + /** + * true if the original paths are equal. + */ + public boolean equals(Object o) { + return o instanceof TokenizedPath + && path.equals(((TokenizedPath) o).path); + } + + public int hashCode() { + return path.hashCode(); + } + + /** + * From base traverse the filesystem in order to find + * a file that matches the given stack of names. + * + * @param base base File (dir) - must not be null. + * @param pathElements array of path elements (dirs...file). + * @param cs whether to scan case-sensitively. + * @return File object that points to the file in question or null. + */ + private static File findFile(File base, final String[] pathElements, + final boolean cs) { + for (int current = 0; current < pathElements.length; current++) { + if (!base.isDirectory()) { + return null; + } + String[] files = base.list(); + if (files == null) { + throw new BuildException("IO error scanning directory " + + base.getAbsolutePath()); + } + boolean found = false; + boolean[] matchCase = cs ? CS_SCAN_ONLY : CS_THEN_NON_CS; + for (int i = 0; !found && i < matchCase.length; i++) { + for (int j = 0; !found && j < files.length; j++) { + if (matchCase[i] + ? files[j].equals(pathElements[current]) + : files[j].equalsIgnoreCase(pathElements[current])) { + base = new File(base, files[j]); + found = true; + } + } + } + if (!found) { + return null; + } + } + return pathElements.length == 0 && !base.isDirectory() ? null : base; + } + + /** + * Creates a TokenizedPattern from the same tokens that make up + * this path. + */ + public TokenizedPattern toPattern() { + return new TokenizedPattern(path, tokenizedPath); + } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/TokenizedPattern.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/TokenizedPattern.java new file mode 100644 index 000000000..bfb666db6 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/TokenizedPattern.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +package org.owasp.dependencycheck.org.apache.tools.ant.types.selectors; + +import java.io.File; + +/** + * Provides reusable path pattern matching. PathPattern is preferable + * to equivalent SelectorUtils methods if you need to execute multiple + * matching with the same pattern because here the pattern itself will + * be parsed only once. + * @see SelectorUtils#matchPath(String, String) + * @see SelectorUtils#matchPath(String, String, boolean) + * @since 1.8.0 + */ +public class TokenizedPattern { + + /** + * Instance that holds no tokens at all. + */ + public static final TokenizedPattern EMPTY_PATTERN = + new TokenizedPattern("", new String[0]); + + private final String pattern; + private final String[] tokenizedPattern; + + /** + * Initialize the PathPattern by parsing it. + * @param pattern The pattern to match against. Must not be + * null. + */ + public TokenizedPattern(String pattern) { + this(pattern, SelectorUtils.tokenizePathAsArray(pattern)); + } + + TokenizedPattern(String pattern, String[] tokens) { + this.pattern = pattern; + this.tokenizedPattern = tokens; + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * @param path The path to match, as a String. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return true if the pattern matches against the string, + * or false otherwise. + */ + public boolean matchPath(TokenizedPath path, boolean isCaseSensitive) { + return SelectorUtils.matchPath(tokenizedPattern, path.getTokens(), + isCaseSensitive); + } + + /** + * Tests whether or not this pattern matches the start of + * a path. + */ + public boolean matchStartOf(TokenizedPath path, + boolean caseSensitive) { + return SelectorUtils.matchPatternStart(tokenizedPattern, + path.getTokens(), caseSensitive); + } + + /** + * @return The pattern String + */ + public String toString() { + return pattern; + } + + public String getPattern() { + return pattern; + } + + /** + * true if the original patterns are equal. + */ + public boolean equals(Object o) { + return o instanceof TokenizedPattern + && pattern.equals(((TokenizedPattern) o).pattern); + } + + public int hashCode() { + return pattern.hashCode(); + } + + /** + * The depth (or length) of a pattern. + */ + public int depth() { + return tokenizedPattern.length; + } + + /** + * Does the tokenized pattern contain the given string? + */ + public boolean containsPattern(String pat) { + for (int i = 0; i < tokenizedPattern.length; i++) { + if (tokenizedPattern[i].equals(pat)) { + return true; + } + } + return false; + } + + /** + * Returns a new TokenizedPath where all tokens of this pattern to + * the right containing wildcards have been removed + * @return the leftmost part of the pattern without wildcards + */ + public TokenizedPath rtrimWildcardTokens() { + StringBuilder sb = new StringBuilder(); + int newLen = 0; + for (; newLen < tokenizedPattern.length; newLen++) { + if (SelectorUtils.hasWildcards(tokenizedPattern[newLen])) { + break; + } + if (newLen > 0 + && sb.charAt(sb.length() - 1) != File.separatorChar) { + sb.append(File.separator); + } + sb.append(tokenizedPattern[newLen]); + } + if (newLen == 0) { + return TokenizedPath.EMPTY_PATH; + } + String[] newPats = new String[newLen]; + System.arraycopy(tokenizedPattern, 0, newPats, 0, newLen); + return new TokenizedPath(sb.toString(), newPats); + } + + /** + * true if the last token equals the given string. + */ + public boolean endsWith(String s) { + return tokenizedPattern.length > 0 + && tokenizedPattern[tokenizedPattern.length - 1].equals(s); + } + + /** + * Returns a new pattern without the last token of this pattern. + */ + public TokenizedPattern withoutLastToken() { + if (tokenizedPattern.length == 0) { + throw new IllegalStateException("cant strip a token from nothing"); + } else if (tokenizedPattern.length == 1) { + return EMPTY_PATTERN; + } else { + String toStrip = tokenizedPattern[tokenizedPattern.length - 1]; + int index = pattern.lastIndexOf(toStrip); + String[] tokens = new String[tokenizedPattern.length - 1]; + System.arraycopy(tokenizedPattern, 0, tokens, 0, + tokenizedPattern.length - 1); + return new TokenizedPattern(pattern.substring(0, index), tokens); + } + } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/package-info.java new file mode 100644 index 000000000..b5a6ae6d6 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/types/selectors/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant.types.selectors + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.types.selectors; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/CollectionUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/CollectionUtils.java new file mode 100644 index 000000000..fe334e346 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/CollectionUtils.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Vector; + +// CheckStyle:HideUtilityClassConstructorCheck OFF - bc + +/** + * A set of helper methods related to collection manipulation. + * + * @since Ant 1.5 + */ +public class CollectionUtils { + + /** + * Collections.emptyList() is Java5+. + */ + @SuppressWarnings("rawtypes") + @Deprecated + public static final List EMPTY_LIST = Collections.EMPTY_LIST; + + /** + * Please use Vector.equals() or List.equals(). + * @param v1 the first vector. + * @param v2 the second vector. + * @return true if the vectors are equal. + * @since Ant 1.5 + * @deprecated since 1.6.x. + */ + public static boolean equals(Vector v1, Vector v2) { + if (v1 == v2) { + return true; + } + + if (v1 == null || v2 == null) { + return false; + } + + return v1.equals(v2); + } + + /** + * Dictionary does not have an equals. + * Please use Map.equals(). + * + *

    Follows the equals contract of Java 2's Map.

    + * @param d1 the first directory. + * @param d2 the second directory. + * @return true if the directories are equal. + * @since Ant 1.5 + * @deprecated since 1.6.x. + */ + public static boolean equals(Dictionary d1, Dictionary d2) { + if (d1 == d2) { + return true; + } + + if (d1 == null || d2 == null) { + return false; + } + + if (d1.size() != d2.size()) { + return false; + } + + Enumeration e1 = d1.keys(); + while (e1.hasMoreElements()) { + Object key = e1.nextElement(); + Object value1 = d1.get(key); + Object value2 = d2.get(key); + if (value2 == null || !value1.equals(value2)) { + return false; + } + } + + // don't need the opposite check as the Dictionaries have the + // same size, so we've also covered all keys of d2 already. + + return true; + } + + /** + * Creates a comma separated list of all values held in the given + * collection. + * + * @since Ant 1.8.0 + */ + public static String flattenToString(Collection c) { + final StringBuilder sb = new StringBuilder(); + for (Object o : c) { + if (sb.length() != 0) { + sb.append(","); + } + sb.append(o); + } + return sb.toString(); + } + + /** + * Dictionary does not know the putAll method. Please use Map.putAll(). + * @param m1 the to directory. + * @param m2 the from directory. + * @since Ant 1.6 + * @deprecated since 1.6.x. + */ + public static void putAll(Dictionary m1, Dictionary m2) { + for (Enumeration it = m2.keys(); it.hasMoreElements();) { + K key = it.nextElement(); + m1.put(key, m2.get(key)); + } + } + + /** + * An empty enumeration. + * @since Ant 1.6 + */ + public static final class EmptyEnumeration implements Enumeration { + /** Constructor for the EmptyEnumeration */ + public EmptyEnumeration() { + } + + /** + * @return false always. + */ + public boolean hasMoreElements() { + return false; + } + + /** + * @return nothing. + * @throws NoSuchElementException always. + */ + public E nextElement() throws NoSuchElementException { + throw new NoSuchElementException(); + } + } + + /** + * Append one enumeration to another. + * Elements are evaluated lazily. + * @param e1 the first enumeration. + * @param e2 the subsequent enumeration. + * @return an enumeration representing e1 followed by e2. + * @since Ant 1.6.3 + */ + public static Enumeration append(Enumeration e1, Enumeration e2) { + return new CompoundEnumeration(e1, e2); + } + + /** + * Adapt the specified Iterator to the Enumeration interface. + * @param iter the Iterator to adapt. + * @return an Enumeration. + */ + public static Enumeration asEnumeration(final Iterator iter) { + return new Enumeration() { + public boolean hasMoreElements() { + return iter.hasNext(); + } + public E nextElement() { + return iter.next(); + } + }; + } + + /** + * Adapt the specified Enumeration to the Iterator interface. + * @param e the Enumeration to adapt. + * @return an Iterator. + */ + public static Iterator asIterator(final Enumeration e) { + return new Iterator() { + public boolean hasNext() { + return e.hasMoreElements(); + } + public E next() { + return e.nextElement(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Returns a collection containing all elements of the iterator. + * + * @since Ant 1.8.0 + */ + public static Collection asCollection(final Iterator iter) { + List l = new ArrayList(); + while (iter.hasNext()) { + l.add(iter.next()); + } + return l; + } + + private static final class CompoundEnumeration implements Enumeration { + + private final Enumeration e1, e2; + + public CompoundEnumeration(Enumeration e1, Enumeration e2) { + this.e1 = e1; + this.e2 = e2; + } + + public boolean hasMoreElements() { + return e1.hasMoreElements() || e2.hasMoreElements(); + } + + public E nextElement() throws NoSuchElementException { + if (e1.hasMoreElements()) { + return e1.nextElement(); + } else { + return e2.nextElement(); + } + } + + } + + /** + * Counts how often the given Object occurs in the given + * collection using equals() for comparison. + * + * @since Ant 1.8.0 + */ + public static int frequency(Collection c, Object o) { + // same as Collections.frequency introduced with JDK 1.5 + int freq = 0; + if (c != null) { + for (Iterator i = c.iterator(); i.hasNext(); ) { + Object test = i.next(); + if (o == null ? test == null : o.equals(test)) { + freq++; + } + } + } + return freq; + } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/FileUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/FileUtils.java new file mode 100644 index 000000000..a097c7991 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/FileUtils.java @@ -0,0 +1,1667 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channel; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Stack; +import java.util.StringTokenizer; +import java.util.jar.JarFile; +import org.owasp.dependencycheck.org.apache.tools.ant.BuildException; +import org.owasp.dependencycheck.org.apache.tools.ant.PathTokenizer; +import org.owasp.dependencycheck.org.apache.tools.ant.launch.Locator; +import org.owasp.dependencycheck.org.apache.tools.ant.taskdefs.condition.Os; + +/** + * This class also encapsulates methods which allow Files to be referred to using abstract path names which are + * translated to native system file paths at runtime as well as copying files or setting their last modification time. + * + */ +public class FileUtils { + + private static final int DELETE_RETRY_SLEEP_MILLIS = 10; + private static final int EXPAND_SPACE = 50; + private static final FileUtils PRIMARY_INSTANCE = new FileUtils(); + + //get some non-crypto-grade randomness from various places. + private static Random rand = new Random(System.currentTimeMillis() + + Runtime.getRuntime().freeMemory()); + + private static final boolean ON_NETWARE = Os.isFamily("netware"); + private static final boolean ON_DOS = Os.isFamily("dos"); + private static final boolean ON_WIN9X = Os.isFamily("win9x"); + private static final boolean ON_WINDOWS = Os.isFamily("windows"); + + static final int BUF_SIZE = 8192; + + /** + * The granularity of timestamps under FAT. + */ + public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000; + + /** + * The granularity of timestamps under Unix. + */ + public static final long UNIX_FILE_TIMESTAMP_GRANULARITY = 1000; + + /** + * The granularity of timestamps under the NT File System. NTFS has a granularity of 100 nanoseconds, which is less + * than 1 millisecond, so we round this up to 1 millisecond. + */ + public static final long NTFS_FILE_TIMESTAMP_GRANULARITY = 1; + + /** + * A one item cache for fromUri. fromUri is called for each element when parseing ant build files. It is a costly + * operation. This just caches the result of the last call. + */ + private Object cacheFromUriLock = new Object(); + private String cacheFromUriRequest = null; + private String cacheFromUriResponse = null; + + /** + * Factory method. + * + * @return a new instance of FileUtils. + * @deprecated since 1.7. Use getFileUtils instead, FileUtils do not have state. + */ + public static FileUtils newFileUtils() { + return new FileUtils(); + } + + /** + * Method to retrieve The FileUtils, which is shared by all users of this method. + * + * @return an instance of FileUtils. + * @since Ant 1.6.3 + */ + public static FileUtils getFileUtils() { + return PRIMARY_INSTANCE; + } + + /** + * Empty constructor. + */ + protected FileUtils() { + } + + /** + * Get the URL for a file taking into account # characters. + * + * @param file the file whose URL representation is required. + * @return The FileURL value. + * @throws MalformedURLException if the URL representation cannot be formed. + */ + public URL getFileURL(File file) throws MalformedURLException { + return new URL(file.toURI().toASCIIString()); + } + +// /** +// * Convenience method to copy a file from a source to a destination. +// * No filtering is performed. +// * +// * @param sourceFile Name of file to copy from. +// * Must not be null. +// * @param destFile Name of file to copy to. +// * Must not be null. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(String sourceFile, String destFile) throws IOException { +// copyFile(new File(sourceFile), new File(destFile), null, false, false); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination +// * specifying if token filtering must be used. +// * +// * @param sourceFile Name of file to copy from. +// * Must not be null. +// * @param destFile Name of file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(String sourceFile, String destFile, FilterSetCollection filters) +// throws IOException { +// copyFile(new File(sourceFile), new File(destFile), filters, false, false); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination specifying if token +// * filtering must be used and if source files may overwrite newer destination files. +// * +// * @param sourceFile Name of file to copy from. Must not be null. +// * @param destFile Name of file to copy to. Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param overwrite Whether or not the destination file should be overwritten if it already +// * exists. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(String sourceFile, String destFile, FilterSetCollection filters, +// boolean overwrite) throws IOException { +// copyFile(new File(sourceFile), new File(destFile), filters, overwrite, false); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination +// * specifying if token +// * filtering must be used, if source files may overwrite newer destination +// * files and the last +// * modified time of destFile file should be made equal to +// * the last modified time +// * of sourceFile. +// * +// * @param sourceFile Name of file to copy from. Must not be null. +// * @param destFile Name of file to copy to. Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file +// * should be set to that of the source file. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(String sourceFile, String destFile, +// FilterSetCollection filters, +// boolean overwrite, boolean preserveLastModified) +// throws IOException { +// copyFile(new File(sourceFile), new File(destFile), filters, overwrite, +// preserveLastModified); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination specifying if token +// * filtering must be used, if source files may overwrite newer destination files and the last +// * modified time of destFile file should be made equal to the last modified time +// * of sourceFile. +// * +// * @param sourceFile Name of file to copy from. Must not be null. +// * @param destFile Name of file to copy to. Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param overwrite Whether or not the destination file should be overwritten if it already +// * exists. +// * @param preserveLastModified Whether or not the last modified time of the resulting file +// * should be set to that of the source file. +// * @param encoding the encoding used to read and write the files. +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.5 +// */ +// public void copyFile(String sourceFile, String destFile, +// FilterSetCollection filters, boolean overwrite, +// boolean preserveLastModified, String encoding) throws IOException { +// copyFile(new File(sourceFile), new File(destFile), filters, +// overwrite, preserveLastModified, encoding); +// } +// // CheckStyle:ParameterNumberCheck OFF - bc +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used, if +// * filter chains must be used, if source files may overwrite +// * newer destination files and the last modified time of +// * destFile file should be made equal +// * to the last modified time of sourceFile. +// * +// * @param sourceFile Name of file to copy from. +// * Must not be null. +// * @param destFile Name of file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param filterChains filterChains to apply during the copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file should be set to that +// * of the source file. +// * @param encoding the encoding used to read and write the files. +// * @param project the project instance. +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.5 +// */ +// public void copyFile(String sourceFile, String destFile, +// FilterSetCollection filters, Vector filterChains, +// boolean overwrite, boolean preserveLastModified, +// String encoding, Project project) throws IOException { +// copyFile(new File(sourceFile), new File(destFile), filters, filterChains, overwrite, +// preserveLastModified, encoding, project); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination specifying if token +// * filtering must be used, if filter chains must be used, if source files may overwrite newer +// * destination files and the last modified time of destFile file should be made +// * equal to the last modified time of sourceFile. +// * +// * @param sourceFile Name of file to copy from. Must not be null. +// * @param destFile Name of file to copy to. Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param filterChains filterChains to apply during the copy. +// * @param overwrite Whether or not the destination file should be overwritten if it already +// * exists. +// * @param preserveLastModified Whether or not the last modified time of the resulting file +// * should be set to that of the source file. +// * @param inputEncoding the encoding used to read the files. +// * @param outputEncoding the encoding used to write the files. +// * @param project the project instance. +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.6 +// */ +// public void copyFile(String sourceFile, String destFile, +// FilterSetCollection filters, Vector filterChains, +// boolean overwrite, boolean preserveLastModified, +// String inputEncoding, String outputEncoding, +// Project project) throws IOException { +// copyFile(new File(sourceFile), new File(destFile), filters, filterChains, overwrite, +// preserveLastModified, inputEncoding, outputEncoding, project); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination. No filtering is performed. +// * +// * @param sourceFile the file to copy from. Must not be null. +// * @param destFile the file to copy to. Must not be null. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(File sourceFile, File destFile) throws IOException { +// copyFile(sourceFile, destFile, null, false, false); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination +// * specifying if token filtering must be used. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(File sourceFile, File destFile, FilterSetCollection filters) +// throws IOException { +// copyFile(sourceFile, destFile, filters, false, false); +// } +// +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used and if +// * source files may overwrite newer destination files. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(File sourceFile, File destFile, FilterSetCollection filters, +// boolean overwrite) throws IOException { +// copyFile(sourceFile, destFile, filters, overwrite, false); +// } +// +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used, if +// * source files may overwrite newer destination files and the +// * last modified time of destFile file should be made equal +// * to the last modified time of sourceFile. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file should be set to that +// * of the source file. +// * +// * @throws IOException if the copying fails. +// */ +// public void copyFile(File sourceFile, File destFile, FilterSetCollection filters, +// boolean overwrite, boolean preserveLastModified) throws IOException { +// copyFile(sourceFile, destFile, filters, overwrite, preserveLastModified, null); +// } +// +// /** +// * Convenience method to copy a file from a source to a destination specifying if token +// * filtering must be used, if source files may overwrite newer destination files, the last +// * modified time of destFile file should be made equal to the last modified time +// * of sourceFile and which character encoding to assume. +// * +// * @param sourceFile the file to copy from. Must not be null. +// * @param destFile the file to copy to. Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param overwrite Whether or not the destination file should be overwritten if it already +// * exists. +// * @param preserveLastModified Whether or not the last modified time of the resulting file +// * should be set to that of the source file. +// * @param encoding the encoding used to read and write the files. +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.5 +// */ +// public void copyFile(File sourceFile, File destFile, +// FilterSetCollection filters, boolean overwrite, +// boolean preserveLastModified, String encoding) throws IOException { +// copyFile(sourceFile, destFile, filters, null, overwrite, +// preserveLastModified, encoding, null); +// } +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used, if +// * filter chains must be used, if source files may overwrite +// * newer destination files and the last modified time of +// * destFile file should be made equal +// * to the last modified time of sourceFile. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param filterChains filterChains to apply during the copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file should be set to that +// * of the source file. +// * @param encoding the encoding used to read and write the files. +// * @param project the project instance. +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.5 +// */ +// public void copyFile(File sourceFile, File destFile, +// FilterSetCollection filters, Vector filterChains, +// boolean overwrite, boolean preserveLastModified, +// String encoding, Project project) throws IOException { +// copyFile(sourceFile, destFile, filters, filterChains, +// overwrite, preserveLastModified, encoding, encoding, project); +// } +// +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used, if +// * filter chains must be used, if source files may overwrite +// * newer destination files and the last modified time of +// * destFile file should be made equal +// * to the last modified time of sourceFile. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param filterChains filterChains to apply during the copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file should be set to that +// * of the source file. +// * @param inputEncoding the encoding used to read the files. +// * @param outputEncoding the encoding used to write the files. +// * @param project the project instance. +// * +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.6 +// */ +// public void copyFile(File sourceFile, File destFile, +// FilterSetCollection filters, Vector filterChains, +// boolean overwrite, boolean preserveLastModified, +// String inputEncoding, String outputEncoding, +// Project project) throws IOException { +// copyFile(sourceFile, destFile, filters, filterChains, overwrite, preserveLastModified, +// false, inputEncoding, outputEncoding, project); +// } +// +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used, if +// * filter chains must be used, if source files may overwrite +// * newer destination files and the last modified time of +// * destFile file should be made equal +// * to the last modified time of sourceFile. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param filterChains filterChains to apply during the copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file should be set to that +// * of the source file. +// * @param append whether to append to the destination file. +// * @param inputEncoding the encoding used to read the files. +// * @param outputEncoding the encoding used to write the files. +// * @param project the project instance. +// * +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.8 +// */ +// public void copyFile(File sourceFile, File destFile, +// FilterSetCollection filters, Vector filterChains, +// boolean overwrite, boolean preserveLastModified, +// boolean append, +// String inputEncoding, String outputEncoding, +// Project project) throws IOException { +// copyFile(sourceFile, destFile, filters, filterChains, overwrite, +// preserveLastModified, append, inputEncoding, outputEncoding, +// project, /* force: */ false); +// } +// +// /** +// * Convenience method to copy a file from a source to a +// * destination specifying if token filtering must be used, if +// * filter chains must be used, if source files may overwrite +// * newer destination files and the last modified time of +// * destFile file should be made equal +// * to the last modified time of sourceFile. +// * +// * @param sourceFile the file to copy from. +// * Must not be null. +// * @param destFile the file to copy to. +// * Must not be null. +// * @param filters the collection of filters to apply to this copy. +// * @param filterChains filterChains to apply during the copy. +// * @param overwrite Whether or not the destination file should be +// * overwritten if it already exists. +// * @param preserveLastModified Whether or not the last modified time of +// * the resulting file should be set to that +// * of the source file. +// * @param append whether to append to the destination file. +// * @param inputEncoding the encoding used to read the files. +// * @param outputEncoding the encoding used to write the files. +// * @param project the project instance. +// * @param force whether to overwrite read-only destination files. +// * +// * @throws IOException if the copying fails. +// * +// * @since Ant 1.8.2 +// */ +// public void copyFile(File sourceFile, File destFile, +// FilterSetCollection filters, Vector filterChains, +// boolean overwrite, boolean preserveLastModified, +// boolean append, +// String inputEncoding, String outputEncoding, +// Project project, boolean force) throws IOException { +// ResourceUtils.copyResource(new FileResource(sourceFile), +// new FileResource(destFile), +// filters, filterChains, overwrite, +// preserveLastModified, append, inputEncoding, +// outputEncoding, project, force); +// } +// +// // CheckStyle:ParameterNumberCheck ON +// +// /** +// * Calls File.setLastModified(long time). Originally written to +// * to dynamically bind to that call on Java1.2+. +// * +// * @param file the file whose modified time is to be set +// * @param time the time to which the last modified time is to be set. +// * if this is -1, the current time is used. +// */ +// public void setFileLastModified(File file, long time) { +// ResourceUtils.setLastModified(new FileResource(file), time); +// } + /** + * Interpret the filename as a file relative to the given file unless the filename already represents an absolute + * filename. Differs from new File(file, filename) in that the resulting File's path will always be a + * normalized, absolute pathname. Also, if it is determined that filename is context-relative, + * file will be discarded and the reference will be resolved using available context/state information + * about the filesystem. + * + * @param file the "reference" file for relative paths. This instance must be an absolute file and must not contain + * "./" or "../" sequences (same for \ instead of /). If it is null, this call is equivalent to + * new java.io.File(filename).getAbsoluteFile(). + * + * @param filename a file name. + * + * @return an absolute file. + * @throws java.lang.NullPointerException if filename is null. + */ + public File resolveFile(File file, String filename) { + if (!isAbsolutePath(filename)) { + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + if (isContextRelativePath(filename)) { + file = null; + // on cygwin, our current directory can be a UNC; + // assume user.dir is absolute or all hell breaks loose... + String udir = System.getProperty("user.dir"); + if (filename.charAt(0) == sep && udir.charAt(0) == sep) { + filename = dissect(udir)[0] + filename.substring(1); + } + } + filename = new File(file, filename).getAbsolutePath(); + } + return normalize(filename); + } + + /** + * On DOS and NetWare, the evaluation of certain file specifications is context-dependent. These are filenames + * beginning with a single separator (relative to current root directory) and filenames with a drive specification + * and no intervening separator (relative to current directory of the specified root). + * + * @param filename the filename to evaluate. + * @return true if the filename is relative to system context. + * @throws java.lang.NullPointerException if filename is null. + * @since Ant 1.7 + */ + public static boolean isContextRelativePath(String filename) { + if (!(ON_DOS || ON_NETWARE) || filename.length() == 0) { + return false; + } + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + char c = filename.charAt(0); + int len = filename.length(); + return (c == sep && (len == 1 || filename.charAt(1) != sep)) + || (Character.isLetter(c) && len > 1 + && filename.charAt(1) == ':' + && (len == 2 || filename.charAt(2) != sep)); + } + + /** + * Verifies that the specified filename represents an absolute path. Differs from new + * java.io.File("filename").isAbsolute() in that a path beginning with a double file separator--signifying a Windows + * UNC--must at minimum match "\\a\b" to be considered an absolute path. + * + * @param filename the filename to be checked. + * @return true if the filename represents an absolute path. + * @throws java.lang.NullPointerException if filename is null. + * @since Ant 1.6.3 + */ + public static boolean isAbsolutePath(String filename) { + int len = filename.length(); + if (len == 0) { + return false; + } + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + char c = filename.charAt(0); + if (!(ON_DOS || ON_NETWARE)) { + return (c == sep); + } + if (c == sep) { + // CheckStyle:MagicNumber OFF + if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) { + return false; + } + // CheckStyle:MagicNumber ON + int nextsep = filename.indexOf(sep, 2); + return nextsep > 2 && nextsep + 1 < len; + } + int colon = filename.indexOf(':'); + return (Character.isLetter(c) && colon == 1 + && filename.length() > 2 && filename.charAt(2) == sep) + || (ON_NETWARE && colon > 0); + } + + /** + * Translate a path into its native (platform specific) format. + *

    + * This method uses PathTokenizer to separate the input path into its components. This handles DOS style paths in a + * relatively sensible way. The file separators are then converted to their platform specific versions. + * + * @param toProcess The path to be translated. May be null. + * + * @return the native version of the specified path or an empty string if the path is null or empty. + * + * @since ant 1.7 + * @see PathTokenizer + */ + public static String translatePath(String toProcess) { + if (toProcess == null || toProcess.length() == 0) { + return ""; + } + StringBuffer path = new StringBuffer(toProcess.length() + EXPAND_SPACE); + PathTokenizer tokenizer = new PathTokenizer(toProcess); + while (tokenizer.hasMoreTokens()) { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace('/', File.separatorChar); + pathComponent = pathComponent.replace('\\', File.separatorChar); + if (path.length() != 0) { + path.append(File.pathSeparatorChar); + } + path.append(pathComponent); + } + return path.toString(); + } + + /** + * "Normalize" the given absolute path. + * + *

    + * This includes: + *

      + *
    • Uppercase the drive letter if there is one.
    • + *
    • Remove redundant slashes after the drive spec.
    • + *
    • Resolve all ./, .\, ../ and ..\ sequences.
    • + *
    • DOS style paths that start with a drive letter will have \ as the separator.
    • + *
    + * Unlike {@link File#getCanonicalPath()} this method specifically does not resolve symbolic links. + * + * @param path the path to be normalized. + * @return the normalized version of the path. + * + * @throws java.lang.NullPointerException if path is null. + */ + public File normalize(final String path) { + Stack s = new Stack(); + String[] dissect = dissect(path); + s.push(dissect[0]); + + StringTokenizer tok = new StringTokenizer(dissect[1], File.separator); + while (tok.hasMoreTokens()) { + String thisToken = tok.nextToken(); + if (".".equals(thisToken)) { + continue; + } + if ("..".equals(thisToken)) { + if (s.size() < 2) { + // Cannot resolve it, so skip it. + return new File(path); + } + s.pop(); + } else { // plain component + s.push(thisToken); + } + } + StringBuffer sb = new StringBuffer(); + final int size = s.size(); + for (int i = 0; i < size; i++) { + if (i > 1) { + // not before the filesystem root and not after it, since root + // already contains one + sb.append(File.separatorChar); + } + sb.append(s.elementAt(i)); + } + return new File(sb.toString()); + } + + /** + * Dissect the specified absolute path. + * + * @param path the path to dissect. + * @return String[] {root, remaining path}. + * @throws java.lang.NullPointerException if path is null. + * @since Ant 1.7 + */ + public String[] dissect(String path) { + char sep = File.separatorChar; + path = path.replace('/', sep).replace('\\', sep); + + // make sure we are dealing with an absolute path + if (!isAbsolutePath(path)) { + throw new BuildException(path + " is not an absolute path"); + } + String root = null; + int colon = path.indexOf(':'); + if (colon > 0 && (ON_DOS || ON_NETWARE)) { + + int next = colon + 1; + root = path.substring(0, next); + char[] ca = path.toCharArray(); + root += sep; + //remove the initial separator; the root has it. + next = (ca[next] == sep) ? next + 1 : next; + + StringBuffer sbPath = new StringBuffer(); + // Eliminate consecutive slashes after the drive spec: + for (int i = next; i < ca.length; i++) { + if (ca[i] != sep || ca[i - 1] != sep) { + sbPath.append(ca[i]); + } + } + path = sbPath.toString(); + } else if (path.length() > 1 && path.charAt(1) == sep) { + // UNC drive + int nextsep = path.indexOf(sep, 2); + nextsep = path.indexOf(sep, nextsep + 1); + root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path; + path = path.substring(root.length()); + } else { + root = File.separator; + path = path.substring(1); + } + return new String[]{root, path}; + } + + /** + * Returns a VMS String representation of a File object. This is useful since the JVM by default + * internally converts VMS paths to Unix style. The returned String is always an absolute path. + * + * @param f The File to get the VMS path for. + * @return The absolute VMS path to f. + */ + public String toVMSPath(File f) { + // format: "DEVICE:[DIR.SUBDIR]FILE" + String osPath; + String path = normalize(f.getAbsolutePath()).getPath(); + String name = f.getName(); + boolean isAbsolute = path.charAt(0) == File.separatorChar; + // treat directories specified using .DIR syntax as files + // CheckStyle:MagicNumber OFF + boolean isDirectory = f.isDirectory() + && !name.regionMatches(true, name.length() - 4, ".DIR", 0, 4); + // CheckStyle:MagicNumber ON + String device = null; + StringBuffer directory = null; + String file = null; + + int index = 0; + + if (isAbsolute) { + index = path.indexOf(File.separatorChar, 1); + if (index == -1) { + return path.substring(1) + ":[000000]"; + } + device = path.substring(1, index++); + } + if (isDirectory) { + directory = new StringBuffer(path.substring(index).replace(File.separatorChar, '.')); + } else { + int dirEnd = path.lastIndexOf(File.separatorChar, path.length()); + if (dirEnd == -1 || dirEnd < index) { + file = path.substring(index); + } else { + directory = new StringBuffer(path.substring(index, dirEnd). + replace(File.separatorChar, '.')); + index = dirEnd + 1; + if (path.length() > index) { + file = path.substring(index); + } + } + } + if (!isAbsolute && directory != null) { + directory.insert(0, '.'); + } + osPath = ((device != null) ? device + ":" : "") + + ((directory != null) ? "[" + directory + "]" : "") + + ((file != null) ? file : ""); + return osPath; + } + + /** + * Create a File object for a temporary file in a given directory. Without actually creating the file. + * + *

    + * The file denoted by the returned abstract pathname did not exist before this method was invoked, any subsequent + * invocation of this method will yield a different file name. + *

    + *

    + * The filename is prefixNNNNNsuffix where NNNN is a random number. + *

    + * + * @param prefix prefix before the random number. + * @param suffix file extension; include the '.'. + * @param parentDir Directory to create the temporary file in; java.io.tmpdir used if not specified. + * + * @deprecated since ant 1.7.1 use createTempFile(String, String, File, boolean, boolean) instead. + * @return a File reference to the new, nonexistent temporary file. + */ + public File createTempFile(String prefix, String suffix, File parentDir) { + return createTempFile(prefix, suffix, parentDir, false, false); + } + + private static final String NULL_PLACEHOLDER = "null"; + + /** + * Create a temporary file in a given directory. + * + *

    + * The file denoted by the returned abstract pathname did not exist before this method was invoked, any subsequent + * invocation of this method will yield a different file name.

    + * + * @param prefix prefix before the random number. + * @param suffix file extension; include the '.'. + * @param parentDir Directory to create the temporary file in; java.io.tmpdir used if not specified. + * @param deleteOnExit whether to set the tempfile for deletion on normal VM exit. + * @param createFile true if the file must actually be created. If false chances exist that a file with the same + * name is created in the time between invoking this method and the moment the file is actually created. If possible + * set to true. + * + * @return a File reference to the new temporary file. + * @since Ant 1.7.1 + */ + public File createTempFile(String prefix, String suffix, File parentDir, + boolean deleteOnExit, boolean createFile) { + File result = null; + String parent = (parentDir == null) + ? System.getProperty("java.io.tmpdir") + : parentDir.getPath(); + if (prefix == null) { + prefix = NULL_PLACEHOLDER; + } + if (suffix == null) { + suffix = NULL_PLACEHOLDER; + } + + if (createFile) { + try { + result = File.createTempFile(prefix, suffix, new File(parent)); + } catch (IOException e) { + throw new BuildException("Could not create tempfile in " + + parent, e); + } + } else { + DecimalFormat fmt = new DecimalFormat("#####"); + synchronized (rand) { + do { + result = new File(parent, prefix + + fmt.format(rand.nextInt(Integer.MAX_VALUE)) + suffix); + } while (result.exists()); + } + } + + if (deleteOnExit) { + result.deleteOnExit(); + } + return result; + } + + /** + * Create a File object for a temporary file in a given directory. Without actually creating the file. + * + *

    + * The file denoted by the returned abstract pathname did not exist before this method was invoked, any subsequent + * invocation of this method will yield a different file name. + *

    + *

    + * The filename is prefixNNNNNsuffix where NNNN is a random number. + *

    + * + * @param prefix prefix before the random number. + * @param suffix file extension; include the '.'. + * @param parentDir Directory to create the temporary file in; java.io.tmpdir used if not specified. + * @param deleteOnExit whether to set the tempfile for deletion on normal VM exit. + * + * @deprecated since ant 1.7.1 use createTempFile(String, String, File, boolean, boolean) instead. + * @return a File reference to the new, nonexistent temporary file. + */ + public File createTempFile(String prefix, String suffix, + File parentDir, boolean deleteOnExit) { + return createTempFile(prefix, suffix, parentDir, deleteOnExit, false); + } + +// /** +// * Compares the contents of two files. +// * +// * @param f1 the file whose content is to be compared. +// * @param f2 the other file whose content is to be compared. +// * +// * @return true if the content of the files is the same. +// * +// * @throws IOException if the files cannot be read. +// */ +// public boolean contentEquals(File f1, File f2) throws IOException { +// return contentEquals(f1, f2, false); +// } +// +// /** +// * Compares the contents of two files. +// * +// * @param f1 the file whose content is to be compared. +// * @param f2 the other file whose content is to be compared. +// * @param textfile true if the file is to be treated as a text file and +// * differences in kind of line break are to be ignored. +// * +// * @return true if the content of the files is the same. +// * +// * @throws IOException if the files cannot be read. +// * @since Ant 1.6.3 +// */ +// public boolean contentEquals(File f1, File f2, boolean textfile) throws IOException { +// return ResourceUtils.contentEquals(new FileResource(f1), new FileResource(f2), textfile); +// } + /** + * This was originally an emulation of {@link File#getParentFile} for JDK 1.1, but it is now implemented using that + * method (Ant 1.6.3 onwards). + * + * @param f the file whose parent is required. + * @return the given file's parent, or null if the file does not have a parent. + * @since 1.10 + * @deprecated since 1.7. Just use {@link File#getParentFile} directly. + */ + public File getParentFile(File f) { + return (f == null) ? null : f.getParentFile(); + } + + /** + * Read from reader till EOF. + * + * @param rdr the reader from which to read. + * @return the contents read out of the given reader. + * + * @throws IOException if the contents could not be read out from the reader. + */ + public static String readFully(Reader rdr) throws IOException { + return readFully(rdr, BUF_SIZE); + } + + /** + * Read from reader till EOF. + * + * @param rdr the reader from which to read. + * @param bufferSize the buffer size to use when reading. + * + * @return the contents read out of the given reader. + * + * @throws IOException if the contents could not be read out from the reader. + */ + public static String readFully(Reader rdr, int bufferSize) + throws IOException { + if (bufferSize <= 0) { + throw new IllegalArgumentException("Buffer size must be greater " + + "than 0"); + } + final char[] buffer = new char[bufferSize]; + int bufferLength = 0; + StringBuffer textBuffer = null; + while (bufferLength != -1) { + bufferLength = rdr.read(buffer); + if (bufferLength > 0) { + textBuffer = (textBuffer == null) ? new StringBuffer() : textBuffer; + textBuffer.append(new String(buffer, 0, bufferLength)); + } + } + return (textBuffer == null) ? null : textBuffer.toString(); + } + + /** + * Safe read fully - do not return a null for an empty reader. + * + * @param reader the input to read from. + * @return the string. + * @throws IOException if unable to read from reader. + * @since Ant 1.7.1 + */ + public static String safeReadFully(Reader reader) throws IOException { + String ret = readFully(reader); + return ret == null ? "" : ret; + } + + /** + * This was originally an emulation of File.createNewFile for JDK 1.1, but it is now implemented using that method + * (Ant 1.6.3 onwards). + * + *

    + * This method has historically not guaranteed that the operation was atomic. In its current + * implementation it is. + * + * @param f the file to be created. + * @return true if the file did not exist already. + * @throws IOException on error. + * @since Ant 1.5 + */ + public boolean createNewFile(File f) throws IOException { + return f.createNewFile(); + } + + /** + * Create a new file, optionally creating parent directories. + * + * @param f the file to be created. + * @param mkdirs boolean whether to create parent directories. + * @return true if the file did not exist already. + * @throws IOException on error. + * @since Ant 1.6.3 + */ + public boolean createNewFile(File f, boolean mkdirs) throws IOException { + File parent = f.getParentFile(); + if (mkdirs && !(parent.exists())) { + parent.mkdirs(); + } + return f.createNewFile(); + } + + /** + * Checks whether a given file is a symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are + * identical--this may lead to false positives on some platforms.

    + * + * @param parent the parent directory of the file to test + * @param name the name of the file to test. + * + * @return true if the file is a symbolic link. + * @throws IOException on error. + * @since Ant 1.5 + * @deprecated use SymbolicLinkUtils instead + */ + public boolean isSymbolicLink(File parent, String name) + throws IOException { + SymbolicLinkUtils u = SymbolicLinkUtils.getSymbolicLinkUtils(); + if (parent == null) { + return u.isSymbolicLink(name); + } + return u.isSymbolicLink(parent, name); + } + + /** + * Removes a leading path from a second path. + * + * @param leading The leading path, must not be null, must be absolute. + * @param path The path to remove from, must not be null, must be absolute. + * + * @return path's normalized absolute if it doesn't start with leading; path's path with leading's path removed + * otherwise. + * + * @since Ant 1.5 + */ + public String removeLeadingPath(File leading, File path) { + String l = normalize(leading.getAbsolutePath()).getAbsolutePath(); + String p = normalize(path.getAbsolutePath()).getAbsolutePath(); + if (l.equals(p)) { + return ""; + } + // ensure that l ends with a / + // so we never think /foo was a parent directory of /foobar + if (!l.endsWith(File.separator)) { + l += File.separator; + } + return (p.startsWith(l)) ? p.substring(l.length()) : p; + } + + /** + * Learn whether one path "leads" another. + * + * @param leading The leading path, must not be null, must be absolute. + * @param path The path to remove from, must not be null, must be absolute. + * @return true if path starts with leading; false otherwise. + * @since Ant 1.7 + */ + public boolean isLeadingPath(File leading, File path) { + String l = normalize(leading.getAbsolutePath()).getAbsolutePath(); + String p = normalize(path.getAbsolutePath()).getAbsolutePath(); + if (l.equals(p)) { + return true; + } + // ensure that l ends with a / + // so we never think /foo was a parent directory of /foobar + if (!l.endsWith(File.separator)) { + l += File.separator; + } + return p.startsWith(l); + } + + /** + * Constructs a file: URI that represents the external form of the given pathname. + * + *

    + * Will be an absolute URI if the given path is absolute.

    + * + *

    + * This code encodes non ASCII characters too.

    + * + *

    + * The coding of the output is the same as what File.toURI().toASCIIString() produces

    + * + * See dt-sysid + * which makes some mention of how characters not supported by URI Reference syntax should be escaped. + * + * @param path the path in the local file system. + * @return the URI version of the local path. + * @since Ant 1.6 + */ + public String toURI(String path) { + return new File(path).toURI().toASCIIString(); + } + + /** + * Constructs a file path from a file: URI. + * + *

    + * Will be an absolute path if the given URI is absolute.

    + * + *

    + * Swallows '%' that are not followed by two characters, doesn't deal with non-ASCII characters.

    + * + * @param uri the URI designating a file in the local filesystem. + * @return the local file system path for the file. + * @since Ant 1.6 + */ + public String fromURI(String uri) { + synchronized (cacheFromUriLock) { + if (uri.equals(cacheFromUriRequest)) { + return cacheFromUriResponse; + } + String path = Locator.fromURI(uri); + String ret = isAbsolutePath(path) ? normalize(path).getAbsolutePath() : path; + cacheFromUriRequest = uri; + cacheFromUriResponse = ret; + return ret; + } + } + + /** + * Compares two filenames. + * + *

    + * Unlike java.io.File#equals this method will try to compare the absolute paths and "normalize" the + * filenames before comparing them.

    + * + * @param f1 the file whose name is to be compared. + * @param f2 the other file whose name is to be compared. + * + * @return true if the file are for the same file. + * + * @since Ant 1.5.3 + */ + public boolean fileNameEquals(File f1, File f2) { + return normalize(f1.getAbsolutePath()).getAbsolutePath().equals( + normalize(f2.getAbsolutePath()).getAbsolutePath()); + } + + /** + * Are the two File instances pointing to the same object on the file system? + * + * @since Ant 1.8.2 + */ + public boolean areSame(File f1, File f2) throws IOException { + if (f1 == null && f2 == null) { + return true; + } + if (f1 == null || f2 == null) { + return false; + } + File f1Normalized = normalize(f1.getAbsolutePath()); + File f2Normalized = normalize(f2.getAbsolutePath()); + return f1Normalized.equals(f2Normalized) + || f1Normalized.getCanonicalFile().equals(f2Normalized + .getCanonicalFile()); + } +// +// /** +// * Renames a file, even if that involves crossing file system boundaries. +// * +// *

    +// * This will remove to (if it exists), ensure that to's parent directory exists and move +// * from, which involves deleting from as well.

    +// * +// * @param from the file to move. +// * @param to the new file name. +// * +// * @throws IOException if anything bad happens during this process. Note that to may have been deleted +// * already when this happens. +// * +// * @since Ant 1.6 +// */ +// public void rename(File from, File to) throws IOException { +// // identical logic lives in Move.renameFile(): +// from = normalize(from.getAbsolutePath()).getCanonicalFile(); +// to = normalize(to.getAbsolutePath()); +// if (!from.exists()) { +// System.err.println("Cannot rename nonexistent file " + from); +// return; +// } +// if (from.getAbsolutePath().equals(to.getAbsolutePath())) { +// System.err.println("Rename of " + from + " to " + to + " is a no-op."); +// return; +// } +// if (to.exists() && !(areSame(from, to) || tryHardToDelete(to))) { +// throw new IOException("Failed to delete " + to + " while trying to rename " + from); +// } +// File parent = to.getParentFile(); +// if (parent != null && !parent.isDirectory() +// && !(parent.mkdirs() || parent.isDirectory())) { +// throw new IOException("Failed to create directory " + parent +// + " while trying to rename " + from); +// } +// if (!from.renameTo(to)) { +// copyFile(from, to); +// if (!tryHardToDelete(from)) { +// throw new IOException("Failed to delete " + from + " while trying to rename it."); +// } +// } +// } + + /** + * Get the granularity of file timestamps. The choice is made based on OS, which is incorrect--it should really be + * by filesystem. We do not have an easy way to probe for file systems, however, so this heuristic gives us a decent + * default. + * + * @return the difference, in milliseconds, which two file timestamps must have in order for the two files to be + * considered to have different timestamps. + */ + public long getFileTimestampGranularity() { + if (ON_WIN9X) { + return FAT_FILE_TIMESTAMP_GRANULARITY; + } + if (ON_WINDOWS) { + return NTFS_FILE_TIMESTAMP_GRANULARITY; + } + if (ON_DOS) { + return FAT_FILE_TIMESTAMP_GRANULARITY; + } + return UNIX_FILE_TIMESTAMP_GRANULARITY; + } + + /** + * test whether a file or directory exists, with an error in the upper/lower case spelling of the name. Using this + * method is only interesting on case insensitive file systems (Windows).
    + * It will return true only if 3 conditions are met : + *
    + *
      + *
    • operating system is case insensitive
    • + *
    • file exists
    • + *
    • actual name from directory reading is different from the supplied argument
    • + *
    + *
    + * the purpose is to identify files or directories on case-insensitive filesystems whose case is not what is + * expected.
    + * Possibly to rename them afterwards to the desired upper/lowercase combination. + *
    + * + * @param localFile file to test + * @return true if the file exists and the case of the actual file is not the case of the parameter + * @since Ant 1.7.1 + */ + public boolean hasErrorInCase(File localFile) { + localFile = normalize(localFile.getAbsolutePath()); + if (!localFile.exists()) { + return false; + } + final String localFileName = localFile.getName(); + FilenameFilter ff = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.equalsIgnoreCase(localFileName) && (!name.equals(localFileName)); + } + }; + String[] names = localFile.getParentFile().list(ff); + return names != null && names.length == 1; + } + + /** + * Returns true if the source is older than the dest. If the dest file does not exist, then the test returns false; + * it is implicitly not up do date. + * + * @param source source file (should be the older). + * @param dest dest file (should be the newer). + * @param granularity an offset added to the source time. + * @return true if the source is older than the dest after accounting for granularity. + * @since Ant 1.6.3 + */ + public boolean isUpToDate(File source, File dest, long granularity) { + //do a check for the destination file existing + if (!dest.exists()) { + //if it does not, then the file is not up to date. + return false; + } + long sourceTime = source.lastModified(); + long destTime = dest.lastModified(); + return isUpToDate(sourceTime, destTime, granularity); + } + + /** + * Returns true if the source is older than the dest. + * + * @param source source file (should be the older). + * @param dest dest file (should be the newer). + * @return true if the source is older than the dest, taking the granularity into account. + * @since Ant 1.6.3 + */ + public boolean isUpToDate(File source, File dest) { + return isUpToDate(source, dest, getFileTimestampGranularity()); + } + + /** + * Compare two timestamps for being up to date using the specified granularity. + * + * @param sourceTime timestamp of source file. + * @param destTime timestamp of dest file. + * @param granularity os/filesys granularity. + * @return true if the dest file is considered up to date. + */ + public boolean isUpToDate(long sourceTime, long destTime, long granularity) { + return destTime != -1 && destTime >= sourceTime + granularity; + } + + /** + * Compare two timestamps for being up to date using the current granularity. + * + * @param sourceTime timestamp of source file. + * @param destTime timestamp of dest file. + * @return true if the dest file is considered up to date. + */ + public boolean isUpToDate(long sourceTime, long destTime) { + return isUpToDate(sourceTime, destTime, getFileTimestampGranularity()); + } + + /** + * Close a Writer without throwing any exception if something went wrong. Do not attempt to close it if the argument + * is null. + * + * @param device output writer, can be null. + */ + public static void close(Writer device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a Reader without throwing any exception if something went wrong. Do not attempt to close it if the argument + * is null. + * + * @param device Reader, can be null. + */ + public static void close(Reader device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a stream without throwing any exception if something went wrong. Do not attempt to close it if the argument + * is null. + * + * @param device stream, can be null. + */ + public static void close(OutputStream device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a stream without throwing any exception if something went wrong. Do not attempt to close it if the argument + * is null. + * + * @param device stream, can be null. + */ + public static void close(InputStream device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a Channel without throwing any exception if something went wrong. Do not attempt to close it if the + * argument is null. + * + * @param device channel, can be null. + * @since Ant 1.8.0 + */ + public static void close(Channel device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Closes an URLConnection if its concrete implementation provides a way to close it that Ant knows of. + * + * @param conn connection, can be null + * @since Ant 1.8.0 + */ + public static void close(URLConnection conn) { + if (conn != null) { + try { + if (conn instanceof JarURLConnection) { + JarURLConnection juc = (JarURLConnection) conn; + JarFile jf = juc.getJarFile(); + jf.close(); + jf = null; + } else if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } catch (IOException exc) { + //ignore + } + } + } + + /** + * Delete the file with {@link File#delete()} if the argument is not null. Do nothing on a null argument. + * + * @param file file to delete. + */ + public static void delete(File file) { + if (file != null) { + file.delete(); + } + } + + /** + * Accommodate Windows bug encountered in both Sun and IBM JDKs. Others possible. If the delete does not work, call + * System.gc(), wait a little and try again. + * + * @return whether deletion was successful + * @since Ant 1.8.0 + */ + public boolean tryHardToDelete(File f) { + return tryHardToDelete(f, ON_WINDOWS); + } + + /** + * If delete does not work, call System.gc() if asked to, wait a little and try again. + * + * @return whether deletion was successful + * @since Ant 1.8.3 + */ + public boolean tryHardToDelete(File f, boolean runGC) { + if (!f.delete()) { + if (runGC) { + System.gc(); + } + try { + Thread.sleep(DELETE_RETRY_SLEEP_MILLIS); + } catch (InterruptedException ex) { + // Ignore Exception + } + return f.delete(); + } + return true; + } + + /** + * Calculates the relative path between two files. + *

    + * Implementation note:
    This function may throw an IOException if an I/O error occurs because its use of the + * canonical pathname may require filesystem queries. + *

    + * + * @param fromFile the File to calculate the path from + * @param toFile the File to calculate the path to + * @return the relative path between the files + * @throws Exception for undocumented reasons + * @see File#getCanonicalPath() + * + * @since Ant 1.7 + */ + public static String getRelativePath(File fromFile, File toFile) throws Exception { + String fromPath = fromFile.getCanonicalPath(); + String toPath = toFile.getCanonicalPath(); + + // build the path stack info to compare + String[] fromPathStack = getPathStack(fromPath); + String[] toPathStack = getPathStack(toPath); + + if (0 < toPathStack.length && 0 < fromPathStack.length) { + if (!fromPathStack[0].equals(toPathStack[0])) { + // not the same device (would be "" on Linux/Unix) + + return getPath(Arrays.asList(toPathStack)); + } + } else { + // no comparison possible + return getPath(Arrays.asList(toPathStack)); + } + + int minLength = Math.min(fromPathStack.length, toPathStack.length); + int same = 1; // Used outside the for loop + + // get index of parts which are equal + for (; + same < minLength && fromPathStack[same].equals(toPathStack[same]); + same++) { + // Do nothing + } + + List relativePathStack = new ArrayList(); + + // if "from" part is longer, fill it up with ".." + // to reach path which is equal to both paths + for (int i = same; i < fromPathStack.length; i++) { + relativePathStack.add(".."); + } + + // fill it up path with parts which were not equal + for (int i = same; i < toPathStack.length; i++) { + relativePathStack.add(toPathStack[i]); + } + + return getPath(relativePathStack); + } + + /** + * Gets all names of the path as an array of Strings. + * + * @param path to get names from + * @return Strings, never null + * + * @since Ant 1.7 + */ + public static String[] getPathStack(String path) { + String normalizedPath = path.replace(File.separatorChar, '/'); + + return normalizedPath.split("/"); + } + + /** + * Gets path from a List of Strings. + * + * @param pathStack List of Strings to be concatenated as a path. + * @return String, never null + * + * @since Ant 1.7 + */ + public static String getPath(List pathStack) { + // can safely use '/' because Windows understands '/' as separator + return getPath(pathStack, '/'); + } + + /** + * Gets path from a List of Strings. + * + * @param pathStack List of Strings to be concated as a path. + * @param separatorChar char to be used as separator between names in path + * @return String, never null + * + * @since Ant 1.7 + */ + public static String getPath(final List pathStack, final char separatorChar) { + final StringBuffer buffer = new StringBuffer(); + + final Iterator iter = pathStack.iterator(); + if (iter.hasNext()) { + buffer.append(iter.next()); + } + while (iter.hasNext()) { + buffer.append(separatorChar); + buffer.append(iter.next()); + } + return buffer.toString(); + } + + /** + * Get the default encoding. This is done by opening an InputStreamReader on a dummy InputStream and getting the + * encoding. Could use System.getProperty("file.encoding"), but cannot see where this is documented. + * + * @return the default file encoding. + */ + public String getDefaultEncoding() { + InputStreamReader is = new InputStreamReader( + new InputStream() { + public int read() { + return -1; + } + }); + try { + return is.getEncoding(); + } finally { + close(is); + } + } +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/SymbolicLinkUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/SymbolicLinkUtils.java new file mode 100644 index 000000000..81aae5541 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/SymbolicLinkUtils.java @@ -0,0 +1,291 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +//import org.apache.tools.ant.Task; +//import org.apache.tools.ant.taskdefs.Execute; + +/** + * Contains methods related to symbolic links - or what Ant thinks is a symbolic link based on the absent support for + * them in Java. + * + * @since Ant 1.8.0 + */ +public class SymbolicLinkUtils { + + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Shared instance. + */ + private static final SymbolicLinkUtils PRIMARY_INSTANCE + = new SymbolicLinkUtils(); + + /** + * Method to retrieve The SymbolicLinkUtils, which is shared by all users of this method. + * + * @return an instance of SymbolicLinkUtils. + */ + public static SymbolicLinkUtils getSymbolicLinkUtils() { + // keep the door open for Java X.Y specific subclass if symbolic + // links ever become supported in the classlib + return PRIMARY_INSTANCE; + } + + /** + * Empty constructor. + */ + protected SymbolicLinkUtils() { + } + + /** + * Checks whether a given file is a symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are + * identical--this may lead to false positives on some platforms.

    + * + * @param file the file to test. Must not be null. + * + * @return true if the file is a symbolic link. + * @throws IOException on error. + */ + public boolean isSymbolicLink(File file) throws IOException { + return isSymbolicLink(file.getParentFile(), file.getName()); + } + + /** + * Checks whether a given file is a symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are + * identical--this may lead to false positives on some platforms.

    + * + * @param name the name of the file to test. + * + * @return true if the file is a symbolic link. + * @throws IOException on error. + */ + public boolean isSymbolicLink(String name) throws IOException { + return isSymbolicLink(new File(name)); + } + + /** + * Checks whether a given file is a symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are + * identical--this may lead to false positives on some platforms.

    + * + * @param parent the parent directory of the file to test + * @param name the name of the file to test. + * + * @return true if the file is a symbolic link. + * @throws IOException on error. + */ + public boolean isSymbolicLink(File parent, String name) + throws IOException { + File toTest = parent != null + ? new File(parent.getCanonicalPath(), name) + : new File(name); + return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath()); + } + + /** + * Checks whether a given file is a broken symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether Java reports that the File doesn't exist but its parent's + * child list contains it--this may lead to false positives on some platforms.

    + * + *

    + * Note that #isSymbolicLink returns false if this method returns true since Java won't produce a canonical name + * different from the abolute one if the link is broken.

    + * + * @param name the name of the file to test. + * + * @return true if the file is a broken symbolic link. + * @throws IOException on error. + */ + public boolean isDanglingSymbolicLink(String name) throws IOException { + return isDanglingSymbolicLink(new File(name)); + } + + /** + * Checks whether a given file is a broken symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether Java reports that the File doesn't exist but its parent's + * child list contains it--this may lead to false positives on some platforms.

    + * + *

    + * Note that #isSymbolicLink returns false if this method returns true since Java won't produce a canonical name + * different from the abolute one if the link is broken.

    + * + * @param file the file to test. + * + * @return true if the file is a broken symbolic link. + * @throws IOException on error. + */ + public boolean isDanglingSymbolicLink(File file) throws IOException { + return isDanglingSymbolicLink(file.getParentFile(), file.getName()); + } + + /** + * Checks whether a given file is a broken symbolic link. + * + *

    + * It doesn't really test for symbolic links but whether Java reports that the File doesn't exist but its parent's + * child list contains it--this may lead to false positives on some platforms.

    + * + *

    + * Note that #isSymbolicLink returns false if this method returns true since Java won't produce a canonical name + * different from the abolute one if the link is broken.

    + * + * @param parent the parent directory of the file to test + * @param name the name of the file to test. + * + * @return true if the file is a broken symbolic link. + * @throws IOException on error. + */ + public boolean isDanglingSymbolicLink(File parent, String name) + throws IOException { + File f = new File(parent, name); + if (!f.exists()) { + final String localName = f.getName(); + String[] c = parent.list(new FilenameFilter() { + public boolean accept(File d, String n) { + return localName.equals(n); + } + }); + return c != null && c.length > 0; + } + return false; + } +// +// /** +// * Delete a symlink (without deleting the associated resource). +// * +// *

    This is a utility method that removes a unix symlink without +// * removing the resource that the symlink points to. If it is +// * accidentally invoked on a real file, the real file will not be +// * harmed, but silently ignored.

    +// * +// *

    Normally this method works by +// * getting the canonical path of the link, using the canonical path to +// * rename the resource (breaking the link) and then deleting the link. +// * The resource is then returned to its original name inside a finally +// * block to ensure that the resource is unharmed even in the event of +// * an exception.

    +// * +// *

    There may be cases where the algorithm described above doesn't work, +// * in that case the method tries to use the native "rm" command on +// * the symlink instead.

    +// * +// * @param link A File object of the symlink to delete. +// * @param task An Ant Task required if "rm" needs to be invoked. +// * +// * @throws IOException If calls to File.rename, +// * File.delete or File.getCanonicalPath +// * fail. +// * @throws BuildException if the execution of "rm" failed. +// */ +// public void deleteSymbolicLink(File link, Task task) +// throws IOException { +// if (isDanglingSymbolicLink(link)) { +// if (!link.delete()) { +// throw new IOException("failed to remove dangling symbolic link " +// + link); +// } +// return; +// } +// +// if (!isSymbolicLink(link)) { +// // plain file, not a link +// return; +// } +// +// if (!link.exists()) { +// throw new FileNotFoundException("No such symbolic link: " + link); +// } +// +// // find the resource of the existing link: +// File target = link.getCanonicalFile(); +// +// // no reason to try the renaming algorithm if we aren't allowed to +// // write to the target's parent directory. Let's hope that +// // File.canWrite works on all platforms. +// +// if (task == null || target.getParentFile().canWrite()) { +// +// // rename the resource, thus breaking the link: +// File temp = FILE_UTILS.createTempFile("symlink", ".tmp", +// target.getParentFile(), false, +// false); +// +// if (FILE_UTILS.isLeadingPath(target, link)) { +// // link points to a parent directory, renaming the parent +// // will rename the file +// link = new File(temp, +// FILE_UTILS.removeLeadingPath(target, link)); +// } +// +// boolean renamedTarget = false; +// try { +// try { +// FILE_UTILS.rename(target, temp); +// renamedTarget = true; +// } catch (IOException e) { +// throw new IOException("Couldn't rename resource when " +// + "attempting to delete '" + link +// + "'. Reason: " + e.getMessage()); +// } +// // delete the (now) broken link: +// if (!link.delete()) { +// throw new IOException("Couldn't delete symlink: " +// + link +// + " (was it a real file? is this " +// + "not a UNIX system?)"); +// } +// } finally { +// if (renamedTarget) { +// // return the resource to its original name: +// try { +// FILE_UTILS.rename(temp, target); +// } catch (IOException e) { +// throw new IOException("Couldn't return resource " +// + temp +// + " to its original name: " +// + target.getAbsolutePath() +// + ". Reason: " + e.getMessage() +// + "\n THE RESOURCE'S NAME ON DISK" +// + " HAS BEEN CHANGED BY THIS" +// + " ERROR!\n"); +// } +// } +// } +// } else { +// Execute.runCommand(task, +// new String[] {"rm", link.getAbsolutePath()}); +// } +// } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/VectorSet.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/VectorSet.java new file mode 100644 index 000000000..36617fe00 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/VectorSet.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.util; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; +import java.util.Vector; + +/** + * Subclass of Vector that won't store duplicate entries and shows + * HashSet's constant time performance characteristics for the + * contains method. + * + *

    This is not a general purpose class but has been written because + * the protected members of {@link + * org.apache.tools.ant.DirectoryScanner DirectoryScanner} prohibited + * later revisions from using a more efficient collection.

    + * + *

    Methods are synchronized to keep Vector's contract.

    + * + * @since Ant 1.8.0 + */ +public final class VectorSet extends Vector { + private static final long serialVersionUID = 1L; + + private final HashSet set = new HashSet(); + + public VectorSet() { super(); } + + public VectorSet(int initialCapacity) { super(initialCapacity); } + + public VectorSet(int initialCapacity, int capacityIncrement) { + super(initialCapacity, capacityIncrement); + } + + public VectorSet(Collection c) { + if (c != null) { + for (E e : c) { + add(e); + } + } + } + + public synchronized boolean add(E o) { + if (!set.contains(o)) { + doAdd(size(), o); + return true; + } + return false; + } + + /** + * This implementation may not add the element at the given index + * if it is already contained in the collection. + */ + public void add(int index, E o) { + doAdd(index, o); + } + + private synchronized void doAdd(int index, E o) { + // Vector.add seems to delegate to insertElementAt, but this + // is not documented so we may better implement it ourselves + if (set.add(o)) { + int count = size(); + ensureCapacity(count + 1); + if (index != count) { + System.arraycopy(elementData, index, elementData, index + 1, + count - index); + } + elementData[index] = o; + elementCount++; + } + } + + public synchronized void addElement(E o) { + doAdd(size(), o); + } + + public synchronized boolean addAll(Collection c) { + boolean changed = false; + for (E e : c) { + changed |= add(e); + } + return changed; + } + + /** + * This implementation may not add all elements at the given index + * if any of them are already contained in the collection. + */ + public synchronized boolean addAll(int index, Collection c) { + LinkedList toAdd = new LinkedList(); + for (E e : c) { + if (set.add(e)) { + toAdd.add(e); + } + } + if (toAdd.isEmpty()) { + return false; + } + int count = size(); + ensureCapacity(count + toAdd.size()); + if (index != count) { + System.arraycopy(elementData, index, elementData, index + toAdd.size(), + count - index); + } + for (Object o : toAdd) { + elementData[index++] = o; + } + elementCount += toAdd.size(); + return true; + } + + public synchronized void clear() { + super.clear(); + set.clear(); + } + + public Object clone() { + @SuppressWarnings("unchecked") + final VectorSet vs = (VectorSet) super.clone(); + vs.set.addAll(set); + return vs; + } + + public synchronized boolean contains(Object o) { + return set.contains(o); + } + + public synchronized boolean containsAll(Collection c) { + return set.containsAll(c); + } + + public void insertElementAt(E o, int index) { + doAdd(index, o); + } + + public synchronized E remove(int index) { + E o = get(index); + remove(o); + return o; + } + + public boolean remove(Object o) { + return doRemove(o); + } + + private synchronized boolean doRemove(Object o) { + // again, remove seems to delegate to removeElement, but we + // shouldn't trust it + if (set.remove(o)) { + int index = indexOf(o); + if (index < elementData.length - 1) { + System.arraycopy(elementData, index + 1, elementData, index, + elementData.length - index - 1); + } + elementCount--; + return true; + } + return false; + } + + public synchronized boolean removeAll(Collection c) { + boolean changed = false; + for (Object o : c) { + changed |= remove(o); + } + return changed; + } + + public synchronized void removeAllElements() { + set.clear(); + super.removeAllElements(); + } + + public boolean removeElement(Object o) { + return doRemove(o); + } + + public synchronized void removeElementAt(int index) { + remove(get(index)); + } + + public synchronized void removeRange(final int fromIndex, int toIndex) { + while (toIndex > fromIndex) { + remove(--toIndex); + } + } + + public synchronized boolean retainAll(Collection c) { + if (!(c instanceof Set)) { + c = new HashSet(c); + } + LinkedList l = new LinkedList(); + for (E o : this) { + if (!c.contains(o)) { + l.addLast(o); + } + } + if (!l.isEmpty()) { + removeAll(l); + return true; + } + return false; + } + + public synchronized E set(int index, E o) { + E orig = get(index); + if (set.add(o)) { + elementData[index] = o; + set.remove(orig); + } else { + int oldIndexOfO = indexOf(o); + remove(o); + remove(orig); + add(oldIndexOfO > index ? index : index - 1, o); + } + return orig; + } + + public void setElementAt(E o, int index) { + set(index, o); + } + +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/package-info.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/package-info.java new file mode 100644 index 000000000..963f6d332 --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/org/apache/tools/ant/util/package-info.java @@ -0,0 +1,14 @@ +/** + * + * + * org.owasp.dependencycheck.org.apache.tools.ant.util + * + * + * This is a copy of classes within Apache Ant. The DirectoryScanner + * is needed by dependency-check. However, we did not want to make + * Ant a dependency. As such, a few files were copied and slightly + * modified to remove any references to the Ant Project class. + * + * + */ +package org.owasp.dependencycheck.org.apache.tools.ant.util; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java index 70b988ec8..8fb0cfed0 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java @@ -25,6 +25,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; +import java.security.InvalidAlgorithmParameterException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; @@ -41,6 +42,10 @@ public final class Downloader { * The logger. */ private static final Logger LOGGER = Logger.getLogger(Downloader.class.getName()); + /** + * The maximum number of redirects that will be followed when attempting to download a file. + */ + private static final int MAX_REDIRECT_ATTEMPTS = 5; /** * Private constructor for utility class. @@ -80,19 +85,48 @@ public final class Downloader { try { org.apache.commons.io.FileUtils.copyFile(file, outputPath); } catch (IOException ex) { - final String msg = String.format("Download failed, unable to copy '%s'", url.toString()); + final String msg = String.format("Download failed, unable to copy '%s' to '%s'", url.toString(), outputPath.getAbsolutePath()); throw new DownloadFailedException(msg); } } else { - final String msg = String.format("Download failed, file does not exist '%s'", url.toString()); + final String msg = String.format("Download failed, file ('%s') does not exist", url.toString()); throw new DownloadFailedException(msg); } } else { HttpURLConnection conn = null; try { + LOGGER.fine(String.format("Attempting download of %s", url.toString())); conn = URLConnectionFactory.createHttpURLConnection(url, useProxy); conn.setRequestProperty("Accept-Encoding", "gzip, deflate"); conn.connect(); + int status = conn.getResponseCode(); + int redirectCount = 0; + while ((status == HttpURLConnection.HTTP_MOVED_TEMP + || status == HttpURLConnection.HTTP_MOVED_PERM + || status == HttpURLConnection.HTTP_SEE_OTHER) + && MAX_REDIRECT_ATTEMPTS > redirectCount++) { + final String location = conn.getHeaderField("Location"); + try { + conn.disconnect(); + } finally { + conn = null; + } + LOGGER.fine(String.format("Download is being redirected from %s to %s", url.toString(), location)); + conn = URLConnectionFactory.createHttpURLConnection(new URL(location), useProxy); + conn.setRequestProperty("Accept-Encoding", "gzip, deflate"); + conn.connect(); + status = conn.getResponseCode(); + } + if (status != 200) { + try { + conn.disconnect(); + } finally { + conn = null; + } + final String msg = String.format("Error downloading file %s; received response code %s.", url.toString(), status); + throw new DownloadFailedException(msg); + + } } catch (IOException ex) { try { if (conn != null) { @@ -101,10 +135,11 @@ public final class Downloader { } finally { conn = null; } - throw new DownloadFailedException("Error downloading file.", ex); + final String msg = String.format("Error downloading file %s; unable to connect.", url.toString()); + throw new DownloadFailedException(msg, ex); } - final String encoding = conn.getContentEncoding(); + final String encoding = conn.getContentEncoding(); BufferedOutputStream writer = null; InputStream reader = null; try { @@ -122,23 +157,29 @@ public final class Downloader { while ((bytesRead = reader.read(buffer)) > 0) { writer.write(buffer, 0, bytesRead); } + LOGGER.fine(String.format("Download of %s complete", url.toString())); + } catch (IOException ex) { + analyzeException(ex); + final String msg = String.format("Error saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n", + url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding); + throw new DownloadFailedException(msg, ex); } catch (Throwable ex) { - throw new DownloadFailedException("Error saving downloaded file.", ex); + final String msg = String.format("Unexpected exception saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n", + url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding); + throw new DownloadFailedException(msg, ex); } finally { if (writer != null) { try { writer.close(); - } catch (Throwable ex) { - LOGGER.log(Level.FINEST, - "Error closing the writer in Downloader.", ex); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "Error closing the writer in Downloader.", ex); } } if (reader != null) { try { reader.close(); - } catch (Throwable ex) { - LOGGER.log(Level.FINEST, - "Error closing the reader in Downloader.", ex); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "Error closing the reader in Downloader.", ex); } } try { @@ -151,8 +192,8 @@ public final class Downloader { } /** - * Makes an HTTP Head request to retrieve the last modified date of the given URL. If the file:// protocol is - * specified, then the lastTimestamp of the file is returned. + * Makes an HTTP Head request to retrieve the last modified date of the given URL. If the file:// protocol is specified, then + * the lastTimestamp of the file is returned. * * @param url the URL to retrieve the timestamp from * @return an epoch timestamp @@ -185,6 +226,7 @@ public final class Downloader { } catch (URLConnectionFailureException ex) { throw new DownloadFailedException("Error creating URL Connection for HTTP HEAD request.", ex); } catch (IOException ex) { + analyzeException(ex); throw new DownloadFailedException("Error making HTTP HEAD request.", ex); } finally { if (conn != null) { @@ -198,4 +240,29 @@ public final class Downloader { } return timestamp; } + + /** + * Analyzes the IOException, logs the appropriate information for debugging purposes, and then throws a + * DownloadFailedException that wraps the IO Exception. + * + * @param ex the original exception + * @throws DownloadFailedException a wrapper exception that contains the original exception as the cause + */ + protected static void analyzeException(IOException ex) throws DownloadFailedException { + Throwable cause = ex; + while (cause != null) { + if (cause instanceof InvalidAlgorithmParameterException) { + final String keystore = System.getProperty("javax.net.ssl.keyStore"); + final String version = System.getProperty("java.version"); + final String vendor = System.getProperty("java.vendor"); + LOGGER.info("Error making HTTPS request - InvalidAlgorithmParameterException"); + LOGGER.info("There appears to be an issue with the installation of Java and the cacerts." + + "See closed issue #177 here: https://github.com/jeremylong/DependencyCheck/issues/177"); + LOGGER.info(String.format("Java Info:%njavax.net.ssl.keyStore='%s'%njava.version='%s'%njava.vendor='%s'", + keystore, version, vendor)); + throw new DownloadFailedException("Error making HTTPS request. Please see the log for more details."); + } + cause = cause.getCause(); + } + } } diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java index 34727e9b1..2ebe930de 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java @@ -78,7 +78,7 @@ public final class FileUtils { if (!org.apache.commons.io.FileUtils.deleteQuietly(file)) { success = false; final String msg = String.format("Failed to delete file: %s; attempting to delete on exit.", file.getPath()); - LOGGER.log(Level.FINE, msg); + LOGGER.log(Level.INFO, msg); file.deleteOnExit(); } return success; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/LogUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/LogUtils.java index 99c5ddbb0..af4b82fed 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/LogUtils.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/LogUtils.java @@ -17,6 +17,7 @@ */ package org.owasp.dependencycheck.utils; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.logging.FileHandler; @@ -57,6 +58,13 @@ public final class LogUtils { if (verboseLogFile != null && !verboseLogFile.isEmpty()) { verboseLoggingEnabled = true; final Logger logger = Logger.getLogger(""); + final File logFile = new File(verboseLogFile); + final File logDir = logFile.getParentFile(); + if (logDir != null && !logDir.isDirectory() && !logDir.mkdirs()) { + final String msg = String.format("Unable to create directory '%s', verbose logging will be disabled.", + logDir.getAbsolutePath()); + throw new IOException(msg); + } final FileHandler fileHandler = new FileHandler(verboseLogFile, true); fileHandler.setFormatter(new SimpleFormatter()); fileHandler.setLevel(Level.FINE); @@ -75,7 +83,7 @@ public final class LogUtils { if (in != null) { try { in.close(); - } catch (Throwable ex) { + } catch (IOException ex) { LOGGER.log(Level.FINEST, "Error closing resource stream", ex); } } diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java index 7ff76b16a..40c495c20 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -50,6 +50,18 @@ public final class Settings { private KEYS() { //do nothing } + /** + * The key to obtain the application name. + */ + public static final String APPLICATION_VAME = "application.name"; + /** + * The key to obtain the application version. + */ + public static final String APPLICATION_VERSION = "application.version"; + /** + * The key to obtain the URL to retrieve the current release version from. + */ + public static final String ENGINE_VERSION_CHECK_URL = "engine.version.url"; /** * The properties key indicating whether or not the cached data sources should be updated. */ @@ -91,13 +103,13 @@ public final class Settings { */ public static final String CVE_META_URL = "cve.url.meta"; /** - * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days) using - * the 2.0 schema. + * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days) using the 2.0 + * schema. */ public static final String CVE_MODIFIED_20_URL = "cve.url-2.0.modified"; /** - * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days) using - * the 1.2 schema. + * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days) using the 1.2 + * schema. */ public static final String CVE_MODIFIED_12_URL = "cve.url-1.2.modified"; /** @@ -105,8 +117,8 @@ public final class Settings { */ public static final String CVE_MODIFIED_VALID_FOR_DAYS = "cve.url.modified.validfordays"; /** - * The properties key for the telling us how many cve.url.* URLs exists. This is used in combination with - * CVE_BASE_URL to be able to retrieve the URLs for all of the files that make up the NVD CVE listing. + * The properties key for the telling us how many cve.url.* URLs exists. This is used in combination with CVE_BASE_URL to + * be able to retrieve the URLs for all of the files that make up the NVD CVE listing. */ public static final String CVE_START_YEAR = "cve.startyear"; /** @@ -188,6 +200,14 @@ public final class Settings { * The properties key for using the proxy to reach Nexus. */ public static final String ANALYZER_NEXUS_PROXY = "analyzer.nexus.proxy"; + /** + * The properties key for whether the Central analyzer is enabled. + */ + public static final String ANALYZER_CENTRAL_ENABLED = "analyzer.central.enabled"; + /** + * The properties key for the Central search URL. + */ + public static final String ANALYZER_CENTRAL_URL = "analyzer.central.url"; /** * The path to mono, if available. */ @@ -277,16 +297,16 @@ public final class Settings { } /** - * Initializes the thread local settings object. Note, to use the settings object you must call this method. - * However, you must also call Settings.cleanup() to properly release resources. + * Initializes the thread local settings object. Note, to use the settings object you must call this method. However, you must + * also call Settings.cleanup() to properly release resources. */ public static void initialize() { localSettings.set(new Settings(PROPERTIES_FILE)); } /** - * Initializes the thread local settings object. Note, to use the settings object you must call this method. - * However, you must also call Settings.cleanup() to properly release resources. + * Initializes the thread local settings object. Note, to use the settings object you must call this method. However, you must + * also call Settings.cleanup() to properly release resources. * * @param propertiesFilePath the path to the base properties file to load */ @@ -403,8 +423,8 @@ public final class Settings { } /** - * Merges a new properties file into the current properties. This method allows for the loading of a user provided - * properties file.

    + * Merges a new properties file into the current properties. This method allows for the loading of a user provided properties + * file.

    * Note: even if using this method - system properties will be loaded before properties loaded from files. * * @param filePath the path to the properties file to merge. @@ -428,8 +448,8 @@ public final class Settings { } /** - * Merges a new properties file into the current properties. This method allows for the loading of a user provided - * properties file.

    + * Merges a new properties file into the current properties. This method allows for the loading of a user provided properties + * file.

    * Note: even if using this method - system properties will be loaded before properties loaded from files. * * @param filePath the path to the properties file to merge. @@ -453,8 +473,8 @@ public final class Settings { } /** - * Merges a new properties file into the current properties. This method allows for the loading of a user provided - * properties file.

    + * Merges a new properties file into the current properties. This method allows for the loading of a user provided properties + * file.

    * Note: even if using this method - system properties will be loaded before properties loaded from files. * * @param stream an Input Stream pointing at a properties file to merge @@ -466,9 +486,9 @@ public final class Settings { } /** - * Returns a value from the properties file as a File object. If the value was specified as a system property or - * passed in via the -Dprop=value argument - this method will return the value from the system properties before the - * values in the contained configuration file. + * Returns a value from the properties file as a File object. If the value was specified as a system property or passed in via + * the -Dprop=value argument - this method will return the value from the system properties before the values in the contained + * configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file converted to a File object @@ -482,13 +502,13 @@ public final class Settings { } /** - * Returns a value from the properties file as a File object. If the value was specified as a system property or - * passed in via the -Dprop=value argument - this method will return the value from the system properties before the - * values in the contained configuration file. + * Returns a value from the properties file as a File object. If the value was specified as a system property or passed in via + * the -Dprop=value argument - this method will return the value from the system properties before the values in the contained + * configuration file. * - * This method will check the configured base directory and will use this as the base of the file path. - * Additionally, if the base directory begins with a leading "[JAR]\" sequence with the path to the folder - * containing the JAR file containing this class. + * This method will check the configured base directory and will use this as the base of the file path. Additionally, if the + * base directory begins with a leading "[JAR]\" sequence with the path to the folder containing the JAR file containing this + * class. * * @param key the key to lookup within the properties file * @return the property from the properties file converted to a File object @@ -533,9 +553,9 @@ public final class Settings { } /** - * Returns a value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument - this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns a value from the properties file. If the value was specified as a system property or passed in via the -Dprop=value + * argument - this method will return the value from the system properties before the values in the contained configuration + * file. * * @param key the key to lookup within the properties file * @param defaultValue the default value for the requested property @@ -571,9 +591,9 @@ public final class Settings { } /** - * Returns a value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument - this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns a value from the properties file. If the value was specified as a system property or passed in via the -Dprop=value + * argument - this method will return the value from the system properties before the values in the contained configuration + * file. * * @param key the key to lookup within the properties file * @return the property from the properties file @@ -592,9 +612,9 @@ public final class Settings { } /** - * Returns an int value from the properties file. If the value was specified as a system property or passed in via - * the -Dprop=value argument - this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns an int value from the properties file. If the value was specified as a system property or passed in via the + * -Dprop=value argument - this method will return the value from the system properties before the values in the contained + * configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file @@ -611,14 +631,14 @@ public final class Settings { } /** - * Returns an int value from the properties file. If the value was specified as a system property or passed in via - * the -Dprop=value argument - this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns an int value from the properties file. If the value was specified as a system property or passed in via the + * -Dprop=value argument - this method will return the value from the system properties before the values in the contained + * configuration file. * * @param key the key to lookup within the properties file * @param defaultValue the default value to return - * @return the property from the properties file or the defaultValue if the property does not exist or cannot be - * converted to an integer + * @return the property from the properties file or the defaultValue if the property does not exist or cannot be converted to + * an integer */ public static int getInt(String key, int defaultValue) { int value; @@ -633,9 +653,9 @@ public final class Settings { } /** - * Returns a long value from the properties file. If the value was specified as a system property or passed in via - * the -Dprop=value argument - this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns a long value from the properties file. If the value was specified as a system property or passed in via the + * -Dprop=value argument - this method will return the value from the system properties before the values in the contained + * configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file @@ -652,9 +672,9 @@ public final class Settings { } /** - * Returns a boolean value from the properties file. If the value was specified as a system property or passed in - * via the -Dprop=value argument this method will return the value from the system properties before - * the values in the contained configuration file. + * Returns a boolean value from the properties file. If the value was specified as a system property or passed in via the + * -Dprop=value argument this method will return the value from the system properties before the values in the + * contained configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file @@ -671,9 +691,9 @@ public final class Settings { } /** - * Returns a boolean value from the properties file. If the value was specified as a system property or passed in - * via the -Dprop=value argument this method will return the value from the system properties before - * the values in the contained configuration file. + * Returns a boolean value from the properties file. If the value was specified as a system property or passed in via the + * -Dprop=value argument this method will return the value from the system properties before the values in the + * contained configuration file. * * @param key the key to lookup within the properties file * @param defaultValue the default value to return if the setting does not exist @@ -695,9 +715,9 @@ public final class Settings { } /** - * Returns a connection string from the configured properties. If the connection string contains a %s, this method - * will determine the 'data' directory and replace the %s with the path to the data directory. If the data directory - * does not exists it will be created. + * Returns a connection string from the configured properties. If the connection string contains a %s, this method will + * determine the 'data' directory and replace the %s with the path to the data directory. If the data directory does not + * exists it will be created. * * @param connectionStringKey the property file key for the connection string * @param dbFileNameKey the settings key for the db filename @@ -750,8 +770,8 @@ public final class Settings { } /** - * Retrieves the directory that the JAR file exists in so that we can ensure we always use a common data directory - * for the embedded H2 database. This is public solely for some unit tests; otherwise this should be private. + * Retrieves the directory that the JAR file exists in so that we can ensure we always use a common data directory for the + * embedded H2 database. This is public solely for some unit tests; otherwise this should be private. * * @return the data directory to store data files * @throws IOException is thrown if an IOException occurs of course... diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java index 06ddf1ddf..b0b40ece8 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java @@ -27,8 +27,8 @@ import java.net.SocketAddress; import java.net.URL; /** - * A URLConnection Factory to create new connections. This encapsulates several configuration checks to ensure that the - * connection uses the correct proxy settings. + * A URLConnection Factory to create new connections. This encapsulates several configuration checks to ensure that the connection + * uses the correct proxy settings. * * @author Jeremy Long */ @@ -41,8 +41,8 @@ public final class URLConnectionFactory { } /** - * Utility method to create an HttpURLConnection. If the application is configured to use a proxy this method will - * retrieve the proxy settings and use them when setting up the connection. + * Utility method to create an HttpURLConnection. If the application is configured to use a proxy this method will retrieve + * the proxy settings and use them when setting up the connection. * * @param url the url to connect to * @return an HttpURLConnection @@ -79,6 +79,7 @@ public final class URLConnectionFactory { } final int timeout = Settings.getInt(Settings.KEYS.CONNECTION_TIMEOUT, 60000); conn.setConnectTimeout(timeout); + conn.setInstanceFollowRedirects(true); } catch (IOException ex) { if (conn != null) { try { @@ -93,8 +94,8 @@ public final class URLConnectionFactory { } /** - * Utility method to create an HttpURLConnection. The use of a proxy here is optional as there may be cases where a - * proxy is configured but we don't want to use it (for example, if there's an internal repository configured) + * Utility method to create an HttpURLConnection. The use of a proxy here is optional as there may be cases where a proxy is + * configured but we don't want to use it (for example, if there's an internal repository configured) * * @param url the URL to connect to * @param proxy whether to use the proxy (if configured) @@ -110,6 +111,7 @@ public final class URLConnectionFactory { conn = (HttpURLConnection) url.openConnection(); final int timeout = Settings.getInt(Settings.KEYS.CONNECTION_TIMEOUT, 60000); conn.setConnectTimeout(timeout); + conn.setInstanceFollowRedirects(true); } catch (IOException ioe) { throw new URLConnectionFailureException("Error getting connection.", ioe); } diff --git a/dependency-check-utils/src/test/java/org/owasp/dependencycheck/org/apache/tools/ant/DirectoryScannerTest.java b/dependency-check-utils/src/test/java/org/owasp/dependencycheck/org/apache/tools/ant/DirectoryScannerTest.java new file mode 100644 index 000000000..99d533e31 --- /dev/null +++ b/dependency-check-utils/src/test/java/org/owasp/dependencycheck/org/apache/tools/ant/DirectoryScannerTest.java @@ -0,0 +1,732 @@ +/* + * Copyright 2014 OWASP. + * + * 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. + */ +package org.owasp.dependencycheck.org.apache.tools.ant; + +import org.owasp.dependencycheck.org.apache.tools.ant.DirectoryScanner; +import java.io.File; +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author Jeremy Long + */ +public class DirectoryScannerTest { + + public DirectoryScannerTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Testing the expected use of the directory scanner. + */ + @Test + public void testExpectedUse() { + DirectoryScanner scanner = new DirectoryScanner(); + scanner.setBasedir("./target"); + scanner.setIncludes("/test-classes/**"); + scanner.scan(); + + File base = new File("./target"); + for (String t : scanner.getIncludedFiles()) { + assertTrue(t.startsWith("test-classes")); + File test = new File(base, t); + assertTrue(test.exists()); + } + } + + /** + * Test of matchPatternStart method, of class DirectoryScanner. + */ + @Test + public void testMatchPatternStart_String_String() { + String pattern = "alpha/be?a/**"; + String str = "alpha/beta/gamma/"; + boolean expResult = true; + boolean result = DirectoryScanner.matchPatternStart(pattern, str); + assertEquals(expResult, result); + } + + /** + * Test of matchPatternStart method, of class DirectoryScanner. + */ + @Test + public void testMatchPatternStart_3args() { + String pattern = "Alpha/be?a/**"; + String str = "alpha/beta/gamma/"; + boolean isCaseSensitive = true; + boolean expResult = false; + boolean result = DirectoryScanner.matchPatternStart(pattern, str, isCaseSensitive); + assertEquals(expResult, result); + + isCaseSensitive = false; + expResult = true; + result = DirectoryScanner.matchPatternStart(pattern, str, isCaseSensitive); + assertEquals(expResult, result); + } + + /** + * Test of matchPath method, of class DirectoryScanner. + */ + @Test + public void testMatchPath_String_String() { + String pattern = "alpha/be?a/**"; + String str = "alpha/beta/gamma/"; + boolean expResult = true; + boolean result = DirectoryScanner.matchPath(pattern, str); + assertEquals(expResult, result); + } +// +// /** +// * Test of matchPath method, of class DirectoryScanner. +// */ +// @Test +// public void testMatchPath_3args() { +// System.out.println("matchPath"); +// String pattern = ""; +// String str = ""; +// boolean isCaseSensitive = false; +// boolean expResult = false; +// boolean result = DirectoryScanner.matchPath(pattern, str, isCaseSensitive); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of match method, of class DirectoryScanner. +// */ +// @Test +// public void testMatch_String_String() { +// System.out.println("match"); +// String pattern = ""; +// String str = ""; +// boolean expResult = false; +// boolean result = DirectoryScanner.match(pattern, str); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of match method, of class DirectoryScanner. +// */ +// @Test +// public void testMatch_3args() { +// System.out.println("match"); +// String pattern = ""; +// String str = ""; +// boolean isCaseSensitive = false; +// boolean expResult = false; +// boolean result = DirectoryScanner.match(pattern, str, isCaseSensitive); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getDefaultExcludes method, of class DirectoryScanner. +// */ +// @Test +// public void testGetDefaultExcludes() { +// System.out.println("getDefaultExcludes"); +// String[] expResult = null; +// String[] result = DirectoryScanner.getDefaultExcludes(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of addDefaultExclude method, of class DirectoryScanner. +// */ +// @Test +// public void testAddDefaultExclude() { +// System.out.println("addDefaultExclude"); +// String s = ""; +// boolean expResult = false; +// boolean result = DirectoryScanner.addDefaultExclude(s); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of removeDefaultExclude method, of class DirectoryScanner. +// */ +// @Test +// public void testRemoveDefaultExclude() { +// System.out.println("removeDefaultExclude"); +// String s = ""; +// boolean expResult = false; +// boolean result = DirectoryScanner.removeDefaultExclude(s); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of resetDefaultExcludes method, of class DirectoryScanner. +// */ +// @Test +// public void testResetDefaultExcludes() { +// System.out.println("resetDefaultExcludes"); +// DirectoryScanner.resetDefaultExcludes(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setBasedir method, of class DirectoryScanner. +// */ +// @Test +// public void testSetBasedir_String() { +// System.out.println("setBasedir"); +// String basedir = ""; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setBasedir(basedir); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setBasedir method, of class DirectoryScanner. +// */ +// @Test +// public void testSetBasedir_File() { +// System.out.println("setBasedir"); +// File basedir = null; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setBasedir(basedir); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getBasedir method, of class DirectoryScanner. +// */ +// @Test +// public void testGetBasedir() { +// System.out.println("getBasedir"); +// DirectoryScanner instance = new DirectoryScanner(); +// File expResult = null; +// File result = instance.getBasedir(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isCaseSensitive method, of class DirectoryScanner. +// */ +// @Test +// public void testIsCaseSensitive() { +// System.out.println("isCaseSensitive"); +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.isCaseSensitive(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setCaseSensitive method, of class DirectoryScanner. +// */ +// @Test +// public void testSetCaseSensitive() { +// System.out.println("setCaseSensitive"); +// boolean isCaseSensitive = false; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setCaseSensitive(isCaseSensitive); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setErrorOnMissingDir method, of class DirectoryScanner. +// */ +// @Test +// public void testSetErrorOnMissingDir() { +// System.out.println("setErrorOnMissingDir"); +// boolean errorOnMissingDir = false; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setErrorOnMissingDir(errorOnMissingDir); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isFollowSymlinks method, of class DirectoryScanner. +// */ +// @Test +// public void testIsFollowSymlinks() { +// System.out.println("isFollowSymlinks"); +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.isFollowSymlinks(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setFollowSymlinks method, of class DirectoryScanner. +// */ +// @Test +// public void testSetFollowSymlinks() { +// System.out.println("setFollowSymlinks"); +// boolean followSymlinks = false; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setFollowSymlinks(followSymlinks); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setMaxLevelsOfSymlinks method, of class DirectoryScanner. +// */ +// @Test +// public void testSetMaxLevelsOfSymlinks() { +// System.out.println("setMaxLevelsOfSymlinks"); +// int max = 0; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setMaxLevelsOfSymlinks(max); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setIncludes method, of class DirectoryScanner. +// */ +// @Test +// public void testSetIncludes() { +// System.out.println("setIncludes"); +// String[] includes = null; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setIncludes(includes); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setExcludes method, of class DirectoryScanner. +// */ +// @Test +// public void testSetExcludes() { +// System.out.println("setExcludes"); +// String[] excludes = null; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setExcludes(excludes); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of addExcludes method, of class DirectoryScanner. +// */ +// @Test +// public void testAddExcludes() { +// System.out.println("addExcludes"); +// String[] excludes = null; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.addExcludes(excludes); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setSelectors method, of class DirectoryScanner. +// */ +// @Test +// public void testSetSelectors() { +// System.out.println("setSelectors"); +// FileSelector[] selectors = null; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.setSelectors(selectors); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isEverythingIncluded method, of class DirectoryScanner. +// */ +// @Test +// public void testIsEverythingIncluded() { +// System.out.println("isEverythingIncluded"); +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.isEverythingIncluded(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of scan method, of class DirectoryScanner. +// */ +// @Test +// public void testScan() { +// System.out.println("scan"); +// DirectoryScanner instance = new DirectoryScanner(); +// instance.scan(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of clearResults method, of class DirectoryScanner. +// */ +// @Test +// public void testClearResults() { +// System.out.println("clearResults"); +// DirectoryScanner instance = new DirectoryScanner(); +// instance.clearResults(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of slowScan method, of class DirectoryScanner. +// */ +// @Test +// public void testSlowScan() { +// System.out.println("slowScan"); +// DirectoryScanner instance = new DirectoryScanner(); +// instance.slowScan(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of scandir method, of class DirectoryScanner. +// */ +// @Test +// public void testScandir() { +// System.out.println("scandir"); +// File dir = null; +// String vpath = ""; +// boolean fast = false; +// DirectoryScanner instance = new DirectoryScanner(); +// instance.scandir(dir, vpath, fast); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isIncluded method, of class DirectoryScanner. +// */ +// @Test +// public void testIsIncluded() { +// System.out.println("isIncluded"); +// String name = ""; +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.isIncluded(name); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of couldHoldIncluded method, of class DirectoryScanner. +// */ +// @Test +// public void testCouldHoldIncluded() { +// System.out.println("couldHoldIncluded"); +// String name = ""; +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.couldHoldIncluded(name); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of contentsExcluded method, of class DirectoryScanner. +// */ +// @Test +// public void testContentsExcluded() { +// System.out.println("contentsExcluded"); +// TokenizedPath path = null; +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.contentsExcluded(path); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isExcluded method, of class DirectoryScanner. +// */ +// @Test +// public void testIsExcluded() { +// System.out.println("isExcluded"); +// String name = ""; +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.isExcluded(name); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isSelected method, of class DirectoryScanner. +// */ +// @Test +// public void testIsSelected() { +// System.out.println("isSelected"); +// String name = ""; +// File file = null; +// DirectoryScanner instance = new DirectoryScanner(); +// boolean expResult = false; +// boolean result = instance.isSelected(name, file); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getIncludedFiles method, of class DirectoryScanner. +// */ +// @Test +// public void testGetIncludedFiles() { +// System.out.println("getIncludedFiles"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getIncludedFiles(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getIncludedFilesCount method, of class DirectoryScanner. +// */ +// @Test +// public void testGetIncludedFilesCount() { +// System.out.println("getIncludedFilesCount"); +// DirectoryScanner instance = new DirectoryScanner(); +// int expResult = 0; +// int result = instance.getIncludedFilesCount(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getNotIncludedFiles method, of class DirectoryScanner. +// */ +// @Test +// public void testGetNotIncludedFiles() { +// System.out.println("getNotIncludedFiles"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getNotIncludedFiles(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getExcludedFiles method, of class DirectoryScanner. +// */ +// @Test +// public void testGetExcludedFiles() { +// System.out.println("getExcludedFiles"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getExcludedFiles(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getDeselectedFiles method, of class DirectoryScanner. +// */ +// @Test +// public void testGetDeselectedFiles() { +// System.out.println("getDeselectedFiles"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getDeselectedFiles(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getIncludedDirectories method, of class DirectoryScanner. +// */ +// @Test +// public void testGetIncludedDirectories() { +// System.out.println("getIncludedDirectories"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getIncludedDirectories(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getIncludedDirsCount method, of class DirectoryScanner. +// */ +// @Test +// public void testGetIncludedDirsCount() { +// System.out.println("getIncludedDirsCount"); +// DirectoryScanner instance = new DirectoryScanner(); +// int expResult = 0; +// int result = instance.getIncludedDirsCount(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getNotIncludedDirectories method, of class DirectoryScanner. +// */ +// @Test +// public void testGetNotIncludedDirectories() { +// System.out.println("getNotIncludedDirectories"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getNotIncludedDirectories(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getExcludedDirectories method, of class DirectoryScanner. +// */ +// @Test +// public void testGetExcludedDirectories() { +// System.out.println("getExcludedDirectories"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getExcludedDirectories(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getDeselectedDirectories method, of class DirectoryScanner. +// */ +// @Test +// public void testGetDeselectedDirectories() { +// System.out.println("getDeselectedDirectories"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getDeselectedDirectories(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getNotFollowedSymlinks method, of class DirectoryScanner. +// */ +// @Test +// public void testGetNotFollowedSymlinks() { +// System.out.println("getNotFollowedSymlinks"); +// DirectoryScanner instance = new DirectoryScanner(); +// String[] expResult = null; +// String[] result = instance.getNotFollowedSymlinks(); +// assertArrayEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of addDefaultExcludes method, of class DirectoryScanner. +// */ +// @Test +// public void testAddDefaultExcludes() { +// System.out.println("addDefaultExcludes"); +// DirectoryScanner instance = new DirectoryScanner(); +// instance.addDefaultExcludes(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getResource method, of class DirectoryScanner. +// */ +// @Test +// public void testGetResource() { +// System.out.println("getResource"); +// String name = ""; +// DirectoryScanner instance = new DirectoryScanner(); +// Resource expResult = null; +// Resource result = instance.getResource(name); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of getScannedDirs method, of class DirectoryScanner. +// */ +// @Test +// public void testGetScannedDirs() { +// System.out.println("getScannedDirs"); +// DirectoryScanner instance = new DirectoryScanner(); +// Set expResult = null; +// Set result = instance.getScannedDirs(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of ensureNonPatternSetsReady method, of class DirectoryScanner. +// */ +// @Test +// public void testEnsureNonPatternSetsReady() { +// System.out.println("ensureNonPatternSetsReady"); +// DirectoryScanner instance = new DirectoryScanner(); +// instance.ensureNonPatternSetsReady(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } + +} diff --git a/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/DownloaderIntegrationTest.java b/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/DownloaderIntegrationTest.java index 530a8faae..b84b49257 100644 --- a/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/DownloaderIntegrationTest.java +++ b/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/DownloaderIntegrationTest.java @@ -42,12 +42,12 @@ public class DownloaderIntegrationTest extends BaseTest { URL url = new URL(Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL)); File outputPath = new File("target/downloaded_cve.xml"); Downloader.fetchFile(url, outputPath); - + assertTrue(outputPath.isFile()); } @Test public void testGetLastModified() throws Exception { - URL url = new URL("http://nvd.nist.gov/download/nvdcve-2012.xml"); + URL url = new URL(Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL)); long timestamp = Downloader.getLastModified(url); assertTrue("timestamp equal to zero?", timestamp > 0); } diff --git a/dependency-check-utils/src/test/resources/dependencycheck.properties b/dependency-check-utils/src/test/resources/dependencycheck.properties index 8ed80a630..2b9b6fcf2 100644 --- a/dependency-check-utils/src/test/resources/dependencycheck.properties +++ b/dependency-check-utils/src/test/resources/dependencycheck.properties @@ -3,6 +3,9 @@ application.version=${pom.version} autoupdate=true max.download.threads=3 +# the url to obtain the current engine version from +engine.version.url=http://jeremylong.github.io/DependencyCheck/current.txt + #temp.directory defaults to System.getProperty("java.io.tmpdir") #temp.directory=[path to temp directory] @@ -45,11 +48,15 @@ cpe.meta.url=http://static.nvd.nist.gov/feeds/xml/cpe/dictionary/official-cpe-di cve.url.modified.validfordays=7 # the path to the modified nvd cve xml file. -cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml -cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml cve.startyear=2014 -cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml -cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml +cve.url-1.2.modified=https://nvd.nist.gov/download/nvdcve-Modified.xml.gz +#cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml +cve.url-2.0.modified=https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-Modified.xml.gz +#cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml +cve.url-1.2.base=https://nvd.nist.gov/download/nvdcve-%d.xml.gz +#cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml +cve.url-2.0.base=https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml.gz +#cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml # the URL for searching Nexus for SHA-1 hashes and whether it's enabled analyzer.nexus.enabled=true diff --git a/pom.xml b/pom.xml index eb48cde22..1ca26b660 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long org.owasp dependency-check-parent - 1.2.6-SNAPSHOT + 1.2.10-SNAPSHOT pom @@ -125,33 +125,189 @@ Copyright (c) 2012 - Jeremy Long + + 3.0 + + + + + org.codehaus.mojo + appassembler-maven-plugin + 1.9 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-assembly-plugin + 2.5.3 + + + org.apache.maven.plugins + maven-clean-plugin + 2.6.1 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + org.apache.maven.plugins + maven-dependency-plugin + 2.9 + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.3.1 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.18.1 + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + org.apache.maven.plugins + maven-install-plugin + 2.5.2 + + + org.apache.maven.plugins + maven-jar-plugin + 2.5 + + + org.apache.maven.plugins + maven-plugin-plugin + 3.3 + + + org.apache.maven.plugins + maven-release-plugin + 2.5.1 + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + org.apache.maven.plugins + maven-site-plugin + + 3.3 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18.1 + + + com.github.github + site-maven-plugin + 0.10 + + + org.apache.maven.plugins maven-release-plugin - 2.4.2 org.apache.maven.plugins maven-compiler-plugin - 3.1 + false 1.6 1.6 + + org.apache.maven.plugins + maven-failsafe-plugin + + + + temp.directory + ${project.build.directory}/temp + + + + **/*IntegrationTest.java + + + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + **/checkstyle* + + + + + org.apache.maven.plugins + maven-resources-plugin + + + site-filtering-hack + pre-site + + copy-resources + + false + + ${project.build.directory}/site/ + + + src/main/site-resources/ + true + + + UTF-8 + + + + org.apache.maven.plugins maven-site-plugin - 3.3 - org.apache.maven.doxia doxia-module-markdown 1.5 @@ -159,42 +315,11 @@ Copyright (c) 2012 - Jeremy Long true - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.7 - - - - - index - summary - mailing-list - issue-tracking - modules - project-team - scm - license - help - - - - - com.github.github site-maven-plugin - 0.9 Creating site for ${project.artifactId}, ${project.version} @@ -212,29 +337,71 @@ Copyright (c) 2012 - Jeremy Long - + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.7 + + + + + index + summary + mailing-list + issue-tracking + modules + project-team + scm + license + + + + + + + + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.jmockit + jmockit + 1.15 + test + + + junit junit - 4.11 test - jar + + + org.hamcrest + hamcrest-core + test - \ No newline at end of file + diff --git a/src/main/config/checkstyle-checks.xml b/src/main/config/checkstyle-checks.xml index 7f316c27a..876432880 100644 --- a/src/main/config/checkstyle-checks.xml +++ b/src/main/config/checkstyle-checks.xml @@ -9,215 +9,215 @@ names will be relative to the specified directory. See http://checkstyle.sourceforge.net/5.x/config.html#Checker - - --> + + --> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - + + - - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - + + - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/config/checkstyle-header.txt b/src/main/config/checkstyle-header.txt index 042e0d16c..f188850eb 100644 --- a/src/main/config/checkstyle-header.txt +++ b/src/main/config/checkstyle-header.txt @@ -13,6 +13,6 @@ ^ \* See the License for the specific language governing permissions and\s*$ ^ \* limitations under the License\.\s*$ ^ \*\s*$ -^ \* Copyright \(c\) 201[234] (Jeremy Long|Steve Springett)\. All Rights Reserved\.\s*$ +^ \* Copyright \(c\) 201[0-9] (Jeremy Long|Steve Springett)\. All Rights Reserved\.\s*$ ^ \*/\s*$ ^package diff --git a/src/main/config/checkstyle-suppressions.xml b/src/main/config/checkstyle-suppressions.xml index 0c5f26ef9..27c63c4b8 100644 --- a/src/main/config/checkstyle-suppressions.xml +++ b/src/main/config/checkstyle-suppressions.xml @@ -6,7 +6,9 @@ - - + + + + \ No newline at end of file diff --git a/src/main/site-resources/current.txt b/src/main/site-resources/current.txt new file mode 100644 index 000000000..f2ab45c3b --- /dev/null +++ b/src/main/site-resources/current.txt @@ -0,0 +1 @@ +${project.version} \ No newline at end of file diff --git a/src/site/markdown/central-analyzer.md b/src/site/markdown/central-analyzer.md new file mode 100644 index 000000000..ec70e36a3 --- /dev/null +++ b/src/site/markdown/central-analyzer.md @@ -0,0 +1,11 @@ +Central Analyzer +============== + +Dependency-check includes an analyzer that will check for the Maven GAV +(Group/Artifact/Version) information for artifacts in the scanned area. By +default the information comes from [Maven Central][1]. If the artifact's hash +is found in the configured Nexus repository, its GAV is recorded as an Identifier +and the Group is collected as Vendor evidence, the Artifact is collected as Product +evidence, and the Version is collected as Version evidence. + +[1]: http://search.maven.org/ "Maven Central" diff --git a/src/site/markdown/nexus-analyzer.md b/src/site/markdown/nexus-analyzer.md index 5c3ebb8b6..b8992182b 100644 --- a/src/site/markdown/nexus-analyzer.md +++ b/src/site/markdown/nexus-analyzer.md @@ -1,54 +1,17 @@ Nexus Analyzer ============== -Dependency-check includes an analyzer that will check for the Maven GAV -(Group/Artifact/Version) information for artifacts in the scanned area. By -default the information comes from [Maven Central][1], but can be configured to -use a local repository if necessary. If the artifact's hash is found in the -configured Nexus repository, its GAV is recorded as an Identifier and the Group -is collected as Vendor evidence, the Artifact is collected as Product evidence, -and the Version is collected as Version evidence. +The Nexus Analyzer has been superceded by the Central Analyzer. If both the +Central Analyzer and Nexus Analyzer are enabled and the Nexus URL has not +been configured to point to an instance of Nexus Pro the Nexus Analyzer will +disable itself. -Default Configuration ---------------------- - -By default, the Nexus analyzer uses the [Sonatype Nexus Repository][2] to search -for SHA-1 hashes of dependencies. If the proxy is configured for Dependency -Check, that proxy is used in order to connect to the Nexus Central repository. -So if you're using `--proxyurl` on the command-line, the `proxyUrl` setting in -the Maven plugin, or the `proxyUrl` attribute in the Ant task, that proxy will -be used by default. Also, the proxy port, user, and password configured globally -are used as well. - -Overriding Defaults -------------------- - -If you have an internal Nexus repository you want to use, Dependency Check can -be configured to use this repository rather than Sonatype. This needs to be a -Nexus repository (support for Artifactory is planned). For a normal installation -of Nexus, you would append `/service/local/` to the root of the URL to your -Nexus repository. This URL can be set as: - -* `analyzer.nexus.url` in `dependencycheck.properties` -* `--nexus ` in the CLI -* The `nexusUrl` property in the Maven plugin -* The `nexusUrl` attribute in the Ant task - -If this repository is internal and should not use the proxy, you can disable the -proxy for just the Nexus analyzer. Setting this makes no difference if a proxy -is not configured. - -* `analyzer.nexus.proxy=false` in `dependencycheck.properties` -* `--nexusUsesProxy false` in the CLI -* The `nexusUsesProxy` property in the Maven plugin -* The `nexusUsesProxy` attribute in the Ant task - -Finally, the Nexus analyzer can be disabled altogether. - -* `analyzer.nexus.enabled=false` in `dependencycheck.properties` -* `--disableNexus` in the CLI -* `nexusAnalyzerEnabled` property in the Maven plugin -* `nexusAnalyzerEnabled` attribute in the Ant task +The Nexus Analyzer will check for the Maven GAV (Group/Artifact/Version) information +for artifacts in the scanned area. This is done by determining if an artifact exists +in a Nexus Pro installation using the SHA-1 hash of the artifact scanned. If the +artifact's hash is found in the configured Nexus repository, its GAV is recorded as +an Identifier and the Group is collected as Vendor evidence, the Artifact is +collected as Product evidence, and the Version is collected as Version evidence. Logging ------- @@ -60,9 +23,9 @@ You may see a log message similar to the following during analysis: At the beginning of analysis, a check is made by the Nexus analyzer to see if it is able to reach the configured Nexus service, and if it cannot be reached, the -analyzer will be disabled. If you see this message, you can use the -configuration settings described above to resolve the issue, or disable the -analyzer altogether. +analyzer will be disabled. If you see this message, you can use the configuration +settings described in either the CLI, Ant, Maven, or Jenkins plugins to resolve +the issue, or disable the analyzer altogether. [1]: http://search.maven.org/ "Maven Central" [2]: https://repository.sonatype.org/ "Sonatype Nexus Repository" diff --git a/src/site/markdown/related.md b/src/site/markdown/related.md new file mode 100644 index 000000000..17ccc6fe6 --- /dev/null +++ b/src/site/markdown/related.md @@ -0,0 +1,20 @@ +Related FOSS Projects +=========== +* [The Victims Project](https://github.com/victims) +* [Retire.js](http://bekk.github.io/retire.js/) + +Vulnerability Sources +=========== +The following are sources of vulnerability information. Dependency-check only uses information in the National Vulnerability +Database (NVD). The other sources listed below contain vulnerability information that may not be included in the NVD. +* [National Vulnerability Database](https://nvd.nist.gov/) +* [OSVDB](http://osvdb.org/) + +Related Commercial Products +=========== +The below list is merely informational. It is not a complete list, nor do the authors of dependency-check endorse any +of the products listed below. +* [Sonatype CLM](http://www.sonatype.com/clm/overview) +* [Black Duck](https://www.blackducksoftware.com/products/black-duck-suite/code-center) +* [Palamida](http://www.palamida.com/products/enterpriseedition.html) + diff --git a/src/site/markdown/thereport.md b/src/site/markdown/thereport.md index 21995f8e4..31d38bb68 100644 --- a/src/site/markdown/thereport.md +++ b/src/site/markdown/thereport.md @@ -1,26 +1,46 @@ -How To Read The Report +How To Read The Reports ======== -There is a lot of information contained in the HTML version of the report. When analyzing the results, the first thing one should do is determine if the CPE looks -appropriate. Due to the way dependency-check works (see above) the report may contain false positives; these false positives are primarily on the CPE values. If the CPE value -is wrong, this is usually obvious and one should use the suppression feature in the report to generate a suppression XML file that can be used on future scans. In addition -to just looking at the CPE values in comparison to the name of the dependency - one may also consider the confidence of the CPE (as discussed in [How does dependency-check -work](./internals.html)). See the [Suppressing False Positives](./suppression.html) page for more information on how to generate and use the suppression file. +The top of the report contains a list of the identified vulnerable components. By clicking the 'Showing Vulnerable +Dependencies' link the list will be expanded to include all of the dependencies scanned. The table lists: -Once you have weeded out any obvious false positives one can then look at the remaining entries and determine if any of the identified CVE entries are actually -exploitable in your environment. Determining if a CVE is exploitable in your environment can be tricky - for this I do not currently have any tips other then -upgrade the library if you can just to be safe. Note, some CVE entries can be fixed by either upgrading the library or changing configuration options. +* Dependency - the file name of the dependency scanned. +* CPE - any Common Platform Enumeration identifiers found. +* GAV - the Maven Group, Artifact, Version (GAV). +* Highest Severity - the highest severity of any associated CVEs. +* CVE Count - the number of associated CVEs. +* CPE Confidence - a ranking of how confident dependency-check is that the CPE was identified correctly. +* Evidence Count - the quantity of data extracted from the dependency that was used to identify the CPE. -One item that dependency-check flags that many may think is a false positive are old database drivers. One thing to consider about an old database driver is that the -CPE/CVEs identified are usually for the server rather then the driver. However, the presence of an old driver may indicate that you have an older version of the server -running in your environment and that server may need to be patched or upgraded. However, in some cases the old database drivers are actually unused, transitive dependencies -from other dependencies. +There is a lot of information contained in the HTML version of the report. When analyzing the results, the first +thing one should do is determine if the identified CPE is correct. Due to the way dependency-check works (see +[How it works](./internals.html) for more information) the report may contain false positives. These false positives +are primarily on the CPE values. If the CPE value is wrong, this is usually obvious, one should use the suppression +feature in the report to generate a suppression XML file that can be used on future scans. In addition to looking +at the CPE values in comparison to the name of the dependency one may also consider the confidence of the CPE +(as discussed in [How does dependency-check work](./internals.html)). See the [Suppressing False Positives](./suppression.html) +page for more information on how to generate and use the suppression file. + +Once you have weeded out any obvious false positives one can then look at the remaining entries and determine if +any of the identified CVE entries are actually exploitable in your environment. Determining if a CVE is exploitable +in your environment can be tricky, for this we do not currently have any tips other then upgrade the library if you +can just to be safe. Note, some CVE entries can be fixed by either upgrading the library or changing configuration +options. + +One item that dependency-check flags that many may think is a false positive are old database drivers. One thing to +consider about an old database driver is that the CPE/CVEs identified are usually for the server rather then the driver. +However, the presence of an old driver may indicate that you have an older version of the server running in your +environment and that server may need to be patched or upgraded. However, in some cases the old database drivers are +actually unused, transitive dependencies. Regarding False Negatives ======= -As stated above, due to the nature of dependency-check there may be publicly disclosed vulnerabilities in the project dependencies scanned by dependency-check that -are not identified. With the current version of dependency-check the HTML report has a table at the top that initially displays just the dependencies with identified -vulnerabilities. This can be toggled to show all dependencies. If you examine the rows that do not have identified CPE/CVE entries you will see an "evidence count". -If the evidence count is extremely low (0-5 entries) then there may not have been enough information contained in the dependency to identify a CPE and associated CVEs. +As stated above, due to the nature of dependency-check there may be publicly disclosed vulnerabilities in the project +dependencies scanned by dependency-check that +are not identified. With the current version of dependency-check the HTML report has a table at the top that initially +displays just the dependencies with identified vulnerabilities. This can be toggled to show all dependencies. If you +examine the rows that do not have identified CPE/CVE entries you will see an "evidence count". If the evidence count +is extremely low (0-5 entries) then there may not have been enough information contained in the dependency to identify +a CPE and associated CVEs. It should be noted that while the false positives described above are bad, more concerning is that there may be vulnerabilities within the project dependencies that have yet to be publicly known. If one has the resources consider performing security assessments on the project dependencies. diff --git a/src/site/site.xml b/src/site/site.xml index 3ad2fb0c2..e4964c715 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -98,6 +98,9 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. Jar Analyzer + + Central Analyzer + Nexus Analyzer @@ -128,6 +131,6 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. A set of utility classes used by dependency-check. -
    Copyright © 2012-2014 Jeremy Long. All Rights Reserved.
    +