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