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