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