View Javadoc
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 java.io.File;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Set;
26  
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  
30  import org.apache.commons.cli.ParseException;
31  import org.apache.tools.ant.DirectoryScanner;
32  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
33  import org.owasp.dependencycheck.dependency.Dependency;
34  import org.owasp.dependencycheck.dependency.Vulnerability;
35  import org.apache.tools.ant.types.LogLevel;
36  import org.owasp.dependencycheck.data.update.exception.UpdateException;
37  import org.owasp.dependencycheck.dependency.naming.Identifier;
38  import org.owasp.dependencycheck.exception.ExceptionCollection;
39  import org.owasp.dependencycheck.exception.ReportException;
40  import org.owasp.dependencycheck.utils.Downloader;
41  import org.owasp.dependencycheck.utils.InvalidSettingException;
42  import org.owasp.dependencycheck.utils.Settings;
43  import org.owasp.dependencycheck.utils.scarf.TelemetryCollector;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  import ch.qos.logback.core.FileAppender;
48  import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
49  import ch.qos.logback.classic.filter.ThresholdFilter;
50  import ch.qos.logback.classic.spi.ILoggingEvent;
51  import ch.qos.logback.classic.Level;
52  import ch.qos.logback.classic.LoggerContext;
53  import io.github.jeremylong.jcs3.slf4j.Slf4jAdapter;
54  
55  import java.util.TreeSet;
56  
57  import org.owasp.dependencycheck.utils.SeverityUtil;
58  
59  /**
60   * The command line interface for the DependencyCheck application.
61   *
62   * @author Jeremy Long
63   */
64  @SuppressWarnings("squid:S106")
65  public class App {
66  
67      /**
68       * The logger.
69       */
70      private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
71      /**
72       * Properties file error message.
73       */
74      private static final String ERROR_LOADING_PROPERTIES_FILE = "Error loading properties file";
75      /**
76       * System specific new line character.
77       */
78      private static final String NEW_LINE = System.getProperty("line.separator", "\n");
79      /**
80       * The configured settings.
81       */
82      private final Settings settings;
83  
84      /**
85       * The main method for the application.
86       *
87       * @param args the command line arguments
88       */
89      @SuppressWarnings("squid:S4823")
90      public static void main(String[] args) {
91          final int exitCode;
92          final App app = new App();
93          exitCode = app.run(args);
94          LOGGER.debug("Exit code: {}", exitCode);
95          System.exit(exitCode);
96      }
97  
98      /**
99       * Builds the App object.
100      */
101     public App() {
102         settings = new Settings();
103     }
104 
105     /**
106      * Builds the App object; this method is used for testing.
107      *
108      * @param settings the configured settings
109      */
110     protected App(Settings settings) {
111         this.settings = settings;
112     }
113 
114     /**
115      * Main CLI entry-point into the application.
116      *
117      * @param args the command line arguments
118      * @return the exit code to return
119      */
120     public int run(String[] args) {
121         int exitCode = 0;
122         final CliParser cli = new CliParser(settings);
123 
124         try {
125             cli.parse(args);
126         } catch (FileNotFoundException ex) {
127             System.err.println(ex.getMessage());
128             cli.printHelp();
129             return 1;
130         } catch (ParseException ex) {
131             System.err.println(ex.getMessage());
132             cli.printHelp();
133             return 2;
134         }
135         final String verboseLog = cli.getStringArgument(CliParser.ARGUMENT.VERBOSE_LOG);
136         if (verboseLog != null) {
137             prepareLogger(verboseLog);
138         }
139 
140         if (cli.isPurge()) {
141             final String connStr = cli.getStringArgument(CliParser.ARGUMENT.CONNECTION_STRING);
142             if (connStr != null) {
143                 LOGGER.error("Unable to purge the database when using a non-default connection string");
144                 exitCode = 3;
145             } else {
146                 try {
147                     populateSettings(cli);
148                     Downloader.getInstance().configure(settings);
149                 } catch (InvalidSettingException ex) {
150                     LOGGER.error(ex.getMessage());
151                     LOGGER.debug(ERROR_LOADING_PROPERTIES_FILE, ex);
152                     exitCode = 4;
153                     return exitCode;
154                 }
155                 try (Engine engine = new Engine(Engine.Mode.EVIDENCE_PROCESSING, settings)) {
156                     if (!engine.purge()) {
157                         exitCode = 7;
158                         return exitCode;
159                     }
160                 } finally {
161                     settings.cleanup();
162                 }
163             }
164         } else if (cli.isGetVersion()) {
165             cli.printVersionInfo();
166         } else if (cli.isUpdateOnly()) {
167             try {
168                 populateSettings(cli);
169                 settings.setBoolean(Settings.KEYS.AUTO_UPDATE, true);
170                 Downloader.getInstance().configure(settings);
171             } catch (InvalidSettingException ex) {
172                 LOGGER.error(ex.getMessage());
173                 LOGGER.debug(ERROR_LOADING_PROPERTIES_FILE, ex);
174                 exitCode = 4;
175                 return exitCode;
176             }
177             try {
178                 runUpdateOnly();
179             } catch (UpdateException ex) {
180                 LOGGER.error(ex.getMessage(), ex);
181                 exitCode = 8;
182             } catch (DatabaseException ex) {
183                 LOGGER.error(ex.getMessage(), ex);
184                 exitCode = 9;
185             } finally {
186                 settings.cleanup();
187             }
188         } else if (cli.isRunScan()) {
189             try {
190                 populateSettings(cli);
191                 Downloader.getInstance().configure(settings);
192                 TelemetryCollector.send(settings);
193             } catch (InvalidSettingException ex) {
194                 LOGGER.error(ex.getMessage(), ex);
195                 LOGGER.debug(ERROR_LOADING_PROPERTIES_FILE, ex);
196                 exitCode = 4;
197                 return exitCode;
198             }
199             try {
200                 final String[] scanFiles = cli.getScanFiles();
201                 if (scanFiles != null) {
202                     exitCode = runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), scanFiles,
203                             cli.getExcludeList(), cli.getSymLinkDepth(), cli.getFailOnCVSS());
204                 } else {
205                     LOGGER.error("No scan files configured");
206                 }
207             } catch (DatabaseException ex) {
208                 LOGGER.error(ex.getMessage());
209                 LOGGER.debug("database exception", ex);
210                 exitCode = 11;
211             } catch (ReportException ex) {
212                 LOGGER.error(ex.getMessage());
213                 LOGGER.debug("report exception", ex);
214                 exitCode = 12;
215             } catch (ExceptionCollection ex) {
216                 if (ex.isFatal()) {
217                     exitCode = 13;
218                     LOGGER.error("One or more fatal errors occurred");
219                 } else {
220                     exitCode = 14;
221                 }
222                 for (Throwable e : ex.getExceptions()) {
223                     if (e.getMessage() != null) {
224                         LOGGER.error(e.getMessage());
225                         LOGGER.debug("unexpected error", e);
226                     }
227                 }
228             } finally {
229                 settings.cleanup();
230             }
231         } else {
232             cli.printHelp();
233         }
234         return exitCode;
235     }
236 
237     /**
238      * Scans the specified directories and writes the dependency reports to the
239      * reportDirectory.
240      *
241      * @param reportDirectory the path to the directory where the reports will
242      * be written
243      * @param outputFormats String[] of output formats of the report
244      * @param applicationName the application name for the report
245      * @param files the files/directories to scan
246      * @param excludes the patterns for files/directories to exclude
247      * @param symLinkDepth the depth that symbolic links will be followed
248      * @param cvssFailScore the score to fail on if a vulnerability is found
249      * @return the exit code if there was an error
250      * @throws ReportException thrown when the report cannot be generated
251      * @throws DatabaseException thrown when there is an error connecting to the
252      * database
253      * @throws ExceptionCollection thrown when an exception occurs during
254      * analysis; there may be multiple exceptions contained within the
255      * collection.
256      */
257     private int runScan(String reportDirectory, String[] outputFormats, String applicationName, String[] files,
258                         String[] excludes, int symLinkDepth, float cvssFailScore) throws DatabaseException,
259             ExceptionCollection, ReportException {
260         Engine engine = null;
261         try {
262             final List<String> antStylePaths = getPaths(files);
263             final Set<File> paths = scanAntStylePaths(antStylePaths, symLinkDepth, excludes);
264 
265             engine = new Engine(settings);
266             engine.scan(paths);
267 
268             ExceptionCollection exCol = null;
269             try {
270                 engine.analyzeDependencies();
271             } catch (ExceptionCollection ex) {
272                 if (ex.isFatal()) {
273                     throw ex;
274                 }
275                 exCol = ex;
276             }
277 
278             try {
279                 for (String outputFormat : outputFormats) {
280                     engine.writeReports(applicationName, new File(reportDirectory), outputFormat, exCol);
281                 }
282             } catch (ReportException ex) {
283                 if (exCol != null) {
284                     exCol.addException(ex);
285                     throw exCol;
286                 } else {
287                     throw ex;
288                 }
289             }
290             if (exCol != null && !exCol.getExceptions().isEmpty()) {
291                 throw exCol;
292             }
293             return determineReturnCode(engine, cvssFailScore);
294         } finally {
295             if (engine != null) {
296                 engine.close();
297             }
298         }
299     }
300 
301     /**
302      * Determines the return code based on if one of the dependencies scanned
303      * has a vulnerability with a CVSS score above the cvssFailScore.
304      *
305      * @param engine the engine used during analysis
306      * @param cvssFailScore the max allowed CVSS score
307      * @return returns <code>1</code> if a severe enough vulnerability is
308      * identified; otherwise <code>0</code>
309      */
310     private int determineReturnCode(Engine engine, float cvssFailScore) {
311         int retCode = 0;
312         //Set the exit code based on whether we found a high enough vulnerability
313         final StringBuilder ids = new StringBuilder();
314         for (Dependency d : engine.getDependencies()) {
315             boolean addName = true;
316             for (Vulnerability v : d.getVulnerabilities()) {
317                 final double cvssV2 = v.getCvssV2() != null && v.getCvssV2().getCvssData() != null
318                         && v.getCvssV2().getCvssData().getBaseScore() != null ? v.getCvssV2().getCvssData().getBaseScore() : -1;
319                 final double cvssV3 = v.getCvssV3() != null && v.getCvssV3().getCvssData() != null
320                         && v.getCvssV3().getCvssData().getBaseScore() != null ? v.getCvssV3().getCvssData().getBaseScore() : -1;
321                 final double cvssV4 = v.getCvssV4() != null && v.getCvssV4().getCvssData() != null
322                         && v.getCvssV4().getCvssData().getBaseScore() != null ? v.getCvssV4().getCvssData().getBaseScore() : -1;
323                 final boolean useUnscored = cvssV2 == -1 && cvssV3 == -1 && cvssV4 == -1;
324                 final double unscoredCvss = (useUnscored && v.getUnscoredSeverity() != null) ? SeverityUtil.estimateCvssV2(v.getUnscoredSeverity()) : -1;
325 
326                 if (cvssV2 >= cvssFailScore
327                         || cvssV3 >= cvssFailScore
328                         || cvssV4 >= cvssFailScore
329                         || unscoredCvss >= cvssFailScore
330                         //safety net to fail on any if for some reason the above misses on 0
331                         || (cvssFailScore <= 0.0f)) {
332                     double score = 0.0;
333                     if (cvssV4 >= 0.0) {
334                         score = cvssV4;
335                     } else if (cvssV3 >= 0.0) {
336                         score = cvssV3;
337                     } else if (cvssV2 >= 0.0) {
338                         score = cvssV2;
339                     } else if (unscoredCvss >= 0.0) {
340                         score = unscoredCvss;
341                     }
342                     if (addName) {
343                         addName = false;
344                         ids.append(NEW_LINE).append(d.getFileName()).append(" (")
345                                 .append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
346                                         .map(Identifier::getValue)
347                                         .collect(Collectors.joining(", ")))
348                                 .append("): ");
349                         ids.append(v.getName()).append('(').append(score).append(')');
350                     } else {
351                         ids.append(", ").append(v.getName()).append('(').append(score).append(')');
352                     }
353                 }
354             }
355         }
356         if (ids.length() > 0) {
357             LOGGER.error(
358                     String.format("%n%nOne or more dependencies were identified with vulnerabilities that have a CVSS score greater than or "
359                             + "equal to '%.1f': %n%s%n%nSee the dependency-check report for more details.%n%n", cvssFailScore, ids)
360             );
361 
362             retCode = 15;
363         }
364 
365         return retCode;
366     }
367 
368     /**
369      * Scans the give Ant Style paths and collects the actual files.
370      *
371      * @param antStylePaths a list of ant style paths to scan for actual files
372      * @param symLinkDepth the depth to traverse symbolic links
373      * @param excludes an array of ant style excludes
374      * @return returns the set of identified files
375      */
376     private Set<File> scanAntStylePaths(List<String> antStylePaths, int symLinkDepth, String[] excludes) {
377         final Set<File> paths = new TreeSet<>();
378         for (String file : antStylePaths) {
379             LOGGER.debug("Scanning {}", file);
380             final DirectoryScanner scanner = new DirectoryScanner();
381             String include = file.replace('\\', '/');
382             final File baseDir;
383             final int pos = getLastFileSeparator(include);
384             String tmpBase = include.substring(0, pos);
385             //fix for windows style paths scanning c:/temp.
386             if (tmpBase.endsWith(":")) {
387                 tmpBase += "/";
388             }
389             final String tmpInclude = include.substring(pos + 1);
390             if (tmpInclude.indexOf('*') >= 0 || tmpInclude.indexOf('?') >= 0
391                     || new File(include).isFile()) {
392                 baseDir = new File(tmpBase);
393                 include = tmpInclude;
394             } else {
395                 baseDir = new File(tmpBase, tmpInclude);
396                 include = "**/*";
397             }
398             LOGGER.debug("BaseDir: " + baseDir);
399             LOGGER.debug("Include: " + include);
400             scanner.setBasedir(baseDir);
401             final String[] includes = {include};
402             scanner.setIncludes(includes);
403             scanner.setMaxLevelsOfSymlinks(symLinkDepth);
404             if (symLinkDepth <= 0) {
405                 scanner.setFollowSymlinks(false);
406             }
407             if (excludes != null && excludes.length > 0) {
408                 for (String e : excludes) {
409                     LOGGER.debug("Exclude: " + e);
410                 }
411                 scanner.addExcludes(excludes);
412             }
413             scanner.scan();
414             if (scanner.getIncludedFilesCount() > 0) {
415                 for (String s : scanner.getIncludedFiles()) {
416                     final File f = new File(baseDir, s);
417                     LOGGER.debug("Found file {}", f);
418                     paths.add(f);
419                 }
420             }
421         }
422         return paths;
423     }
424 
425     /**
426      * Determines the ant style paths from the given array of files.
427      *
428      * @param files an array of file paths
429      * @return a list containing ant style paths
430      */
431     private List<String> getPaths(String[] files) {
432         final List<String> antStylePaths = new ArrayList<>();
433         for (String file : files) {
434             final String antPath = ensureCanonicalPath(file);
435             antStylePaths.add(antPath);
436         }
437         return antStylePaths;
438     }
439 
440     /**
441      * Only executes the update phase of dependency-check.
442      *
443      * @throws UpdateException thrown if there is an error updating
444      * @throws DatabaseException thrown if a fatal error occurred and a
445      * connection to the database could not be established
446      */
447     private void runUpdateOnly() throws UpdateException, DatabaseException {
448         try (Engine engine = new Engine(settings)) {
449             engine.doUpdates();
450         }
451     }
452 
453     //CSOFF: MethodLength
454 
455     /**
456      * Updates the global Settings.
457      *
458      * @param cli a reference to the CLI Parser that contains the command line
459      * arguments used to set the corresponding settings in the core engine.
460      * @throws InvalidSettingException thrown when a user defined properties
461      * file is unable to be loaded.
462      */
463     protected void populateSettings(CliParser cli) throws InvalidSettingException {
464         String name = System.getenv("ODC_NAME") != null ? System.getenv("ODC_NAME") : "dependency-check-cli";
465         if (name.isBlank()) {
466             name = "dependency-check-cli";
467         }
468         name = name.replace("/", "-").replace(" ", "_");
469         settings.setString(Settings.KEYS.APPLICATION_NAME, name);
470         final File propertiesFile = cli.getFileArgument(CliParser.ARGUMENT.PROP);
471         if (propertiesFile != null) {
472             try {
473                 settings.mergeProperties(propertiesFile);
474             } catch (FileNotFoundException ex) {
475                 throw new InvalidSettingException("Unable to find properties file '" + propertiesFile.getPath() + "'", ex);
476             } catch (IOException ex) {
477                 throw new InvalidSettingException("Error reading properties file '" + propertiesFile.getPath() + "'", ex);
478             }
479         }
480         final String dataDirectory = cli.getStringArgument(CliParser.ARGUMENT.DATA_DIRECTORY);
481         if (dataDirectory != null) {
482             settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
483         } else if (System.getProperty("basedir") != null) {
484             final File dataDir = new File(System.getProperty("basedir"), "data");
485             settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
486         } else {
487             final File jarPath = new File(App.class
488                     .getProtectionDomain().getCodeSource().getLocation().getPath());
489             final File base = jarPath.getParentFile();
490             final String sub = settings.getString(Settings.KEYS.DATA_DIRECTORY);
491             final File dataDir = new File(base, sub);
492             settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
493         }
494         final Boolean autoUpdate = cli.hasOption(CliParser.ARGUMENT.DISABLE_AUTO_UPDATE) != null ? false : null;
495         settings.setBooleanIfNotNull(Settings.KEYS.AUTO_UPDATE, autoUpdate);
496         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER,
497                 cli.getStringArgument(CliParser.ARGUMENT.PROXY_SERVER));
498         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT,
499                 cli.getStringArgument(CliParser.ARGUMENT.PROXY_PORT));
500         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME,
501                 cli.getStringArgument(CliParser.ARGUMENT.PROXY_USERNAME));
502         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD,
503                 cli.getStringArgument(CliParser.ARGUMENT.PROXY_PASSWORD, Settings.KEYS.PROXY_PASSWORD));
504         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_NON_PROXY_HOSTS,
505                 cli.getStringArgument(CliParser.ARGUMENT.NON_PROXY_HOSTS));
506         settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT,
507                 cli.getStringArgument(CliParser.ARGUMENT.CONNECTION_TIMEOUT));
508         settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_READ_TIMEOUT,
509                 cli.getStringArgument(CliParser.ARGUMENT.CONNECTION_READ_TIMEOUT));
510         settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE,
511                 cli.getStringArgument(CliParser.ARGUMENT.HINTS_FILE));
512         settings.setArrayIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE,
513                 cli.getStringArguments(CliParser.ARGUMENT.SUPPRESSION_FILES));
514         settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE_USER,
515                 cli.getStringArgument(CliParser.ARGUMENT.SUPPRESSION_FILE_USER));
516         settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE_PASSWORD,
517                 cli.getStringArgument(CliParser.ARGUMENT.SUPPRESSION_FILE_PASSWORD));
518         settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE_BEARER_TOKEN,
519                 cli.getStringArgument(CliParser.ARGUMENT.SUPPRESSION_FILE_BEARER_TOKEN));
520         //File Type Analyzer Settings
521         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED,
522                 cli.hasOption(CliParser.ARGUMENT.EXPERIMENTAL));
523         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RETIRED_ENABLED,
524                 cli.hasOption(CliParser.ARGUMENT.RETIRED));
525         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_GOLANG_PATH,
526                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_GO));
527         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_YARN_PATH,
528                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_YARN));
529         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_PNPM_PATH,
530                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_PNPM));
531         settings.setBooleanIfNotNull(Settings.KEYS.PRETTY_PRINT,
532                 cli.hasOption(CliParser.ARGUMENT.PRETTY_PRINT));
533         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL,
534                 cli.getStringArgument(CliParser.ARGUMENT.RETIREJS_URL));
535         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_USER,
536                 cli.getStringArgument(CliParser.ARGUMENT.RETIREJS_URL_USER));
537         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_PASSWORD,
538                 cli.getStringArgument(CliParser.ARGUMENT.RETIREJS_URL_PASSWORD));
539         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_BEARER_TOKEN,
540                 cli.getStringArgument(CliParser.ARGUMENT.RETIREJS_URL_BEARER_TOKEN));
541         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_FORCEUPDATE,
542                 cli.hasOption(CliParser.ARGUMENT.RETIRE_JS_FORCEUPDATE));
543         settings.setStringIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_FILTERS,
544                 cli.getStringArgument(CliParser.ARGUMENT.RETIREJS_FILTERS));
545         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_FILTER_NON_VULNERABLE,
546                 cli.hasOption(CliParser.ARGUMENT.RETIREJS_FILTER_NON_VULNERABLE));
547         settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED,
548                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_JAR, Settings.KEYS.ANALYZER_JAR_ENABLED));
549         settings.setBoolean(Settings.KEYS.UPDATE_VERSION_CHECK_ENABLED,
550                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_VERSION_CHECK, Settings.KEYS.UPDATE_VERSION_CHECK_ENABLED));
551         settings.setBoolean(Settings.KEYS.ANALYZER_MSBUILD_PROJECT_ENABLED,
552                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_MSBUILD, Settings.KEYS.ANALYZER_MSBUILD_PROJECT_ENABLED));
553         settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED,
554                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_ARCHIVE, Settings.KEYS.ANALYZER_ARCHIVE_ENABLED));
555         settings.setBoolean(Settings.KEYS.ANALYZER_KNOWN_EXPLOITED_ENABLED,
556                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_KEV, Settings.KEYS.ANALYZER_KNOWN_EXPLOITED_ENABLED));
557         settings.setStringIfNotNull(Settings.KEYS.KEV_URL,
558                 cli.getStringArgument(CliParser.ARGUMENT.KEV_URL));
559         settings.setStringIfNotNull(Settings.KEYS.KEV_USER,
560                 cli.getStringArgument(CliParser.ARGUMENT.KEV_USER));
561         settings.setStringIfNotNull(Settings.KEYS.KEV_PASSWORD,
562                 cli.getStringArgument(CliParser.ARGUMENT.KEV_PASSWORD));
563         settings.setStringIfNotNull(Settings.KEYS.KEV_BEARER_TOKEN,
564                 cli.getStringArgument(CliParser.ARGUMENT.KEV_BEARER_TOKEN));
565         settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED,
566                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_PY_DIST, Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED));
567         settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED,
568                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_PY_PKG, Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED));
569         settings.setBoolean(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED,
570                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_AUTOCONF, Settings.KEYS.ANALYZER_AUTOCONF_ENABLED));
571         settings.setBoolean(Settings.KEYS.ANALYZER_MAVEN_INSTALL_ENABLED,
572                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_MAVEN_INSTALL, Settings.KEYS.ANALYZER_MAVEN_INSTALL_ENABLED));
573         settings.setBoolean(Settings.KEYS.ANALYZER_PIP_ENABLED,
574                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_PIP, Settings.KEYS.ANALYZER_PIP_ENABLED));
575         settings.setBoolean(Settings.KEYS.ANALYZER_PIPFILE_ENABLED,
576                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_PIPFILE, Settings.KEYS.ANALYZER_PIPFILE_ENABLED));
577         settings.setBoolean(Settings.KEYS.ANALYZER_POETRY_ENABLED,
578                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_POETRY, Settings.KEYS.ANALYZER_POETRY_ENABLED));
579         settings.setBoolean(Settings.KEYS.ANALYZER_CMAKE_ENABLED,
580                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_CMAKE, Settings.KEYS.ANALYZER_CMAKE_ENABLED));
581         settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED,
582                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_NUSPEC, Settings.KEYS.ANALYZER_NUSPEC_ENABLED));
583         settings.setBoolean(Settings.KEYS.ANALYZER_NUGETCONF_ENABLED,
584                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_NUGETCONF, Settings.KEYS.ANALYZER_NUGETCONF_ENABLED));
585         settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED,
586                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_ASSEMBLY, Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED));
587         settings.setBoolean(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED,
588                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_BUNDLE_AUDIT, Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED));
589         settings.setBoolean(Settings.KEYS.ANALYZER_FILE_NAME_ENABLED,
590                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_FILENAME, Settings.KEYS.ANALYZER_FILE_NAME_ENABLED));
591         settings.setBoolean(Settings.KEYS.ANALYZER_MIX_AUDIT_ENABLED,
592                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_MIX_AUDIT, Settings.KEYS.ANALYZER_MIX_AUDIT_ENABLED));
593         settings.setBoolean(Settings.KEYS.ANALYZER_OPENSSL_ENABLED,
594                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_OPENSSL, Settings.KEYS.ANALYZER_OPENSSL_ENABLED));
595         settings.setBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED,
596                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_COMPOSER, Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED));
597         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_SKIP_DEV,
598                 cli.hasOption(CliParser.ARGUMENT.COMPOSER_LOCK_SKIP_DEV));
599         settings.setBoolean(Settings.KEYS.ANALYZER_CPANFILE_ENABLED,
600                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_CPAN, Settings.KEYS.ANALYZER_CPANFILE_ENABLED));
601         settings.setBoolean(Settings.KEYS.ANALYZER_GOLANG_DEP_ENABLED,
602                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_GO_DEP, Settings.KEYS.ANALYZER_GOLANG_DEP_ENABLED));
603         settings.setBoolean(Settings.KEYS.ANALYZER_GOLANG_MOD_ENABLED,
604                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_GOLANG_MOD, Settings.KEYS.ANALYZER_GOLANG_MOD_ENABLED));
605         settings.setBoolean(Settings.KEYS.ANALYZER_DART_ENABLED,
606                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_DART, Settings.KEYS.ANALYZER_DART_ENABLED));
607         settings.setBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED,
608                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_NODE_JS, Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED));
609         settings.setBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_ENABLED,
610                 !cli.isNodeAuditDisabled());
611         settings.setBoolean(Settings.KEYS.ANALYZER_YARN_AUDIT_ENABLED,
612                 !cli.isYarnAuditDisabled());
613         settings.setBoolean(Settings.KEYS.ANALYZER_PNPM_AUDIT_ENABLED,
614                 !cli.isPnpmAuditDisabled());
615         settings.setBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE,
616                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_NODE_AUDIT_CACHE, Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE));
617         settings.setBoolean(Settings.KEYS.ANALYZER_RETIREJS_ENABLED,
618                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_RETIRE_JS, Settings.KEYS.ANALYZER_RETIREJS_ENABLED));
619         settings.setBoolean(Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED,
620                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_SWIFT, Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED));
621         settings.setBoolean(Settings.KEYS.ANALYZER_SWIFT_PACKAGE_RESOLVED_ENABLED,
622                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_SWIFT_RESOLVED, Settings.KEYS.ANALYZER_SWIFT_PACKAGE_RESOLVED_ENABLED));
623         settings.setBoolean(Settings.KEYS.ANALYZER_COCOAPODS_ENABLED,
624                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_COCOAPODS, Settings.KEYS.ANALYZER_COCOAPODS_ENABLED));
625         settings.setBoolean(Settings.KEYS.ANALYZER_CARTHAGE_ENABLED,
626                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_CARTHAGE, Settings.KEYS.ANALYZER_CARTHAGE_ENABLED));
627         settings.setBoolean(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED,
628                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_RUBYGEMS, Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED));
629         settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED,
630                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_CENTRAL, Settings.KEYS.ANALYZER_CENTRAL_ENABLED));
631         settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_USE_CACHE,
632                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_CENTRAL_CACHE, Settings.KEYS.ANALYZER_CENTRAL_USE_CACHE));
633         settings.setBoolean(Settings.KEYS.ANALYZER_OSSINDEX_ENABLED,
634                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_OSSINDEX, Settings.KEYS.ANALYZER_OSSINDEX_ENABLED));
635         settings.setBoolean(Settings.KEYS.ANALYZER_OSSINDEX_USE_CACHE,
636                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_OSSINDEX_CACHE, Settings.KEYS.ANALYZER_OSSINDEX_USE_CACHE));
637 
638         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_SKIPDEV,
639                 cli.hasOption(CliParser.ARGUMENT.NODE_PACKAGE_SKIP_DEV_DEPENDENCIES));
640         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_AUDIT_SKIPDEV,
641                 cli.hasOption(CliParser.ARGUMENT.DISABLE_NODE_AUDIT_SKIPDEV));
642         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NEXUS_ENABLED,
643                 cli.hasOption(CliParser.ARGUMENT.ENABLE_NEXUS));
644         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_URL,
645                 cli.getStringArgument(CliParser.ARGUMENT.CENTRAL_URL));
646         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_USER,
647                 cli.getStringArgument(CliParser.ARGUMENT.CENTRAL_USERNAME));
648         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_PASSWORD,
649                 cli.getStringArgument(CliParser.ARGUMENT.CENTRAL_PASSWORD));
650         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_BEARER_TOKEN,
651                 cli.getStringArgument(CliParser.ARGUMENT.CENTRAL_BEARER_TOKEN));
652         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_OSSINDEX_URL,
653                 cli.getStringArgument(CliParser.ARGUMENT.OSSINDEX_URL));
654         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_OSSINDEX_USER,
655                 cli.getStringArgument(CliParser.ARGUMENT.OSSINDEX_USERNAME));
656         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_OSSINDEX_PASSWORD,
657                 cli.getStringArgument(CliParser.ARGUMENT.OSSINDEX_PASSWORD, Settings.KEYS.ANALYZER_OSSINDEX_PASSWORD));
658         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_OSSINDEX_WARN_ONLY_ON_REMOTE_ERRORS,
659                 cli.getStringArgument(CliParser.ARGUMENT.OSSINDEX_WARN_ONLY_ON_REMOTE_ERRORS,
660                         Settings.KEYS.ANALYZER_OSSINDEX_WARN_ONLY_ON_REMOTE_ERRORS));
661         settings.setFloat(Settings.KEYS.JUNIT_FAIL_ON_CVSS,
662                 cli.getFloatArgument(CliParser.ARGUMENT.FAIL_JUNIT_ON_CVSS, 0));
663         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_ARTIFACTORY_ENABLED,
664                 cli.hasOption(CliParser.ARGUMENT.ARTIFACTORY_ENABLED));
665         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_ARTIFACTORY_PARALLEL_ANALYSIS,
666                 cli.getBooleanArgument(CliParser.ARGUMENT.ARTIFACTORY_PARALLEL_ANALYSIS));
667         settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_ARTIFACTORY_USES_PROXY,
668                 cli.getBooleanArgument(CliParser.ARGUMENT.ARTIFACTORY_USES_PROXY));
669         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ARTIFACTORY_URL,
670                 cli.getStringArgument(CliParser.ARGUMENT.ARTIFACTORY_URL));
671         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ARTIFACTORY_API_USERNAME,
672                 cli.getStringArgument(CliParser.ARGUMENT.ARTIFACTORY_USERNAME));
673         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ARTIFACTORY_API_TOKEN,
674                 cli.getStringArgument(CliParser.ARGUMENT.ARTIFACTORY_API_TOKEN));
675         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ARTIFACTORY_BEARER_TOKEN,
676                 cli.getStringArgument(CliParser.ARGUMENT.ARTIFACTORY_BEARER_TOKEN));
677         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_MIX_AUDIT_PATH,
678                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_MIX_AUDIT));
679         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH,
680                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_BUNDLE_AUDIT));
681         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_WORKING_DIRECTORY,
682                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_BUNDLE_AUDIT_WORKING_DIRECTORY));
683         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL,
684                 cli.getStringArgument(CliParser.ARGUMENT.NEXUS_URL));
685         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_USER,
686                 cli.getStringArgument(CliParser.ARGUMENT.NEXUS_USERNAME));
687         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_PASSWORD,
688                 cli.getStringArgument(CliParser.ARGUMENT.NEXUS_PASSWORD, Settings.KEYS.ANALYZER_NEXUS_PASSWORD));
689         //TODO deprecate this in favor of non-proxy host
690         final boolean nexusUsesProxy = cli.isNexusUsesProxy();
691         settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
692         settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME,
693                 cli.getStringArgument(CliParser.ARGUMENT.DB_DRIVER));
694         settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH,
695                 cli.getStringArgument(CliParser.ARGUMENT.DB_DRIVER_PATH));
696         settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING,
697                 cli.getStringArgument(CliParser.ARGUMENT.CONNECTION_STRING));
698         settings.setStringIfNotEmpty(Settings.KEYS.DB_USER,
699                 cli.getStringArgument(CliParser.ARGUMENT.DB_NAME));
700         settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD,
701                 cli.getStringArgument(CliParser.ARGUMENT.DB_PASSWORD, Settings.KEYS.DB_PASSWORD));
702         settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS,
703                 cli.getStringArgument(CliParser.ARGUMENT.ADDITIONAL_ZIP_EXTENSIONS));
704         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_DOTNET_PATH,
705                 cli.getStringArgument(CliParser.ARGUMENT.PATH_TO_CORE));
706 
707         String key = cli.getStringArgument(CliParser.ARGUMENT.NVD_API_KEY);
708         if (key != null) {
709             if ((key.startsWith("\"") && key.endsWith("\"") || (key.startsWith("'") && key.endsWith("'")))) {
710                 key = key.substring(1, key.length() - 1);
711             }
712             settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_KEY, key);
713         }
714         settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_ENDPOINT,
715                 cli.getStringArgument(CliParser.ARGUMENT.NVD_API_ENDPOINT));
716         settings.setIntIfNotNull(Settings.KEYS.NVD_API_DELAY, cli.getIntegerValue(CliParser.ARGUMENT.NVD_API_DELAY));
717         settings.setIntIfNotNull(Settings.KEYS.NVD_API_RESULTS_PER_PAGE, cli.getIntegerValue(CliParser.ARGUMENT.NVD_API_RESULTS_PER_PAGE));
718         settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_DATAFEED_URL, cli.getStringArgument(CliParser.ARGUMENT.NVD_API_DATAFEED_URL));
719         settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_DATAFEED_USER, cli.getStringArgument(CliParser.ARGUMENT.NVD_API_DATAFEED_USER));
720         settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_DATAFEED_PASSWORD, cli.getStringArgument(CliParser.ARGUMENT.NVD_API_DATAFEED_PASSWORD));
721         settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_DATAFEED_BEARER_TOKEN, cli.getStringArgument(CliParser.ARGUMENT.NVD_API_DATAFEED_BEARER_TOKEN));
722         settings.setIntIfNotNull(Settings.KEYS.NVD_API_MAX_RETRY_COUNT, cli.getIntegerValue(CliParser.ARGUMENT.NVD_API_MAX_RETRY_COUNT));
723         settings.setIntIfNotNull(Settings.KEYS.NVD_API_VALID_FOR_HOURS, cli.getIntegerValue(CliParser.ARGUMENT.NVD_API_VALID_FOR_HOURS));
724 
725         settings.setStringIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_URL,
726                 cli.getStringArgument(CliParser.ARGUMENT.HOSTED_SUPPRESSIONS_URL));
727         settings.setStringIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_USER,
728                 cli.getStringArgument(CliParser.ARGUMENT.HOSTED_SUPPRESSIONS_USER));
729         settings.setStringIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_PASSWORD,
730                 cli.getStringArgument(CliParser.ARGUMENT.HOSTED_SUPPRESSIONS_PASSWORD));
731         settings.setStringIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_BEARER_TOKEN,
732                 cli.getStringArgument(CliParser.ARGUMENT.HOSTED_SUPPRESSIONS_BEARER_TOKEN));
733         settings.setBoolean(Settings.KEYS.HOSTED_SUPPRESSIONS_ENABLED,
734                 !cli.isDisabled(CliParser.ARGUMENT.DISABLE_HOSTED_SUPPRESSIONS, Settings.KEYS.HOSTED_SUPPRESSIONS_ENABLED));
735         settings.setBooleanIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_FORCEUPDATE,
736                 cli.hasOption(CliParser.ARGUMENT.HOSTED_SUPPRESSIONS_FORCEUPDATE));
737         settings.setIntIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_VALID_FOR_HOURS,
738                 cli.getIntegerValue(CliParser.ARGUMENT.HOSTED_SUPPRESSIONS_VALID_FOR_HOURS));
739     }
740 
741     //CSON: MethodLength
742 
743     /**
744      * Creates a file appender and adds it to logback.
745      *
746      * @param verboseLog the path to the verbose log file
747      */
748     private void prepareLogger(String verboseLog) {
749         final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
750         final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
751         encoder.setPattern("%d %C:%L%n%-5level - %msg%n");
752         encoder.setContext(context);
753         encoder.start();
754         final FileAppender<ILoggingEvent> fa = new FileAppender<>();
755         fa.setAppend(true);
756         fa.setEncoder(encoder);
757         fa.setContext(context);
758         fa.setFile(verboseLog);
759         final File f = new File(verboseLog);
760         String name = f.getName();
761         final int i = name.lastIndexOf('.');
762         if (i > 1) {
763             name = name.substring(0, i);
764         }
765         fa.setName(name);
766         fa.start();
767         final ch.qos.logback.classic.Logger rootLogger = context.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
768         rootLogger.setLevel(Level.DEBUG);
769         final ThresholdFilter filter = new ThresholdFilter();
770         filter.setLevel(LogLevel.INFO.getValue());
771         filter.setContext(context);
772         filter.start();
773         rootLogger.iteratorForAppenders().forEachRemaining(action -> action.addFilter(filter));
774         rootLogger.addAppender(fa);
775     }
776 
777     /**
778      * Takes a path and resolves it to be a canonical &amp; absolute path. The
779      * caveats are that this method will take an Ant style file selector path
780      * (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at
781      * least to the left of the first * or ?).
782      *
783      * @param path the path to canonicalize
784      * @return the canonical path
785      */
786     protected String ensureCanonicalPath(String path) {
787         final String basePath;
788         String wildCards = null;
789         final String file = path.replace('\\', '/');
790         if (file.contains("*") || file.contains("?")) {
791 
792             int pos = getLastFileSeparator(file);
793             if (pos < 0) {
794                 return file;
795             }
796             pos += 1;
797             basePath = file.substring(0, pos);
798             wildCards = file.substring(pos);
799         } else {
800             basePath = file;
801         }
802 
803         File f = new File(basePath);
804         try {
805             f = f.getCanonicalFile();
806             if (wildCards != null) {
807                 f = new File(f, wildCards);
808             }
809         } catch (IOException ex) {
810             LOGGER.warn("Invalid path '{}' was provided.", path);
811             LOGGER.debug("Invalid path provided", ex);
812         }
813         return f.getAbsolutePath().replace('\\', '/');
814     }
815 
816     /**
817      * Returns the position of the last file separator.
818      *
819      * @param file a file path
820      * @return the position of the last file separator
821      */
822     @SuppressWarnings("ManualMinMaxCalculation")
823     private int getLastFileSeparator(String file) {
824         if (file.contains("*") || file.contains("?")) {
825             int p1 = file.indexOf('*');
826             int p2 = file.indexOf('?');
827             p1 = p1 > 0 ? p1 : file.length();
828             p2 = p2 > 0 ? p2 : file.length();
829             int pos = p1 < p2 ? p1 : p2;
830             pos = file.lastIndexOf('/', pos);
831             return pos;
832         } else {
833             return file.lastIndexOf('/');
834         }
835     }
836 }