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 675eee417..824f5103a 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 @@ -30,6 +30,8 @@ import org.owasp.dependencycheck.data.update.UpdateService; import org.owasp.dependencycheck.data.update.exception.UpdateException; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.exception.NoDataException; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; @@ -47,8 +49,10 @@ import java.util.Map; import java.util.Set; /** - * Scans files, directories, etc. for Dependencies. Analyzers are loaded and used to process the files found by the scan, if a - * file is encountered and an Analyzer is associated with the file type then the file is turned into a dependency. + * Scans files, directories, etc. for Dependencies. Analyzers are loaded and + * used to process the files found by the scan, if a file is encountered and an + * Analyzer is associated with the file type then the file is turned into a + * dependency. * * @author Jeremy Long */ @@ -69,7 +73,8 @@ public class Engine implements FileFilter { private final Set fileTypeAnalyzers = new HashSet(); /** - * The ClassLoader to use when dynamically loading Analyzer and Update services. + * The ClassLoader to use when dynamically loading Analyzer and Update + * services. */ private ClassLoader serviceClassLoader = Thread.currentThread().getContextClassLoader(); /** @@ -80,7 +85,8 @@ public class Engine implements FileFilter { /** * Creates a new Engine. * - * @throws DatabaseException thrown if there is an error connecting to the database + * @throws DatabaseException thrown if there is an error connecting to the + * database */ public Engine() throws DatabaseException { initializeEngine(); @@ -90,7 +96,8 @@ public class Engine implements FileFilter { * 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 + * @throws DatabaseException thrown if there is an error connecting to the + * database */ public Engine(ClassLoader serviceClassLoader) throws DatabaseException { this.serviceClassLoader = serviceClassLoader; @@ -98,9 +105,11 @@ public class Engine implements FileFilter { } /** - * Creates a new Engine using the specified classloader to dynamically load Analyzer and Update services. + * Creates a new Engine using the specified classloader to dynamically load + * Analyzer and Update services. * - * @throws DatabaseException thrown if there is an error connecting to the database + * @throws DatabaseException thrown if there is an error connecting to the + * database */ protected final void initializeEngine() throws DatabaseException { ConnectionFactory.initialize(); @@ -115,7 +124,8 @@ public class Engine implements FileFilter { } /** - * Loads the analyzers specified in the configuration file (or system properties). + * Loads the analyzers specified in the configuration file (or system + * properties). */ private void loadAnalyzers() { if (!analyzers.isEmpty()) { @@ -164,8 +174,9 @@ public class Engine implements FileFilter { } /** - * 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. + * 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. * * @param paths an array of paths to files or directories to be analyzed * @return the list of dependencies scanned @@ -183,8 +194,9 @@ public class Engine implements FileFilter { } /** - * 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. + * 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 * @return the list of dependencies scanned @@ -195,8 +207,9 @@ public class Engine implements FileFilter { } /** - * 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. + * 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. * * @param files an array of paths to files or directories to be analyzed. * @return the list of dependencies @@ -214,8 +227,9 @@ public class Engine implements FileFilter { } /** - * Scans a collection of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies - * identified are added to the dependency collection. + * Scans a collection of files or directories. If a directory is specified, + * it will be scanned recursively. Any dependencies identified are added to + * the dependency collection. * * @param files a set of paths to files or directories to be analyzed * @return the list of dependencies scanned @@ -233,8 +247,9 @@ public class Engine implements FileFilter { } /** - * 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. + * 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 @@ -257,7 +272,8 @@ public class Engine implements FileFilter { } /** - * Recursively scans files and directories. Any dependencies identified are added to the dependency collection. + * Recursively scans files and directories. Any dependencies identified are + * added to the dependency collection. * * @param dir the directory to scan * @return the list of Dependency objects scanned @@ -282,7 +298,8 @@ public class Engine implements FileFilter { } /** - * Scans a specified file. If a dependency is identified it is added to the dependency collection. + * Scans a specified file. If a dependency is identified it is added to the + * dependency collection. * * @param file The file to scan * @return the scanned dependency @@ -301,20 +318,38 @@ public class Engine implements FileFilter { } /** - * Runs the analyzers against all of the dependencies. Since the mutable dependencies list is exposed via - * {@link #getDependencies()}, this method iterates over a copy of the dependencies list. Thus, the potential for - * {@link java.util.ConcurrentModificationException}s is avoided, and analyzers may safely add or remove entries from the - * dependencies list. + * Runs the analyzers against all of the dependencies. Since the mutable + * dependencies list is exposed via {@link #getDependencies()}, this method + * iterates over a copy of the dependencies list. Thus, the potential for + * {@link java.util.ConcurrentModificationException}s is avoided, and + * analyzers may safely add or remove entries from the dependencies list. + * + * Every effort is made to complete analysis on the dependencies. In some + * cases an exception will occur with part of the analysis being performed + * which may not affect the entire analysis. If an exception occurs it will + * be included in the thrown exception collection. + * + * @throws ExceptionCollection a collections of any exceptions that occurred + * during analysis */ - public void analyzeDependencies() { + public void analyzeDependencies() throws ExceptionCollection { + List exceptions = new ArrayList(); boolean autoUpdate = true; try { autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); } catch (InvalidSettingException ex) { LOGGER.debug("Invalid setting for auto-update; using true."); + exceptions.add(ex); } if (autoUpdate) { - doUpdates(); + try { + doUpdates(); + } catch (UpdateException ex) { + exceptions.add(ex); + LOGGER.warn("Unable to update Cached Web DataSource, using local " + + "data instead. Results may not include recent vulnerabilities."); + LOGGER.debug("Update Error", ex); + } } //need to ensure that data exists @@ -323,12 +358,13 @@ public class Engine implements FileFilter { } catch (NoDataException ex) { LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage()); LOGGER.debug("", ex); - return; + exceptions.add(ex); + throw new ExceptionCollection(exceptions, true); } catch (DatabaseException ex) { LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage()); LOGGER.debug("", ex); - return; - + exceptions.add(ex); + throw new ExceptionCollection(exceptions, true); } LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------"); @@ -340,7 +376,12 @@ public class Engine implements FileFilter { final List analyzerList = analyzers.get(phase); for (Analyzer a : analyzerList) { - a = initializeAnalyzer(a); + try { + a = initializeAnalyzer(a); + } catch (InitializationException ex) { + exceptions.add(ex); + continue; + } /* need to create a copy of the collection because some of the * analyzers may modify it. This prevents ConcurrentModificationExceptions. @@ -361,10 +402,12 @@ public class Engine implements FileFilter { } catch (AnalysisException ex) { LOGGER.warn("An error occurred while analyzing '{}'.", d.getActualFilePath()); LOGGER.debug("", ex); + exceptions.add(ex); } catch (Throwable ex) { //final AnalysisException ax = new AnalysisException(axMsg, ex); LOGGER.warn("An unexpected error occurred during analysis of '{}'", d.getActualFilePath()); LOGGER.debug("", ex); + exceptions.add(ex); } } } @@ -380,6 +423,9 @@ public class Engine implements FileFilter { LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------"); LOGGER.info("Analysis Complete ({} ms)", System.currentTimeMillis() - analysisStart); + if (exceptions.size() > 0) { + throw new ExceptionCollection(exceptions); + } } /** @@ -387,12 +433,14 @@ public class Engine implements FileFilter { * * @param analyzer the analyzer to initialize * @return the initialized analyzer + * @throws InitializationException thrown when there is a problem + * initializing the analyzer */ - protected Analyzer initializeAnalyzer(Analyzer analyzer) { + protected Analyzer initializeAnalyzer(Analyzer analyzer) throws InitializationException { try { LOGGER.debug("Initializing {}", analyzer.getName()); analyzer.initialize(); - } catch (Throwable ex) { + } catch (InitializationException ex) { LOGGER.error("Exception occurred initializing {}.", analyzer.getName()); LOGGER.debug("", ex); try { @@ -400,6 +448,16 @@ public class Engine implements FileFilter { } catch (Throwable ex1) { LOGGER.trace("", ex1); } + throw ex; + } catch (Throwable ex) { + LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName()); + LOGGER.debug("", ex); + try { + analyzer.close(); + } catch (Throwable ex1) { + LOGGER.trace("", ex1); + } + throw new InitializationException("Unexpected Exception", ex); } return analyzer; } @@ -419,28 +477,26 @@ public class Engine implements FileFilter { } /** - * Cycles through the cached web data sources and calls update on all of them. + * Cycles through the cached web data sources and calls update on all of + * them. + * + * @throws UpdateException */ - public void doUpdates() { + public void doUpdates() throws UpdateException { LOGGER.info("Checking for updates"); final long updateStart = System.currentTimeMillis(); final UpdateService service = new UpdateService(serviceClassLoader); final Iterator iterator = service.getDataSources(); while (iterator.hasNext()) { final CachedWebDataSource source = iterator.next(); - try { - source.update(); - } catch (UpdateException ex) { - LOGGER.warn( - "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities."); - LOGGER.debug("Unable to update details for {}", source.getClass().getName(), ex); - } + source.update(); } LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart); } /** - * Returns a full list of all of the analyzers. This is useful for reporting which analyzers where used. + * Returns a full list of all of the analyzers. This is useful for reporting + * which analyzers where used. * * @return a list of Analyzers */ @@ -457,7 +513,8 @@ public class Engine implements FileFilter { * Checks all analyzers to see if an extension is supported. * * @param file a file extension - * @return true or false depending on whether or not the file extension is supported + * @return true or false depending on whether or not the file extension is + * supported */ @Override public boolean accept(File file) { @@ -483,10 +540,12 @@ public class Engine implements FileFilter { } /** - * Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown. + * Checks the CPE Index to ensure documents exists. If none exist a + * NoDataException is thrown. * * @throws NoDataException thrown if no data exists in the CPE Index - * @throws DatabaseException thrown if there is an exception opening the database + * @throws DatabaseException thrown if there is an exception opening the + * database */ private void ensureDataExists() throws NoDataException, DatabaseException { final CveDB cve = new CveDB(); 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 b2b63955e..038644a3e 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 @@ -27,6 +27,7 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.exception.ScanAgentException; import org.owasp.dependencycheck.reporting.ReportGenerator; import org.owasp.dependencycheck.utils.Settings; @@ -34,10 +35,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This class provides a way to easily conduct a scan solely based on existing evidence metadata rather than collecting evidence - * from the files themselves. This class is based on the Ant task and Maven plugin with the exception that it takes a list of - * dependencies that can be programmatically added from data in a spreadsheet, database or some other datasource and conduct a - * scan based on this pre-defined evidence. + * This class provides a way to easily conduct a scan solely based on existing + * evidence metadata rather than collecting evidence from the files themselves. + * This class is based on the Ant task and Maven plugin with the exception that + * it takes a list of dependencies that can be programmatically added from data + * in a spreadsheet, database or some other datasource and conduct a scan based + * on this pre-defined evidence. * *

Example:

*
@@ -138,7 +141,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Specifies the destination directory for the generated Dependency-Check report.
+     * Specifies the destination directory for the generated Dependency-Check
+     * report.
      */
     private String reportOutputDirectory;
 
@@ -161,9 +165,11 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * 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;
 
@@ -186,8 +192,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * 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;
 
@@ -233,8 +239,9 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * 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 ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
 
@@ -283,7 +290,9 @@ public class DependencyCheckScanAgent {
      * Get the value of proxyServer.
      *
      * @return the value of proxyServer
-     * @deprecated use {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()} instead
+     * @deprecated use
+     * {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()}
+     * instead
      */
     @Deprecated
     public String getProxyUrl() {
@@ -694,8 +703,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * 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;
 
@@ -836,11 +845,17 @@ public class DependencyCheckScanAgent {
      * Executes the Dependency-Check on the dependent libraries.
      *
      * @return the Engine used to scan the dependencies.
-     * @throws org.owasp.dependencycheck.data.nvdcve.DatabaseException thrown if there is an exception connecting to the database
+     * @throws org.owasp.dependencycheck.data.nvdcve.DatabaseException thrown if
+     * there is an exception connecting to the database
      */
-    private Engine executeDependencyCheck() throws DatabaseException {
+    private Engine executeDependencyCheck() throws ExceptionCollection {
         populateSettings();
-        final Engine engine = new Engine();
+        final Engine engine;
+        try {
+            engine = new Engine();
+        } catch (DatabaseException ex) {
+            throw new ExceptionCollection(ex, true);
+        }
         engine.setDependencies(this.dependencies);
         engine.analyzeDependencies();
         return engine;
@@ -881,8 +896,9 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * 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();
@@ -925,7 +941,8 @@ public class DependencyCheckScanAgent {
      * Executes the dependency-check and generates the report.
      *
      * @return a reference to the engine used to perform the scan.
-     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if there is an exception executing the scan.
+     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
+     * there is an exception executing the scan.
      */
     public Engine execute() throws ScanAgentException {
         Engine engine = null;
@@ -940,10 +957,12 @@ public class DependencyCheckScanAgent {
             if (this.failBuildOnCVSS <= 10) {
                 checkForFailure(engine.getDependencies());
             }
-        } catch (DatabaseException ex) {
-            LOGGER.error(
-                    "Unable to connect to the dependency-check database; analysis has stopped");
-            LOGGER.debug("", ex);
+        } catch (ExceptionCollection ex) {
+            if (ex.isFatal()) {
+                LOGGER.error("A fatal exception occurred during analysis; analysis has stopped. Please see the debug log for more details.");
+                LOGGER.debug("", ex);
+            }
+            throw new ScanAgentException("One or more exceptions occurred during analysis; please see the debug log for more details.", ex);
         } finally {
             Settings.cleanup(true);
             if (engine != null) {
@@ -954,11 +973,12 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the
-     * configuration.
+     * Checks to see if a vulnerability has been identified with a CVSS score
+     * that is above the threshold set in the configuration.
      *
      * @param dependencies the list of dependency objects
-     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if there is an exception executing the scan.
+     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
+     * there is an exception executing the scan.
      */
     private void checkForFailure(List dependencies) throws ScanAgentException {
         final StringBuilder ids = new StringBuilder();
@@ -986,7 +1006,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries.
+     * Generates a warning message listing a summary of dependencies and their
+     * associated CPE and CVE entries.
      *
      * @param dependencies a list of dependency objects
      */
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ExceptionCollection.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ExceptionCollection.java
new file mode 100644
index 000000000..67e426a0d
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ExceptionCollection.java
@@ -0,0 +1,131 @@
+/*
+ * 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) 2016 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.exception;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A collection of several exceptions.
+ *
+ * @author Jeremy Lomg
+ */
+public class ExceptionCollection extends Exception {
+
+    /**
+     * Instantiates a new exception collection.
+     *
+     * @param exceptions a list of exceptions
+     */
+    public ExceptionCollection(List exceptions) {
+        super();
+        this.exceptions = exceptions;
+    }
+    /**
+     * Instantiates a new exception collection.
+     *
+     * @param exceptions a list of exceptions
+     * @param fatal indicates if the exception that occurred is fatal - meaning
+     * that no analysis was performed.
+     */
+    public ExceptionCollection(List exceptions, boolean fatal) {
+        super();
+        this.exceptions = exceptions;
+        this.fatal = fatal;
+    }
+        /**
+     * Instantiates a new exception collection.
+     *
+     * @param exceptions a list of exceptions
+     * @param fatal indicates if the exception that occurred is fatal - meaning
+     * that no analysis was performed.
+     */
+    public ExceptionCollection(Throwable exceptions, boolean fatal) {
+        super();
+        this.exceptions.add(exceptions);
+        this.fatal = fatal;
+    }
+    /**
+     * Instantiates a new exception collection.
+     */
+    public ExceptionCollection() {
+        super();
+        this.exceptions = new ArrayList();
+    }
+    /**
+     * The serial version uid.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * A collection of exceptions.
+     */
+    private List exceptions;
+
+    /**
+     * Get the value of exceptions.
+     *
+     * @return the value of exceptions
+     */
+    public List getExceptions() {
+        return exceptions;
+    }
+
+    /**
+     * Adds an exception to the collection.
+     *
+     * @param ex the exception to add
+     */
+    public void addException(Throwable ex) {
+        this.exceptions.add(ex);
+    }
+        /**
+     * Adds an exception to the collection.
+     *
+     * @param ex the exception to add
+     */
+    public void addException(Throwable ex, boolean fatal) {
+        addException(ex);
+        this.fatal = fatal;
+    }
+
+    /**
+     * Flag indicating if a fatal exception occurred that would prevent the attempt
+     * at completing the analysis even if exceptions occurred.
+     */
+    private boolean fatal = false;
+
+    /**
+     * Get the value of fatal
+     *
+     * @return the value of fatal
+     */
+    public boolean isFatal() {
+        return fatal;
+    }
+
+    /**
+     * Set the value of fatal
+     *
+     * @param fatal new value of fatal
+     */
+    public void setFatal(boolean fatal) {
+        this.fatal = fatal;
+    }
+
+}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ReportException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ReportException.java
new file mode 100644
index 000000000..7199b3520
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ReportException.java
@@ -0,0 +1,66 @@
+/*
+ * 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) 2016 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.exception;
+
+/**
+ * An exception used when generating reports.
+ *
+ * @author Jeremy Long
+ */
+public class ReportException extends Exception {
+
+    /**
+     * The serial version uid.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates a new ReportException.
+     */
+    public ReportException() {
+        super();
+    }
+
+    /**
+     * Creates a new ReportException.
+     *
+     * @param msg a message for the exception.
+     */
+    public ReportException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * Creates a new ReportException.
+     *
+     * @param ex the cause of the exception.
+     */
+    public ReportException(Throwable ex) {
+        super(ex);
+    }
+
+    /**
+     * Creates a new ReportException.
+     *
+     * @param msg a message for the exception.
+     * @param ex the cause of the exception.
+     */
+    public ReportException(String msg, Throwable ex) {
+        super(msg, ex);
+    }
+}
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 b47f9a8b9..99b7b387d 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
@@ -26,6 +26,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -37,13 +38,16 @@ import org.apache.velocity.runtime.RuntimeConstants;
 import org.owasp.dependencycheck.analyzer.Analyzer;
 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
 import org.owasp.dependencycheck.dependency.Dependency;
+import org.owasp.dependencycheck.exception.ReportException;
 import org.owasp.dependencycheck.utils.Settings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The ReportGenerator is used to, as the name implies, generate reports. Internally the generator uses the Velocity
- * Templating Engine. The ReportGenerator exposes a list of Dependencies to the template when generating the report.
+ * The ReportGenerator is used to, as the name implies, generate reports.
+ * Internally the generator uses the Velocity Templating Engine. The
+ * ReportGenerator exposes a list of Dependencies to the template when
+ * generating the report.
  *
  * @author Jeremy Long
  */
@@ -79,7 +83,7 @@ public class ReportGenerator {
     /**
      * The Velocity Engine.
      */
-    private final VelocityEngine engine;
+    private final VelocityEngine velocityEngine;
     /**
      * The Velocity Engine Context.
      */
@@ -91,13 +95,14 @@ public class ReportGenerator {
      * @param applicationName the application name being analyzed
      * @param dependencies the list of dependencies
      * @param analyzers the list of analyzers used
-     * @param properties the database properties (containing timestamps of the NVD CVE data)
+     * @param properties the database properties (containing timestamps of the
+     * NVD CVE data)
      */
     public ReportGenerator(String applicationName, List dependencies, List analyzers, DatabaseProperties properties) {
-        engine = createVelocityEngine();
+        velocityEngine = createVelocityEngine();
         context = createContext();
 
-        engine.init();
+        velocityEngine.init();
 
         final DateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy 'at' HH:mm:ss z");
         final DateFormat dateFormatXML = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
@@ -119,19 +124,19 @@ public class ReportGenerator {
     /**
      * Creates a new Velocity Engine.
      *
-     * @return a velocity engine.
+     * @return a velocity engine
      */
     private VelocityEngine createVelocityEngine() {
-        final VelocityEngine engine = new VelocityEngine();
+        final VelocityEngine velocity = new VelocityEngine();
         // Logging redirection for Velocity - Required by Jenkins and other server applications
-        engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, VelocityLoggerRedirect.class.getName());
-        return engine;
+        velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, VelocityLoggerRedirect.class.getName());
+        return velocity;
     }
 
     /**
      * Creates a new Velocity Context.
      *
-     * @return a Velocity Context.
+     * @return a Velocity Context
      */
     private Context createContext() {
         return new VelocityContext();
@@ -143,7 +148,7 @@ public class ReportGenerator {
      * @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.
+     * @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) {
@@ -162,10 +167,9 @@ public class ReportGenerator {
      *
      * @param outputDir the path where the reports should be written
      * @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.
+     * @throws ReportException is thrown if there is an error writing out the reports
      */
-    public void generateReports(String outputDir, Format format) throws IOException, Exception {
+    public void generateReports(String outputDir, Format format) throws ReportException {
         if (format == Format.XML || format == Format.ALL) {
             generateReport("XmlReport", outputDir + File.separator + "dependency-check-report.xml");
         }
@@ -181,11 +185,12 @@ public class ReportGenerator {
      * Generates the Dependency Reports for the identified dependencies.
      *
      * @param outputDir the path where the reports should be written
-     * @param outputFormat the format the report should be written in (XML, HTML, ALL)
-     * @throws IOException is thrown when the template file does not exist
-     * @throws Exception is thrown if there is an error writing out the reports.
+     * @param outputFormat the format the report should be written in (XML,
+     * HTML, ALL)
+     * @throws ReportException is thrown if there is an error creating out the
+     * reports
      */
-    public void generateReports(String outputDir, String outputFormat) throws IOException, Exception {
+    public void generateReports(String outputDir, String outputFormat) throws ReportException {
         final String format = outputFormat.toUpperCase();
         final String pathToCheck = outputDir.toLowerCase();
         if (format.matches("^(XML|HTML|VULN|ALL)$")) {
@@ -217,16 +222,16 @@ 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
+     * 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 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.
+     * @param templateName the name of the template to load
+     * @param outputStream the OutputStream to write the report to
+     * @throws ReportException is thrown when an exception occurs
      */
-    protected void generateReport(String templateName, OutputStream outputStream) throws IOException, Exception {
+    protected void generateReport(String templateName, OutputStream outputStream) throws ReportException {
         InputStream input = null;
         String templatePath = null;
         final File f = new File(templateName);
@@ -235,27 +240,30 @@ public class ReportGenerator {
                 templatePath = templateName;
                 input = new FileInputStream(f);
             } catch (FileNotFoundException ex) {
-                LOGGER.error("Unable to generate the report, the report template file could not be found.");
-                LOGGER.debug("", ex);
+                throw new ReportException("Unable to locate template file: " + templateName, ex);
             }
         } else {
             templatePath = "templates/" + templateName + ".vsl";
             input = this.getClass().getClassLoader().getResourceAsStream(templatePath);
         }
         if (input == null) {
-            throw new IOException("Template file doesn't exist");
+            throw new ReportException("Template file doesn't exist: " + templatePath);
         }
 
-        final InputStreamReader reader = new InputStreamReader(input, "UTF-8");
+        InputStreamReader reader = null;
         OutputStreamWriter writer = null;
 
         try {
+            reader = new InputStreamReader(input, "UTF-8");
             writer = new OutputStreamWriter(outputStream, "UTF-8");
-
-            if (!engine.evaluate(context, writer, templatePath, reader)) {
-                throw new Exception("Failed to convert the template into html.");
+            if (!velocityEngine.evaluate(context, writer, templatePath, reader)) {
+                throw new ReportException("Failed to convert the template into html.");
             }
             writer.flush();
+        } catch (UnsupportedEncodingException ex) {
+            throw new ReportException("Unable to generate the report using UTF-8", ex);
+        } catch (IOException ex) {
+            throw new ReportException("Unable to write the report", ex);
         } finally {
             if (writer != null) {
                 try {
@@ -271,25 +279,27 @@ public class ReportGenerator {
                     LOGGER.trace("", ex);
                 }
             }
-            try {
-                reader.close();
-            } catch (IOException ex) {
-                LOGGER.trace("", ex);
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException ex) {
+                    LOGGER.trace("", ex);
+                }
             }
         }
     }
 
     /**
-     * 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
+     * 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.
+     * @param templateName the name of the template to load
+     * @param outFileName the filename and path to write the report to
+     * @throws ReportException is thrown when the report cannot be generated
      */
-    protected void generateReport(String templateName, String outFileName) throws Exception {
+    protected void generateReport(String templateName, String outFileName) throws ReportException {
         File outFile = new File(outFileName);
         if (outFile.getParentFile() == null) {
             outFile = new File(".", outFileName);
@@ -297,7 +307,7 @@ public class ReportGenerator {
         if (!outFile.getParentFile().exists()) {
             final boolean created = outFile.getParentFile().mkdirs();
             if (!created) {
-                throw new Exception("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'.");
+                throw new ReportException("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'.");
             }
         }
 
@@ -305,6 +315,8 @@ public class ReportGenerator {
         try {
             outputSteam = new FileOutputStream(outFile);
             generateReport(templateName, outputSteam);
+        } catch (FileNotFoundException ex) {
+            throw new ReportException("Unable to write to file: " + outFile, ex);
         } finally {
             if (outputSteam != null) {
                 try {
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 1e12e6a82..bceefc23b 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
@@ -17,12 +17,19 @@
  */
 package org.owasp.dependencycheck;
 
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 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.exception.ExceptionCollection;
+import org.owasp.dependencycheck.exception.ReportException;
 import org.owasp.dependencycheck.reporting.ReportGenerator;
+import org.owasp.dependencycheck.utils.InvalidSettingException;
 import org.owasp.dependencycheck.utils.Settings;
 
 /**
@@ -34,10 +41,14 @@ public class EngineIntegrationTest extends BaseDBTestCase {
     /**
      * Test running the entire engine.
      *
-     * @throws Exception is thrown when an exception occurs.
+     * @throws java.io.IOException
+     * @throws org.owasp.dependencycheck.utils.InvalidSettingException
+     * @throws org.owasp.dependencycheck.data.nvdcve.DatabaseException
+     * @throws org.owasp.dependencycheck.exception.ReportException
+     * @throws org.owasp.dependencycheck.exception.ExceptionCollection
      */
     @Test
-    public void testEngine() throws Exception {
+    public void testEngine() throws IOException, InvalidSettingException, DatabaseException, ReportException, ExceptionCollection {
         String testClasses = "target/test-classes";
         boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false);
@@ -45,7 +56,23 @@ public class EngineIntegrationTest extends BaseDBTestCase {
         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
         instance.scan(testClasses);
         assertTrue(instance.getDependencies().size() > 0);
-        instance.analyzeDependencies();
+        try {
+            instance.analyzeDependencies();
+        } catch (ExceptionCollection ex) {
+            if (ex.getExceptions().size()==1 &&
+                    (ex.getExceptions().get(0).getMessage().contains("bundle-audit") ||
+                    ex.getExceptions().get(0).getMessage().contains("AssemblyAnalyzer"))) {
+                //this is fine to ignore
+            } else if (ex.getExceptions().size()==2 &&
+                    ((ex.getExceptions().get(0).getMessage().contains("bundle-audit") &&
+                    ex.getExceptions().get(1).getMessage().contains("AssemblyAnalyzer")) ||
+                    (ex.getExceptions().get(1).getMessage().contains("bundle-audit") &&
+                    ex.getExceptions().get(0).getMessage().contains("AssemblyAnalyzer")))) {
+                //this is fine to ignore
+            } else {
+                throw ex;
+            }
+        }
         CveDB cveDB = new CveDB();
         cveDB.open();
         DatabaseProperties dbProp = cveDB.getDatabaseProperties();
diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java
index d1f9101c3..050259597 100644
--- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java
+++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java
@@ -26,6 +26,7 @@ import java.io.File;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Level;
 
 import org.junit.After;
 import org.junit.Assume;
@@ -40,6 +41,7 @@ import org.owasp.dependencycheck.dependency.Dependency;
 import org.owasp.dependencycheck.dependency.Evidence;
 import org.owasp.dependencycheck.dependency.Identifier;
 import org.owasp.dependencycheck.dependency.Vulnerability;
+import org.owasp.dependencycheck.exception.ExceptionCollection;
 import org.owasp.dependencycheck.utils.Settings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -176,6 +178,7 @@ public class RubyBundleAuditAnalyzerTest extends BaseDBTestCase {
      * Test Ruby dependencies and their paths.
      *
      * @throws AnalysisException is thrown when an exception occurs.
+     * @throws DatabaseException thrown when an exception occurs
      */
     @Test
     public void testDependenciesPath() throws AnalysisException, DatabaseException {
@@ -187,6 +190,8 @@ public class RubyBundleAuditAnalyzerTest extends BaseDBTestCase {
         } catch (NullPointerException ex) {
             LOGGER.error("NPE", ex);
             throw ex;
+        } catch (ExceptionCollection ex) {
+            Assume.assumeNoException("Exception setting up RubyBundleAuditAnalyzer; bundle audit may not be installed, or property \"analyzer.bundle.audit.path\" may not be set.", ex);
         }
         List dependencies = engine.getDependencies();
         LOGGER.info(dependencies.size() + " dependencies found.");
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
index f849c8a7e..b691ccde5 100644
--- 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
@@ -23,13 +23,16 @@ 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.data.update.exception.UpdateException;
+import org.owasp.dependencycheck.exception.ExceptionCollection;
+import org.owasp.dependencycheck.exception.InitializationException;
 import org.owasp.dependencycheck.utils.Settings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * A modified version of the core engine specifically designed to persist some data between multiple executions of a multi-module
- * Maven project.
+ * 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
  */
@@ -52,16 +55,19 @@ public class Engine extends org.owasp.dependencycheck.Engine {
      */
     private List reactorProjects;
     /**
-     * Key used in the MavenProject context values to note whether or not an update has been executed.
+     * 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.
+     * Creates a new Engine to perform analysis 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
+     * @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;
@@ -71,9 +77,12 @@ public class Engine extends org.owasp.dependencycheck.Engine {
 
     /**
      * Runs the analyzers against all of the dependencies.
+     *
+     * @throws ExceptionCollection thrown if an exception occurred; contains a
+     * collection of exceptions that occurred during analysis.
      */
     @Override
-    public void analyzeDependencies() {
+    public void analyzeDependencies() throws ExceptionCollection {
         final MavenProject root = getExecutionRoot();
         if (root != null) {
             LOGGER.debug("Checking root project, {}, if updates have already been completed", root.getArtifactId());
@@ -91,8 +100,10 @@ public class Engine extends org.owasp.dependencycheck.Engine {
 
     /**
      * Runs the update steps of dependency-check.
+     *
+     * @throws UpdateException thrown if there is an exception
      */
-    public void update() {
+    public void update() throws UpdateException {
         final MavenProject root = getExecutionRoot();
         if (root != null && root.getContextValue(UPDATE_EXECUTED_FLAG) != null) {
             System.setProperty(Settings.KEYS.AUTO_UPDATE, Boolean.FALSE.toString());
@@ -103,20 +114,21 @@ public class Engine extends org.owasp.dependencycheck.Engine {
     /**
      * This constructor should not be called. Use Engine(MavenProject) instead.
      *
-     * @throws DatabaseException thrown if there is an issue connecting to the database
+     * @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.
+     * 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) {
+    protected Analyzer initializeAnalyzer(Analyzer analyzer) throws InitializationException {
         if (analyzer instanceof CPEAnalyzer) {
             CPEAnalyzer cpe = getPreviouslyLoadedCPEAnalyzer();
             if (cpe != null && cpe.isOpen()) {
@@ -129,7 +141,8 @@ public class Engine extends org.owasp.dependencycheck.Engine {
     }
 
     /**
-     * Releases resources used by the analyzers by calling close() on each analyzer.
+     * Releases resources used by the analyzers by calling close() on each
+     * analyzer.
      */
     @Override
     public void cleanup() {
@@ -216,8 +229,10 @@ public class Engine extends org.owasp.dependencycheck.Engine {
     }
 
     /**
-     * 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.
+     * 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()) {