View Javadoc

1   /*
2    * This file is part of dependency-check-cli.
3    *
4    * Dependency-check-cli is free software: you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation, either version 3 of the License, or (at your option) any
7    * later version.
8    *
9    * Dependency-check-cli is distributed in the hope that it will be useful, but
10   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12   * details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * dependency-check-cli. If not, see http://www.gnu.org/licenses/.
16   *
17   * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
18   */
19  package org.owasp.dependencycheck.cli;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import org.apache.commons.cli.CommandLine;
24  import org.apache.commons.cli.CommandLineParser;
25  import org.apache.commons.cli.HelpFormatter;
26  import org.apache.commons.cli.Option;
27  import org.apache.commons.cli.OptionBuilder;
28  import org.apache.commons.cli.OptionGroup;
29  import org.apache.commons.cli.Options;
30  import org.apache.commons.cli.ParseException;
31  import org.apache.commons.cli.PosixParser;
32  import org.owasp.dependencycheck.reporting.ReportGenerator.Format;
33  import org.owasp.dependencycheck.utils.Settings;
34  
35  /**
36   * A utility to parse command line arguments for the DependencyCheck.
37   *
38   * @author Jeremy Long (jeremy.long@owasp.org)
39   */
40  public final class CliParser {
41  
42      /**
43       * The command line.
44       */
45      private CommandLine line;
46      /**
47       * The options for the command line parser.
48       */
49      private final Options options = createCommandLineOptions();
50      /**
51       * Indicates whether the arguments are valid.
52       */
53      private boolean isValid = true;
54  
55      /**
56       * Parses the arguments passed in and captures the results for later use.
57       *
58       * @param args the command line arguments
59       * @throws FileNotFoundException is thrown when a 'file' argument does not
60       * point to a file that exists.
61       * @throws ParseException is thrown when a Parse Exception occurs.
62       */
63      public void parse(String[] args) throws FileNotFoundException, ParseException {
64          line = parseArgs(args);
65  
66          if (line != null) {
67              validateArgs();
68          }
69      }
70  
71      /**
72       * Parses the command line arguments.
73       *
74       * @param args the command line arguments
75       * @return the results of parsing the command line arguments
76       * @throws ParseException if the arguments are invalid
77       */
78      private CommandLine parseArgs(String[] args) throws ParseException {
79          final CommandLineParser parser = new PosixParser();
80          return parser.parse(options, args);
81      }
82  
83      /**
84       * Validates that the command line arguments are valid.
85       *
86       * @throws FileNotFoundException if there is a file specified by either the
87       * SCAN or CPE command line arguments that does not exist.
88       * @throws ParseException is thrown if there is an exception parsing the
89       * command line.
90       */
91      private void validateArgs() throws FileNotFoundException, ParseException {
92          if (isRunScan()) {
93              validatePathExists(getScanFiles(), "scan");
94              validatePathExists(getReportDirectory(), "out");
95              if (!line.hasOption(ArgumentName.APP_NAME)) {
96                  throw new ParseException("Missing 'app' argument; the scan cannot be run without the an application name.");
97              }
98              if (line.hasOption(ArgumentName.OUTPUT_FORMAT)) {
99                  final String format = line.getOptionValue(ArgumentName.OUTPUT_FORMAT);
100                 try {
101                     Format.valueOf(format);
102                 } catch (IllegalArgumentException ex) {
103                     final String msg = String.format("An invalid 'format' of '%s' was specified. Supported output formats are XML, HTML, VULN, or ALL", format);
104                     throw new ParseException(msg);
105                 }
106             }
107         }
108     }
109 
110     /**
111      * Validates whether or not the path(s) points at a file that exists; if the
112      * path(s) does not point to an existing file a FileNotFoundException is
113      * thrown.
114      *
115      * @param paths the paths to validate if they exists
116      * @param optType the option being validated (e.g. scan, out, etc.)
117      * @throws FileNotFoundException is thrown if one of the paths being
118      * validated does not exist.
119      */
120     private void validatePathExists(String[] paths, String optType) throws FileNotFoundException {
121         for (String path : paths) {
122             validatePathExists(path, optType);
123         }
124     }
125 
126     /**
127      * Validates whether or not the path points at a file that exists; if the
128      * path does not point to an existing file a FileNotFoundException is
129      * thrown.
130      *
131      * @param path the paths to validate if they exists
132      * @param optType the option being validated (e.g. scan, out, etc.)
133      * @throws FileNotFoundException is thrown if the path being validated does
134      * not exist.
135      */
136     private void validatePathExists(String path, String optType) throws FileNotFoundException {
137         final File f = new File(path);
138         if (!f.exists()) {
139             isValid = false;
140             final String msg = String.format("Invalid '%s' argument: '%s'", optType, path);
141             throw new FileNotFoundException(msg);
142         }
143     }
144 
145     /**
146      * Generates an Options collection that is used to parse the command line
147      * and to display the help message.
148      *
149      * @return the command line options used for parsing the command line
150      */
151     @SuppressWarnings("static-access")
152     private Options createCommandLineOptions() {
153         final Option help = new Option(ArgumentName.HELP_SHORT, ArgumentName.HELP, false,
154                 "Print this message.");
155 
156         final Option version = new Option(ArgumentName.VERSION_SHORT, ArgumentName.VERSION,
157                 false, "Print the version information.");
158 
159         final Option noUpdate = new Option(ArgumentName.DISABLE_AUTO_UPDATE_SHORT, ArgumentName.DISABLE_AUTO_UPDATE,
160                 false, "Disables the automatic updating of the CPE data.");
161 
162         final Option appName = OptionBuilder.withArgName("name").hasArg().withLongOpt(ArgumentName.APP_NAME)
163                 .withDescription("The name of the application being scanned. This is a required argument.")
164                 .create(ArgumentName.APP_NAME_SHORT);
165 
166         final Option connectionTimeout = OptionBuilder.withArgName("timeout").hasArg().withLongOpt(ArgumentName.CONNECTION_TIMEOUT)
167                 .withDescription("The connection timeout (in milliseconds) to use when downloading resources.")
168                 .create(ArgumentName.CONNECTION_TIMEOUT_SHORT);
169 
170         final Option proxyUrl = OptionBuilder.withArgName("url").hasArg().withLongOpt(ArgumentName.PROXY_URL)
171                 .withDescription("The proxy url to use when downloading resources.")
172                 .create(ArgumentName.PROXY_URL_SHORT);
173 
174         final Option proxyPort = OptionBuilder.withArgName("port").hasArg().withLongOpt(ArgumentName.PROXY_PORT)
175                 .withDescription("The proxy port to use when downloading resources.")
176                 .create(ArgumentName.PROXY_PORT_SHORT);
177 
178         final Option proxyUsername = OptionBuilder.withArgName("user").hasArg().withLongOpt(ArgumentName.PROXY_USERNAME)
179                 .withDescription("The proxy username to use when downloading resources.")
180                 .create(ArgumentName.PROXY_USERNAME_SHORT);
181 
182         final Option proxyPassword = OptionBuilder.withArgName("pass").hasArg().withLongOpt(ArgumentName.PROXY_PASSWORD)
183                 .withDescription("The proxy password to use when downloading resources.")
184                 .create(ArgumentName.PROXY_PASSWORD_SHORT);
185 
186         final Option path = OptionBuilder.withArgName("path").hasArg().withLongOpt(ArgumentName.SCAN)
187                 .withDescription("The path to scan - this option can be specified multiple times.")
188                 .create(ArgumentName.SCAN_SHORT);
189 
190         final Option props = OptionBuilder.withArgName("file").hasArg().withLongOpt(ArgumentName.PROP)
191                 .withDescription("A property file to load.")
192                 .create(ArgumentName.PROP_SHORT);
193 
194         final Option data = OptionBuilder.withArgName("path").hasArg().withLongOpt(ArgumentName.DATA_DIRECTORY)
195                 .withDescription("The location of the data directory used to store persistent data. This option should generally not be set.")
196                 .create(ArgumentName.DATA_DIRECTORY_SHORT);
197 
198         final Option out = OptionBuilder.withArgName("folder").hasArg().withLongOpt(ArgumentName.OUT)
199                 .withDescription("The folder to write reports to. This defaults to the current directory.")
200                 .create(ArgumentName.OUT_SHORT);
201 
202         final Option outputFormat = OptionBuilder.withArgName("format").hasArg().withLongOpt(ArgumentName.OUTPUT_FORMAT)
203                 .withDescription("The output format to write to (XML, HTML, VULN, ALL). The default is HTML.")
204                 .create(ArgumentName.OUTPUT_FORMAT_SHORT);
205 
206         final Option verboseLog = OptionBuilder.withArgName("file").hasArg().withLongOpt(ArgumentName.VERBOSE_LOG)
207                 .withDescription("The file path to write verbose logging information.")
208                 .create(ArgumentName.VERBOSE_LOG_SHORT);
209 
210         final Option suppressionFile = OptionBuilder.withArgName("file").hasArg().withLongOpt(ArgumentName.SUPPRESION_FILE)
211                 .withDescription("The file path to the suppression XML file.")
212                 .create(ArgumentName.SUPPRESION_FILE_SHORT);
213 
214 
215         final OptionGroup og = new OptionGroup();
216         og.addOption(path);
217 
218         final Options opts = new Options();
219         opts.addOptionGroup(og);
220         opts.addOption(out);
221         opts.addOption(outputFormat);
222         opts.addOption(appName);
223         opts.addOption(version);
224         opts.addOption(help);
225         opts.addOption(noUpdate);
226         opts.addOption(props);
227         opts.addOption(data);
228         opts.addOption(verboseLog);
229         opts.addOption(suppressionFile);
230         opts.addOption(proxyPort);
231         opts.addOption(proxyUrl);
232         opts.addOption(proxyUsername);
233         opts.addOption(proxyPassword);
234         opts.addOption(connectionTimeout);
235 
236         return opts;
237     }
238 
239     /**
240      * Determines if the 'version' command line argument was passed in.
241      *
242      * @return whether or not the 'version' command line argument was passed in
243      */
244     public boolean isGetVersion() {
245         return (line != null) && line.hasOption(ArgumentName.VERSION);
246     }
247 
248     /**
249      * Determines if the 'help' command line argument was passed in.
250      *
251      * @return whether or not the 'help' command line argument was passed in
252      */
253     public boolean isGetHelp() {
254         return (line != null) && line.hasOption(ArgumentName.HELP);
255     }
256 
257     /**
258      * Determines if the 'scan' command line argument was passed in.
259      *
260      * @return whether or not the 'scan' command line argument was passed in
261      */
262     public boolean isRunScan() {
263         return (line != null) && isValid && line.hasOption(ArgumentName.SCAN);
264     }
265 
266     /**
267      * Displays the command line help message to the standard output.
268      */
269     public void printHelp() {
270         final HelpFormatter formatter = new HelpFormatter();
271         final String nl = System.getProperty("line.separator");
272 
273         formatter.printHelp(Settings.getString("application.name", "DependencyCheck"),
274                 nl + Settings.getString("application.name", "DependencyCheck")
275                 + " can be used to identify if there are any known CVE vulnerabilities in libraries utilized by an application. "
276                 + Settings.getString("application.name", "DependencyCheck")
277                 + " will automatically update required data from the Internet, such as the CVE and CPE data files from nvd.nist.gov." + nl + nl,
278                 options,
279                 "",
280                 true);
281     }
282 
283     /**
284      * Retrieves the file command line parameter(s) specified for the 'scan'
285      * argument.
286      *
287      * @return the file paths specified on the command line for scan
288      */
289     public String[] getScanFiles() {
290         return line.getOptionValues(ArgumentName.SCAN);
291     }
292 
293     /**
294      * Returns the directory to write the reports to specified on the command
295      * line.
296      *
297      * @return the path to the reports directory.
298      */
299     public String getReportDirectory() {
300         return line.getOptionValue(ArgumentName.OUT, ".");
301     }
302 
303     /**
304      * Returns the output format specified on the command line. Defaults to HTML
305      * if no format was specified.
306      *
307      * @return the output format name.
308      */
309     public String getReportFormat() {
310         return line.getOptionValue(ArgumentName.OUTPUT_FORMAT, "HTML");
311     }
312 
313     /**
314      * Returns the application name specified on the command line.
315      *
316      * @return the application name.
317      */
318     public String getApplicationName() {
319         return line.getOptionValue(ArgumentName.APP_NAME);
320     }
321 
322     /**
323      * Returns the connection timeout.
324      *
325      * @return the connection timeout
326      */
327     public String getConnectionTimeout() {
328         return line.getOptionValue(ArgumentName.CONNECTION_TIMEOUT);
329     }
330 
331     /**
332      * Returns the proxy url.
333      *
334      * @return the proxy url
335      */
336     public String getProxyUrl() {
337         return line.getOptionValue(ArgumentName.PROXY_URL);
338     }
339 
340     /**
341      * Returns the proxy port.
342      *
343      * @return the proxy port
344      */
345     public String getProxyPort() {
346         return line.getOptionValue(ArgumentName.PROXY_PORT);
347     }
348 
349     /**
350      * Returns the proxy username.
351      *
352      * @return the proxy username
353      */
354     public String getProxyUsername() {
355         return line.getOptionValue(ArgumentName.PROXY_USERNAME);
356     }
357 
358     /**
359      * Returns the proxy password.
360      *
361      * @return the proxy password
362      */
363     public String getProxyPassword() {
364         return line.getOptionValue(ArgumentName.PROXY_PASSWORD);
365     }
366 
367     /**
368      * Get the value of dataDirectory.
369      *
370      * @return the value of dataDirectory
371      */
372     public String getDataDirectory() {
373         return line.getOptionValue(ArgumentName.DATA_DIRECTORY);
374     }
375 
376     /**
377      * Returns the properties file specified on the command line.
378      *
379      * @return the properties file specified on the command line
380      */
381     public File getPropertiesFile() {
382         final String path = line.getOptionValue(ArgumentName.PROP);
383         if (path != null) {
384             return new File(path);
385         }
386         return null;
387     }
388 
389     /**
390      * Returns the path to the verbose log file.
391      *
392      * @return the path to the verbose log file
393      */
394     public String getVerboseLog() {
395         return line.getOptionValue(ArgumentName.VERBOSE_LOG);
396     }
397 
398     /**
399      * Returns the path to the suppression file.
400      *
401      * @return the path to the suppression file
402      */
403     public String getSuppressionFile() {
404         return line.getOptionValue(ArgumentName.SUPPRESION_FILE);
405     }
406 
407     /**
408      * <p>Prints the manifest information to standard output.</p>
409      * <ul><li>Implementation-Title: ${pom.name}</li>
410      * <li>Implementation-Version: ${pom.version}</li></ul>
411      */
412     public void printVersionInfo() {
413         final String version = String.format("%s version %s",
414                 Settings.getString("application.name", "DependencyCheck"),
415                 Settings.getString("application.version", "Unknown"));
416         System.out.println(version);
417     }
418 
419     /**
420      * Checks if the auto update feature has been disabled. If it has been
421      * disabled via the command line this will return false.
422      *
423      * @return if auto-update is allowed.
424      */
425     public boolean isAutoUpdate() {
426         return (line == null) || !line.hasOption(ArgumentName.DISABLE_AUTO_UPDATE);
427     }
428 
429     /**
430      * A collection of static final strings that represent the possible command
431      * line arguments.
432      */
433     public static class ArgumentName {
434 
435         /**
436          * The long CLI argument name specifying the directory/file to scan.
437          */
438         public static final String SCAN = "scan";
439         /**
440          * The short CLI argument name specifying the directory/file to scan.
441          */
442         public static final String SCAN_SHORT = "s";
443         /**
444          * The long CLI argument name specifying that the CPE/CVE/etc. data
445          * should not be automatically updated.
446          */
447         public static final String DISABLE_AUTO_UPDATE = "noupdate";
448         /**
449          * The short CLI argument name specifying that the CPE/CVE/etc. data
450          * should not be automatically updated.
451          */
452         public static final String DISABLE_AUTO_UPDATE_SHORT = "n";
453         /**
454          * The long CLI argument name specifying the directory to write the
455          * reports to.
456          */
457         public static final String OUT = "out";
458         /**
459          * The short CLI argument name specifying the directory to write the
460          * reports to.
461          */
462         public static final String OUT_SHORT = "o";
463         /**
464          * The long CLI argument name specifying the output format to write the
465          * reports to.
466          */
467         public static final String OUTPUT_FORMAT = "format";
468         /**
469          * The short CLI argument name specifying the output format to write the
470          * reports to.
471          */
472         public static final String OUTPUT_FORMAT_SHORT = "f";
473         /**
474          * The long CLI argument name specifying the name of the application to
475          * be scanned.
476          */
477         public static final String APP_NAME = "app";
478         /**
479          * The short CLI argument name specifying the name of the application to
480          * be scanned.
481          */
482         public static final String APP_NAME_SHORT = "a";
483         /**
484          * The long CLI argument name asking for help.
485          */
486         public static final String HELP = "help";
487         /**
488          * The short CLI argument name asking for help.
489          */
490         public static final String HELP_SHORT = "h";
491         /**
492          * The long CLI argument name asking for the version.
493          */
494         public static final String VERSION_SHORT = "v";
495         /**
496          * The short CLI argument name asking for the version.
497          */
498         public static final String VERSION = "version";
499         /**
500          * The short CLI argument name indicating the proxy port.
501          */
502         public static final String PROXY_PORT_SHORT = "p";
503         /**
504          * The CLI argument name indicating the proxy port.
505          */
506         public static final String PROXY_PORT = "proxyport";
507         /**
508          * The short CLI argument name indicating the proxy url.
509          */
510         public static final String PROXY_URL_SHORT = "u";
511         /**
512          * The CLI argument name indicating the proxy url.
513          */
514         public static final String PROXY_URL = "proxyurl";
515         /**
516          * The short CLI argument name indicating the proxy username.
517          */
518         public static final String PROXY_USERNAME_SHORT = "pu";
519         /**
520          * The CLI argument name indicating the proxy username.
521          */
522         public static final String PROXY_USERNAME = "proxyuser";
523         /**
524          * The short CLI argument name indicating the proxy password.
525          */
526         public static final String PROXY_PASSWORD_SHORT = "pp";
527         /**
528          * The CLI argument name indicating the proxy password.
529          */
530         public static final String PROXY_PASSWORD = "proxypass";
531         /**
532          * The short CLI argument name indicating the connection timeout.
533          */
534         public static final String CONNECTION_TIMEOUT_SHORT = "c";
535         /**
536          * The CLI argument name indicating the connection timeout.
537          */
538         public static final String CONNECTION_TIMEOUT = "connectiontimeout";
539         /**
540          * The short CLI argument name for setting the location of an additional
541          * properties file.
542          */
543         public static final String PROP_SHORT = "p";
544         /**
545          * The CLI argument name for setting the location of an additional
546          * properties file.
547          */
548         public static final String PROP = "propertyfile";
549         /**
550          * The CLI argument name for setting the location of the data directory.
551          */
552         public static final String DATA_DIRECTORY = "data";
553         /**
554          * The short CLI argument name for setting the location of the data
555          * directory.
556          */
557         public static final String DATA_DIRECTORY_SHORT = "d";
558         /**
559          * The CLI argument name for setting the location of the data directory.
560          */
561         public static final String VERBOSE_LOG = "log";
562         /**
563          * The short CLI argument name for setting the location of the data
564          * directory.
565          */
566         public static final String VERBOSE_LOG_SHORT = "l";
567         /**
568          * The CLI argument name for setting the location of the suppression
569          * file.
570          */
571         public static final String SUPPRESION_FILE = "suppression";
572         /**
573          * The short CLI argument name for setting the location of the
574          * suppression file.
575          */
576         public static final String SUPPRESION_FILE_SHORT = "sf";
577     }
578 }