Coverage Report - org.owasp.dependencycheck.App
 
Classes in this File Line Coverage Branch Coverage Complexity
App
10%
27/253
9%
10/104
7.75
 
 1  
 /*
 2  
  * This file is part of dependency-check-cli.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  *
 16  
  * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
 17  
  */
 18  
 package org.owasp.dependencycheck;
 19  
 
 20  
 import ch.qos.logback.classic.LoggerContext;
 21  
 import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
 22  
 import java.io.File;
 23  
 import java.io.FileNotFoundException;
 24  
 import java.io.IOException;
 25  
 import java.util.ArrayList;
 26  
 import java.util.HashSet;
 27  
 import java.util.List;
 28  
 import java.util.Set;
 29  
 import org.apache.commons.cli.ParseException;
 30  
 import org.apache.commons.lang.StringUtils;
 31  
 import org.owasp.dependencycheck.data.nvdcve.CveDB;
 32  
 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
 33  
 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
 34  
 import org.owasp.dependencycheck.dependency.Dependency;
 35  
 import org.apache.tools.ant.DirectoryScanner;
 36  
 import org.owasp.dependencycheck.reporting.ReportGenerator;
 37  
 import org.owasp.dependencycheck.utils.Settings;
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 import ch.qos.logback.core.FileAppender;
 41  
 import org.slf4j.impl.StaticLoggerBinder;
 42  
 
 43  
 /**
 44  
  * The command line interface for the DependencyCheck application.
 45  
  *
 46  
  * @author Jeremy Long
 47  
  */
 48  2
 public class App {
 49  
 
 50  
     /**
 51  
      * The logger.
 52  
      */
 53  1
     private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
 54  
 
 55  
     /**
 56  
      * The main method for the application.
 57  
      *
 58  
      * @param args the command line arguments
 59  
      */
 60  
     public static void main(String[] args) {
 61  
         try {
 62  0
             Settings.initialize();
 63  0
             final App app = new App();
 64  0
             app.run(args);
 65  
         } finally {
 66  0
             Settings.cleanup(true);
 67  0
         }
 68  0
     }
 69  
 
 70  
     /**
 71  
      * Main CLI entry-point into the application.
 72  
      *
 73  
      * @param args the command line arguments
 74  
      */
 75  
     public void run(String[] args) {
 76  0
         final CliParser cli = new CliParser();
 77  
 
 78  
         try {
 79  0
             cli.parse(args);
 80  0
         } catch (FileNotFoundException ex) {
 81  0
             System.err.println(ex.getMessage());
 82  0
             cli.printHelp();
 83  0
             return;
 84  0
         } catch (ParseException ex) {
 85  0
             System.err.println(ex.getMessage());
 86  0
             cli.printHelp();
 87  0
             return;
 88  0
         }
 89  
 
 90  0
         if (cli.getVerboseLog() != null) {
 91  0
             prepareLogger(cli.getVerboseLog());
 92  
         }
 93  
 
 94  0
         if (cli.isPurge()) {
 95  0
             if (cli.getConnectionString() != null) {
 96  0
                 LOGGER.error("Unable to purge the database when using a non-default connection string");
 97  
             } else {
 98  0
                 populateSettings(cli);
 99  
                 File db;
 100  
                 try {
 101  0
                     db = new File(Settings.getDataDirectory(), "dc.h2.db");
 102  0
                     if (db.exists()) {
 103  0
                         if (db.delete()) {
 104  0
                             LOGGER.info("Database file purged; local copy of the NVD has been removed");
 105  
                         } else {
 106  0
                             LOGGER.error("Unable to delete '{}'; please delete the file manually", db.getAbsolutePath());
 107  
                         }
 108  
                     } else {
 109  0
                         LOGGER.error("Unable to purge database; the database file does not exists: {}", db.getAbsolutePath());
 110  
                     }
 111  0
                 } catch (IOException ex) {
 112  0
                     LOGGER.error("Unable to delete the database");
 113  0
                 }
 114  0
             }
 115  0
         } else if (cli.isGetVersion()) {
 116  0
             cli.printVersionInfo();
 117  0
         } else if (cli.isUpdateOnly()) {
 118  0
             populateSettings(cli);
 119  0
             runUpdateOnly();
 120  0
         } else if (cli.isRunScan()) {
 121  0
             populateSettings(cli);
 122  
             try {
 123  0
                 runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), cli.getScanFiles(),
 124  
                         cli.getExcludeList(), cli.getSymLinkDepth());
 125  0
             } catch (InvalidScanPathException ex) {
 126  0
                 LOGGER.error("An invalid scan path was detected; unable to scan '//*' paths");
 127  0
             }
 128  
         } else {
 129  0
             cli.printHelp();
 130  
         }
 131  0
     }
 132  
 
 133  
     /**
 134  
      * Scans the specified directories and writes the dependency reports to the reportDirectory.
 135  
      *
 136  
      * @param reportDirectory the path to the directory where the reports will be written
 137  
      * @param outputFormat the output format of the report
 138  
      * @param applicationName the application name for the report
 139  
      * @param files the files/directories to scan
 140  
      * @param excludes the patterns for files/directories to exclude
 141  
      * @param symLinkDepth the depth that symbolic links will be followed
 142  
      *
 143  
      * @throws InvalidScanPathException thrown if the path to scan starts with "//"
 144  
      */
 145  
     private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files,
 146  
             String[] excludes, int symLinkDepth) throws InvalidScanPathException {
 147  0
         Engine engine = null;
 148  
         try {
 149  0
             engine = new Engine();
 150  0
             final List<String> antStylePaths = new ArrayList<String>();
 151  0
             for (String file : files) {
 152  0
                 final String antPath = ensureCanonicalPath(file);
 153  0
                 antStylePaths.add(antPath);
 154  
             }
 155  
 
 156  0
             final Set<File> paths = new HashSet<File>();
 157  0
             for (String file : antStylePaths) {
 158  0
                 LOGGER.debug("Scanning {}", file);
 159  0
                 final DirectoryScanner scanner = new DirectoryScanner();
 160  0
                 String include = file.replace('\\', '/');
 161  
                 File baseDir;
 162  
 
 163  0
                 if (include.startsWith("//")) {
 164  0
                     throw new InvalidScanPathException("Unable to scan paths specified by //");
 165  
                 } else {
 166  0
                     final int pos = getLastFileSeparator(include);
 167  0
                     final String tmpBase = include.substring(0, pos);
 168  0
                     final String tmpInclude = include.substring(pos + 1);
 169  0
                     if (tmpInclude.indexOf('*') >= 0 || tmpInclude.indexOf('?') >= 0
 170  
                             || (new File(include)).isFile()) {
 171  0
                         baseDir = new File(tmpBase);
 172  0
                         include = tmpInclude;
 173  
                     } else {
 174  0
                         baseDir = new File(tmpBase, tmpInclude);
 175  0
                         include = "**/*";
 176  
                     }
 177  
                 }
 178  
                 //LOGGER.debug("baseDir: {}", baseDir);
 179  
                 //LOGGER.debug("include: {}", include);
 180  0
                 scanner.setBasedir(baseDir);
 181  0
                 final String[] includes = {include};
 182  0
                 scanner.setIncludes(includes);
 183  0
                 scanner.setMaxLevelsOfSymlinks(symLinkDepth);
 184  0
                 if (symLinkDepth <= 0) {
 185  0
                     scanner.setFollowSymlinks(false);
 186  
                 }
 187  0
                 if (excludes != null && excludes.length > 0) {
 188  0
                     scanner.addExcludes(excludes);
 189  
                 }
 190  0
                 scanner.scan();
 191  0
                 if (scanner.getIncludedFilesCount() > 0) {
 192  0
                     for (String s : scanner.getIncludedFiles()) {
 193  0
                         final File f = new File(baseDir, s);
 194  0
                         LOGGER.debug("Found file {}", f.toString());
 195  0
                         paths.add(f);
 196  
                     }
 197  
                 }
 198  0
             }
 199  0
             engine.scan(paths);
 200  
 
 201  0
             engine.analyzeDependencies();
 202  0
             final List<Dependency> dependencies = engine.getDependencies();
 203  0
             DatabaseProperties prop = null;
 204  0
             CveDB cve = null;
 205  
             try {
 206  0
                 cve = new CveDB();
 207  0
                 cve.open();
 208  0
                 prop = cve.getDatabaseProperties();
 209  0
             } catch (DatabaseException ex) {
 210  0
                 LOGGER.debug("Unable to retrieve DB Properties", ex);
 211  
             } finally {
 212  0
                 if (cve != null) {
 213  0
                     cve.close();
 214  
                 }
 215  
             }
 216  0
             final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop);
 217  
             try {
 218  0
                 report.generateReports(reportDirectory, outputFormat);
 219  0
             } catch (IOException ex) {
 220  0
                 LOGGER.error("There was an IO error while attempting to generate the report.");
 221  0
                 LOGGER.debug("", ex);
 222  0
             } catch (Throwable ex) {
 223  0
                 LOGGER.error("There was an error while attempting to generate the report.");
 224  0
                 LOGGER.debug("", ex);
 225  0
             }
 226  0
         } catch (DatabaseException ex) {
 227  0
             LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped");
 228  0
             LOGGER.debug("", ex);
 229  
         } finally {
 230  0
             if (engine != null) {
 231  0
                 engine.cleanup();
 232  
             }
 233  
         }
 234  0
     }
 235  
 
 236  
     /**
 237  
      * Only executes the update phase of dependency-check.
 238  
      */
 239  
     private void runUpdateOnly() {
 240  0
         Engine engine = null;
 241  
         try {
 242  0
             engine = new Engine();
 243  0
             engine.doUpdates();
 244  0
         } catch (DatabaseException ex) {
 245  0
             LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped");
 246  0
             LOGGER.debug("", ex);
 247  
         } finally {
 248  0
             if (engine != null) {
 249  0
                 engine.cleanup();
 250  
             }
 251  
         }
 252  0
     }
 253  
 
 254  
     /**
 255  
      * Updates the global Settings.
 256  
      *
 257  
      * @param cli a reference to the CLI Parser that contains the command line arguments used to set the corresponding settings in
 258  
      * the core engine.
 259  
      */
 260  
     private void populateSettings(CliParser cli) {
 261  
 
 262  0
         final boolean autoUpdate = cli.isAutoUpdate();
 263  0
         final String connectionTimeout = cli.getConnectionTimeout();
 264  0
         final String proxyServer = cli.getProxyServer();
 265  0
         final String proxyPort = cli.getProxyPort();
 266  0
         final String proxyUser = cli.getProxyUsername();
 267  0
         final String proxyPass = cli.getProxyPassword();
 268  0
         final String dataDirectory = cli.getDataDirectory();
 269  0
         final File propertiesFile = cli.getPropertiesFile();
 270  0
         final String suppressionFile = cli.getSuppressionFile();
 271  0
         final String nexusUrl = cli.getNexusUrl();
 272  0
         final String databaseDriverName = cli.getDatabaseDriverName();
 273  0
         final String databaseDriverPath = cli.getDatabaseDriverPath();
 274  0
         final String connectionString = cli.getConnectionString();
 275  0
         final String databaseUser = cli.getDatabaseUser();
 276  0
         final String databasePassword = cli.getDatabasePassword();
 277  0
         final String additionalZipExtensions = cli.getAdditionalZipExtensions();
 278  0
         final String pathToMono = cli.getPathToMono();
 279  0
         final String cveMod12 = cli.getModifiedCve12Url();
 280  0
         final String cveMod20 = cli.getModifiedCve20Url();
 281  0
         final String cveBase12 = cli.getBaseCve12Url();
 282  0
         final String cveBase20 = cli.getBaseCve20Url();
 283  0
         final Integer cveValidForHours = cli.getCveValidForHours();
 284  
 
 285  0
         if (propertiesFile != null) {
 286  
             try {
 287  0
                 Settings.mergeProperties(propertiesFile);
 288  0
             } catch (FileNotFoundException ex) {
 289  0
                 LOGGER.error("Unable to load properties file '{}'", propertiesFile.getPath());
 290  0
                 LOGGER.debug("", ex);
 291  0
             } catch (IOException ex) {
 292  0
                 LOGGER.error("Unable to find properties file '{}'", propertiesFile.getPath());
 293  0
                 LOGGER.debug("", ex);
 294  0
             }
 295  
         }
 296  
         // We have to wait until we've merged the properties before attempting to set whether we use
 297  
         // the proxy for Nexus since it could be disabled in the properties, but not explicitly stated
 298  
         // on the command line
 299  0
         final boolean nexusUsesProxy = cli.isNexusUsesProxy();
 300  0
         if (dataDirectory != null) {
 301  0
             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
 302  0
         } else if (System.getProperty("basedir") != null) {
 303  0
             final File dataDir = new File(System.getProperty("basedir"), "data");
 304  0
             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
 305  0
         } else {
 306  0
             final File jarPath = new File(App.class.getProtectionDomain().getCodeSource().getLocation().getPath());
 307  0
             final File base = jarPath.getParentFile();
 308  0
             final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
 309  0
             final File dataDir = new File(base, sub);
 310  0
             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
 311  
         }
 312  0
         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
 313  0
         Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER, proxyServer);
 314  0
         Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT, proxyPort);
 315  0
         Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUser);
 316  0
         Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPass);
 317  0
         Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
 318  0
         Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
 319  0
         Settings.setIntIfNotNull(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, cveValidForHours);
 320  
 
 321  
         //File Type Analyzer Settings
 322  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, !cli.isJarDisabled());
 323  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, !cli.isArchiveDisabled());
 324  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, !cli.isPythonDistributionDisabled());
 325  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED, !cli.isPythonPackageDisabled());
 326  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED, !cli.isAutoconfDisabled());
 327  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_CMAKE_ENABLED, !cli.isCmakeDisabled());
 328  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, !cli.isNuspecDisabled());
 329  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, !cli.isAssemblyDisabled());
 330  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED, !cli.isBundleAuditDisabled());
 331  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, !cli.isOpenSSLDisabled());
 332  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, !cli.isComposerDisabled());
 333  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, !cli.isNodeJsDisabled());
 334  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED, !cli.isRubyGemspecDisabled());
 335  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !cli.isCentralDisabled());
 336  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !cli.isNexusDisabled());
 337  
 
 338  0
         Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH, cli.getPathToBundleAudit());
 339  0
         Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
 340  0
         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
 341  0
         Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
 342  0
         Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
 343  0
         Settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
 344  0
         Settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
 345  0
         Settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
 346  0
         Settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, additionalZipExtensions);
 347  0
         Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono);
 348  0
         if (cveBase12 != null && !cveBase12.isEmpty()) {
 349  0
             Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveBase12);
 350  0
             Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveBase20);
 351  0
             Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, cveMod12);
 352  0
             Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, cveMod20);
 353  
         }
 354  0
     }
 355  
 
 356  
     /**
 357  
      * Creates a file appender and adds it to logback.
 358  
      *
 359  
      * @param verboseLog the path to the verbose log file
 360  
      */
 361  
     private void prepareLogger(String verboseLog) {
 362  0
         final StaticLoggerBinder loggerBinder = StaticLoggerBinder.getSingleton();
 363  0
         final LoggerContext context = (LoggerContext) loggerBinder.getLoggerFactory();
 364  
 
 365  0
         final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
 366  0
         encoder.setPattern("%d %C:%L%n%-5level - %msg%n");
 367  0
         encoder.setContext(context);
 368  0
         encoder.start();
 369  0
         final FileAppender fa = new FileAppender();
 370  0
         fa.setAppend(true);
 371  0
         fa.setEncoder(encoder);
 372  0
         fa.setContext(context);
 373  0
         fa.setFile(verboseLog);
 374  0
         final File f = new File(verboseLog);
 375  0
         String name = f.getName();
 376  0
         final int i = name.lastIndexOf('.');
 377  0
         if (i > 1) {
 378  0
             name = name.substring(0, i);
 379  
         }
 380  0
         fa.setName(name);
 381  0
         fa.start();
 382  0
         final ch.qos.logback.classic.Logger rootLogger = context.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
 383  0
         rootLogger.addAppender(fa);
 384  0
     }
 385  
 
 386  
     /**
 387  
      * Takes a path and resolves it to be a canonical &amp; absolute path. The caveats are that this method will take an Ant style
 388  
      * file selector path (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at least to the left of the first *
 389  
      * or ?).
 390  
      *
 391  
      * @param path the path to canonicalize
 392  
      * @return the canonical path
 393  
      */
 394  
     protected String ensureCanonicalPath(String path) {
 395  2
         String basePath = null;
 396  2
         String wildCards = null;
 397  2
         final String file = path.replace('\\', '/');
 398  2
         if (file.contains("*") || file.contains("?")) {
 399  
 
 400  1
             int pos = getLastFileSeparator(file);
 401  1
             if (pos < 0) {
 402  0
                 return file;
 403  
             }
 404  1
             pos += 1;
 405  1
             basePath = file.substring(0, pos);
 406  1
             wildCards = file.substring(pos);
 407  1
         } else {
 408  1
             basePath = file;
 409  
         }
 410  
 
 411  2
         File f = new File(basePath);
 412  
         try {
 413  2
             f = f.getCanonicalFile();
 414  2
             if (wildCards != null) {
 415  1
                 f = new File(f, wildCards);
 416  
             }
 417  0
         } catch (IOException ex) {
 418  0
             LOGGER.warn("Invalid path '{}' was provided.", path);
 419  0
             LOGGER.debug("Invalid path provided", ex);
 420  2
         }
 421  2
         return f.getAbsolutePath().replace('\\', '/');
 422  
     }
 423  
 
 424  
     /**
 425  
      * Returns the position of the last file separator.
 426  
      *
 427  
      * @param file a file path
 428  
      * @return the position of the last file separator
 429  
      */
 430  
     private int getLastFileSeparator(String file) {
 431  1
         if (file.contains("*") || file.contains("?")) {
 432  1
             int p1 = file.indexOf('*');
 433  1
             int p2 = file.indexOf('?');
 434  1
             p1 = p1 > 0 ? p1 : file.length();
 435  1
             p2 = p2 > 0 ? p2 : file.length();
 436  1
             int pos = p1 < p2 ? p1 : p2;
 437  1
             pos = file.lastIndexOf('/', pos);
 438  1
             return pos;
 439  
         } else {
 440  0
             return file.lastIndexOf('/');
 441  
         }
 442  
     }
 443  
 }