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