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 OptionGroup og = new OptionGroup();
    211         og.addOption(path);
    212
    213         final Options opts = new Options();
    214         opts.addOptionGroup(og);
    215         opts.addOption(out);
    216         opts.addOption(outputFormat);
    217         opts.addOption(appName);
    218         opts.addOption(version);
    219         opts.addOption(help);
    220         opts.addOption(noUpdate);
    221         opts.addOption(props);
    222         opts.addOption(data);
    223         opts.addOption(verboseLog);
    224         opts.addOption(proxyPort);
    225         opts.addOption(proxyUrl);
    226         opts.addOption(proxyUsername);
    227         opts.addOption(proxyPassword);
    228         opts.addOption(connectionTimeout);
    229
    230         return opts;
    231     }
    232
    233     /**
    234      * Determines if the 'version' command line argument was passed in.
    235      *
    236      * @return whether or not the 'version' command line argument was passed in
    237      */
    238     public boolean isGetVersion() {
    239         return (line != null) && line.hasOption(ArgumentName.VERSION);
    240     }
    241
    242     /**
    243      * Determines if the 'help' command line argument was passed in.
    244      *
    245      * @return whether or not the 'help' command line argument was passed in
    246      */
    247     public boolean isGetHelp() {
    248         return (line != null) && line.hasOption(ArgumentName.HELP);
    249     }
    250
    251     /**
    252      * Determines if the 'scan' command line argument was passed in.
    253      *
    254      * @return whether or not the 'scan' command line argument was passed in
    255      */
    256     public boolean isRunScan() {
    257         return (line != null) && isValid && line.hasOption(ArgumentName.SCAN);
    258     }
    259
    260     /**
    261      * Displays the command line help message to the standard output.
    262      */
    263     public void printHelp() {
    264         final HelpFormatter formatter = new HelpFormatter();
    265         final String nl = System.getProperty("line.separator");
    266
    267         formatter.printHelp(Settings.getString("application.name", "DependencyCheck"),
    268                 nl + Settings.getString("application.name", "DependencyCheck")
    269                 + " can be used to identify if there are any known CVE vulnerabilities in libraries utilized by an application. "
    270                 + Settings.getString("application.name", "DependencyCheck")
    271                 + " will automatically update required data from the Internet, such as the CVE and CPE data files from nvd.nist.gov." + nl + nl,
    272                 options,
    273                 "",
    274                 true);
    275     }
    276
    277     /**
    278      * Retrieves the file command line parameter(s) specified for the 'scan'
    279      * argument.
    280      *
    281      * @return the file paths specified on the command line for scan
    282      */
    283     public String[] getScanFiles() {
    284         return line.getOptionValues(ArgumentName.SCAN);
    285     }
    286
    287     /**
    288      * Returns the directory to write the reports to specified on the command
    289      * line.
    290      *
    291      * @return the path to the reports directory.
    292      */
    293     public String getReportDirectory() {
    294         return line.getOptionValue(ArgumentName.OUT, ".");
    295     }
    296
    297     /**
    298      * Returns the output format specified on the command line. Defaults to HTML
    299      * if no format was specified.
    300      *
    301      * @return the output format name.
    302      */
    303     public String getReportFormat() {
    304         return line.getOptionValue(ArgumentName.OUTPUT_FORMAT, "HTML");
    305     }
    306
    307     /**
    308      * Returns the application name specified on the command line.
    309      *
    310      * @return the application name.
    311      */
    312     public String getApplicationName() {
    313         return line.getOptionValue(ArgumentName.APP_NAME);
    314     }
    315
    316     /**
    317      * Returns the connection timeout.
    318      *
    319      * @return the connection timeout
    320      */
    321     public String getConnectionTimeout() {
    322         return line.getOptionValue(ArgumentName.CONNECTION_TIMEOUT);
    323     }
    324
    325     /**
    326      * Returns the proxy url.
    327      *
    328      * @return the proxy url
    329      */
    330     public String getProxyUrl() {
    331         return line.getOptionValue(ArgumentName.PROXY_URL);
    332     }
    333
    334     /**
    335      * Returns the proxy port.
    336      *
    337      * @return the proxy port
    338      */
    339     public String getProxyPort() {
    340         return line.getOptionValue(ArgumentName.PROXY_PORT);
    341     }
    342
    343     /**
    344      * Returns the proxy username.
    345      *
    346      * @return the proxy username
    347      */
    348     public String getProxyUsername() {
    349         return line.getOptionValue(ArgumentName.PROXY_USERNAME);
    350     }
    351
    352     /**
    353      * Returns the proxy password.
    354      *
    355      * @return the proxy password
    356      */
    357     public String getProxyPassword() {
    358         return line.getOptionValue(ArgumentName.PROXY_PASSWORD);
    359     }
    360
    361     /**
    362      * Get the value of dataDirectory.
    363      *
    364      * @return the value of dataDirectory
    365      */
    366     public String getDataDirectory() {
    367         return line.getOptionValue(ArgumentName.DATA_DIRECTORY);
    368     }
    369
    370     /**
    371      * Returns the properties file specified on the command line.
    372      *
    373      * @return the properties file specified on the command line
    374      */
    375     public File getPropertiesFile() {
    376         final String path = line.getOptionValue(ArgumentName.PROP);
    377         if (path != null) {
    378             return new File(path);
    379         }
    380         return null;
    381     }
    382
    383     /**
    384      * Returns the path to the verbose log file.
    385      *
    386      * @return the path to the verbose log file
    387      */
    388     public String getVerboseLog() {
    389         return line.getOptionValue(ArgumentName.VERBOSE_LOG);
    390     }
    391
    392     /**
    393      * <p>Prints the manifest information to standard output.</p>
    394      * <ul><li>Implementation-Title: ${pom.name}</li>
    395      * <li>Implementation-Version: ${pom.version}</li></ul>
    396      */
    397     public void printVersionInfo() {
    398         final String version = String.format("%s version %s",
    399                 Settings.getString("application.name", "DependencyCheck"),
    400                 Settings.getString("application.version", "Unknown"));
    401         System.out.println(version);
    402     }
    403
    404     /**
    405      * Checks if the auto update feature has been disabled. If it has been
    406      * disabled via the command line this will return false.
    407      *
    408      * @return if auto-update is allowed.
    409      */
    410     public boolean isAutoUpdate() {
    411         return (line == null) || !line.hasOption(ArgumentName.DISABLE_AUTO_UPDATE);
    412     }
    413
    414     /**
    415      * A collection of static final strings that represent the possible command
    416      * line arguments.
    417      */
    418     public static class ArgumentName {
    419
    420         /**
    421          * The long CLI argument name specifying the directory/file to scan.
    422          */
    423         public static final String SCAN = "scan";
    424         /**
    425          * The short CLI argument name specifying the directory/file to scan.
    426          */
    427         public static final String SCAN_SHORT = "s";
    428         /**
    429          * The long CLI argument name specifying that the CPE/CVE/etc. data
    430          * should not be automatically updated.
    431          */
    432         public static final String DISABLE_AUTO_UPDATE = "noupdate";
    433         /**
    434          * The short CLI argument name specifying that the CPE/CVE/etc. data
    435          * should not be automatically updated.
    436          */
    437         public static final String DISABLE_AUTO_UPDATE_SHORT = "n";
    438         /**
    439          * The long CLI argument name specifying the directory to write the
    440          * reports to.
    441          */
    442         public static final String OUT = "out";
    443         /**
    444          * The short CLI argument name specifying the directory to write the
    445          * reports to.
    446          */
    447         public static final String OUT_SHORT = "o";
    448         /**
    449          * The long CLI argument name specifying the output format to write the
    450          * reports to.
    451          */
    452         public static final String OUTPUT_FORMAT = "format";
    453         /**
    454          * The short CLI argument name specifying the output format to write the
    455          * reports to.
    456          */
    457         public static final String OUTPUT_FORMAT_SHORT = "f";
    458         /**
    459          * The long CLI argument name specifying the name of the application to
    460          * be scanned.
    461          */
    462         public static final String APP_NAME = "app";
    463         /**
    464          * The short CLI argument name specifying the name of the application to
    465          * be scanned.
    466          */
    467         public static final String APP_NAME_SHORT = "a";
    468         /**
    469          * The long CLI argument name asking for help.
    470          */
    471         public static final String HELP = "help";
    472         /**
    473          * The short CLI argument name asking for help.
    474          */
    475         public static final String HELP_SHORT = "h";
    476         /**
    477          * The long CLI argument name asking for the version.
    478          */
    479         public static final String VERSION_SHORT = "v";
    480         /**
    481          * The short CLI argument name asking for the version.
    482          */
    483         public static final String VERSION = "version";
    484         /**
    485          * The short CLI argument name indicating the proxy port.
    486          */
    487         public static final String PROXY_PORT_SHORT = "p";
    488         /**
    489          * The CLI argument name indicating the proxy port.
    490          */
    491         public static final String PROXY_PORT = "proxyport";
    492         /**
    493          * The short CLI argument name indicating the proxy url.
    494          */
    495         public static final String PROXY_URL_SHORT = "u";
    496         /**
    497          * The CLI argument name indicating the proxy url.
    498          */
    499         public static final String PROXY_URL = "proxyurl";
    500         /**
    501          * The short CLI argument name indicating the proxy username.
    502          */
    503         public static final String PROXY_USERNAME_SHORT = "pu";
    504         /**
    505          * The CLI argument name indicating the proxy username.
    506          */
    507         public static final String PROXY_USERNAME = "proxyuser";
    508         /**
    509          * The short CLI argument name indicating the proxy password.
    510          */
    511         public static final String PROXY_PASSWORD_SHORT = "pp";
    512         /**
    513          * The CLI argument name indicating the proxy password.
    514          */
    515         public static final String PROXY_PASSWORD = "proxypass";
    516         /**
    517          * The short CLI argument name indicating the connection timeout.
    518          */
    519         public static final String CONNECTION_TIMEOUT_SHORT = "c";
    520         /**
    521          * The CLI argument name indicating the connection timeout.
    522          */
    523         public static final String CONNECTION_TIMEOUT = "connectiontimeout";
    524         /**
    525          * The short CLI argument name for setting the location of an additional
    526          * properties file.
    527          */
    528         public static final String PROP_SHORT = "p";
    529         /**
    530          * The CLI argument name for setting the location of an additional
    531          * properties file.
    532          */
    533         public static final String PROP = "propertyfile";
    534         /**
    535          * The CLI argument name for setting the location of the data directory.
    536          */
    537         public static final String DATA_DIRECTORY = "data";
    538         /**
    539          * The short CLI argument name for setting the location of the data
    540          * directory.
    541          */
    542         public static final String DATA_DIRECTORY_SHORT = "d";
    543         /**
    544          * The CLI argument name for setting the location of the data directory.
    545          */
    546         public static final String VERBOSE_LOG = "log";
    547         /**
    548          * The short CLI argument name for setting the location of the data
    549          * directory.
    550          */
    551         public static final String VERBOSE_LOG_SHORT = "l";
    552     }
    553 }