1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck;
19
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.util.logging.Logger;
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.InvalidSettingException;
34 import org.owasp.dependencycheck.utils.Settings;
35
36
37
38
39
40
41 public final class CliParser {
42
43
44
45
46 private static final Logger LOGGER = Logger.getLogger(CliParser.class.getName());
47
48
49
50 private CommandLine line;
51
52
53
54 private boolean isValid = true;
55
56
57
58
59
60
61
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
73
74
75
76
77
78 private CommandLine parseArgs(String[] args) throws ParseException {
79 final CommandLineParser parser = new PosixParser();
80 final Options options = createCommandLineOptions();
81 return parser.parse(options, args);
82 }
83
84
85
86
87
88
89
90
91 private void validateArgs() throws FileNotFoundException, ParseException {
92 if (isRunScan()) {
93 validatePathExists(getScanFiles(), ARGUMENT.SCAN);
94 validatePathExists(getReportDirectory(), ARGUMENT.OUT);
95 if (getPathToMono() != null) {
96 validatePathExists(getPathToMono(), ARGUMENT.PATH_TO_MONO);
97 }
98 if (!line.hasOption(ARGUMENT.APP_NAME)) {
99 throw new ParseException("Missing 'app' argument; the scan cannot be run without the an application name.");
100 }
101 if (line.hasOption(ARGUMENT.OUTPUT_FORMAT)) {
102 final String format = line.getOptionValue(ARGUMENT.OUTPUT_FORMAT);
103 try {
104 Format.valueOf(format);
105 } catch (IllegalArgumentException ex) {
106 final String msg = String.format("An invalid 'format' of '%s' was specified. "
107 + "Supported output formats are XML, HTML, VULN, or ALL", format);
108 throw new ParseException(msg);
109 }
110 }
111 }
112 }
113
114
115
116
117
118
119
120
121
122 private void validatePathExists(String[] paths, String optType) throws FileNotFoundException {
123 for (String path : paths) {
124 validatePathExists(path, optType);
125 }
126 }
127
128
129
130
131
132
133
134
135
136 private void validatePathExists(String path, String argumentName) throws FileNotFoundException {
137 if (path == null) {
138 isValid = false;
139 final String msg = String.format("Invalid '%s' argument: null", argumentName);
140 throw new FileNotFoundException(msg);
141 } else if (!path.contains("*") && !path.contains("?")) {
142 final File f = new File(path);
143 if ("o".equals(argumentName.substring(0, 1).toLowerCase()) && !"ALL".equals(this.getReportFormat().toUpperCase())) {
144 final String checkPath = path.toLowerCase();
145 if (checkPath.endsWith(".html") || checkPath.endsWith(".xml") || checkPath.endsWith(".htm")) {
146 if (!f.getParentFile().isDirectory()) {
147 isValid = false;
148 final String msg = String.format("Invalid '%s' argument: '%s'", argumentName, path);
149 throw new FileNotFoundException(msg);
150 }
151 }
152 } else {
153 if (!f.exists()) {
154 isValid = false;
155 final String msg = String.format("Invalid '%s' argument: '%s'", argumentName, path);
156 throw new FileNotFoundException(msg);
157 }
158 }
159 } else if (path.startsWith("//") || path.startsWith("\\\\")) {
160 isValid = false;
161 final String msg = String.format("Invalid '%s' argument: '%s'%nUnable to scan paths that start with '//'.", argumentName, path);
162 throw new FileNotFoundException(msg);
163 }
164 }
165
166
167
168
169
170
171 @SuppressWarnings("static-access")
172 private Options createCommandLineOptions() {
173 final Options options = new Options();
174 addStandardOptions(options);
175 addAdvancedOptions(options);
176 addDeprecatedOptions(options);
177 return options;
178 }
179
180
181
182
183
184
185
186 @SuppressWarnings("static-access")
187 private void addStandardOptions(final Options options) throws IllegalArgumentException {
188 final Option help = new Option(ARGUMENT.HELP_SHORT, ARGUMENT.HELP, false,
189 "Print this message.");
190
191 final Option advancedHelp = OptionBuilder.withLongOpt(ARGUMENT.ADVANCED_HELP)
192 .withDescription("Print the advanced help message.").create();
193
194 final Option version = new Option(ARGUMENT.VERSION_SHORT, ARGUMENT.VERSION,
195 false, "Print the version information.");
196
197 final Option noUpdate = new Option(ARGUMENT.DISABLE_AUTO_UPDATE_SHORT, ARGUMENT.DISABLE_AUTO_UPDATE,
198 false, "Disables the automatic updating of the CPE data.");
199
200 final Option appName = OptionBuilder.withArgName("name").hasArg().withLongOpt(ARGUMENT.APP_NAME)
201 .withDescription("The name of the application being scanned. This is a required argument.")
202 .create(ARGUMENT.APP_NAME_SHORT);
203
204 final Option path = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.SCAN)
205 .withDescription("The path to scan - this option can be specified multiple times. Ant style"
206 + " paths are supported (e.g. path/**/*.jar).")
207 .create(ARGUMENT.SCAN_SHORT);
208
209 final Option excludes = OptionBuilder.withArgName("pattern").hasArg().withLongOpt(ARGUMENT.EXCLUDE)
210 .withDescription("Specify and exclusion pattern. This option can be specified multiple times"
211 + " and it accepts Ant style excludsions.")
212 .create();
213
214 final Option props = OptionBuilder.withArgName("file").hasArg().withLongOpt(ARGUMENT.PROP)
215 .withDescription("A property file to load.")
216 .create(ARGUMENT.PROP_SHORT);
217
218 final Option out = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.OUT)
219 .withDescription("The folder to write reports to. This defaults to the current directory. "
220 + "It is possible to set this to a specific file name if the format argument is not set to ALL.")
221 .create(ARGUMENT.OUT_SHORT);
222
223 final Option outputFormat = OptionBuilder.withArgName("format").hasArg().withLongOpt(ARGUMENT.OUTPUT_FORMAT)
224 .withDescription("The output format to write to (XML, HTML, VULN, ALL). The default is HTML.")
225 .create(ARGUMENT.OUTPUT_FORMAT_SHORT);
226
227 final Option verboseLog = OptionBuilder.withArgName("file").hasArg().withLongOpt(ARGUMENT.VERBOSE_LOG)
228 .withDescription("The file path to write verbose logging information.")
229 .create(ARGUMENT.VERBOSE_LOG_SHORT);
230
231 final Option suppressionFile = OptionBuilder.withArgName("file").hasArg().withLongOpt(ARGUMENT.SUPPRESSION_FILE)
232 .withDescription("The file path to the suppression XML file.")
233 .create();
234
235
236 final OptionGroup og = new OptionGroup();
237 og.addOption(path);
238
239 final OptionGroup exog = new OptionGroup();
240 exog.addOption(excludes);
241
242 options.addOptionGroup(og)
243 .addOptionGroup(exog)
244 .addOption(out)
245 .addOption(outputFormat)
246 .addOption(appName)
247 .addOption(version)
248 .addOption(help)
249 .addOption(advancedHelp)
250 .addOption(noUpdate)
251 .addOption(props)
252 .addOption(verboseLog)
253 .addOption(suppressionFile);
254 }
255
256
257
258
259
260
261
262
263 @SuppressWarnings("static-access")
264 private void addAdvancedOptions(final Options options) throws IllegalArgumentException {
265
266 final Option data = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.DATA_DIRECTORY)
267 .withDescription("The location of the H2 Database file. This option should generally not be set.")
268 .create(ARGUMENT.DATA_DIRECTORY_SHORT);
269
270 final Option connectionTimeout = OptionBuilder.withArgName("timeout").hasArg().withLongOpt(ARGUMENT.CONNECTION_TIMEOUT)
271 .withDescription("The connection timeout (in milliseconds) to use when downloading resources.")
272 .create(ARGUMENT.CONNECTION_TIMEOUT_SHORT);
273
274 final Option proxyServer = OptionBuilder.withArgName("server").hasArg().withLongOpt(ARGUMENT.PROXY_SERVER)
275 .withDescription("The proxy server to use when downloading resources.")
276 .create();
277
278 final Option proxyPort = OptionBuilder.withArgName("port").hasArg().withLongOpt(ARGUMENT.PROXY_PORT)
279 .withDescription("The proxy port to use when downloading resources.")
280 .create();
281
282 final Option proxyUsername = OptionBuilder.withArgName("user").hasArg().withLongOpt(ARGUMENT.PROXY_USERNAME)
283 .withDescription("The proxy username to use when downloading resources.")
284 .create();
285
286 final Option proxyPassword = OptionBuilder.withArgName("pass").hasArg().withLongOpt(ARGUMENT.PROXY_PASSWORD)
287 .withDescription("The proxy password to use when downloading resources.")
288 .create();
289
290 final Option connectionString = OptionBuilder.withArgName("connStr").hasArg().withLongOpt(ARGUMENT.CONNECTION_STRING)
291 .withDescription("The connection string to the database.")
292 .create();
293
294 final Option dbUser = OptionBuilder.withArgName("user").hasArg().withLongOpt(ARGUMENT.DB_NAME)
295 .withDescription("The username used to connect to the database.")
296 .create();
297
298 final Option dbPassword = OptionBuilder.withArgName("password").hasArg().withLongOpt(ARGUMENT.DB_PASSWORD)
299 .withDescription("The password for connecting to the database.")
300 .create();
301
302 final Option dbDriver = OptionBuilder.withArgName("driver").hasArg().withLongOpt(ARGUMENT.DB_DRIVER)
303 .withDescription("The database driver name.")
304 .create();
305
306 final Option dbDriverPath = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.DB_DRIVER_PATH)
307 .withDescription("The path to the database driver; note, this does not need to be set unless the JAR is outside of the classpath.")
308 .create();
309
310 final Option disableJarAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_JAR)
311 .withDescription("Disable the Jar Analyzer.")
312 .create();
313 final Option disableArchiveAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_ARCHIVE)
314 .withDescription("Disable the Archive Analyzer.")
315 .create();
316 final Option disableNuspecAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_NUSPEC)
317 .withDescription("Disable the Nuspec Analyzer.")
318 .create();
319 final Option disableAssemblyAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_ASSEMBLY)
320 .withDescription("Disable the .NET Assembly Analyzer.")
321 .create();
322
323 final Option disableNexusAnalyzer = OptionBuilder.withLongOpt(ARGUMENT.DISABLE_NEXUS)
324 .withDescription("Disable the Nexus Analyzer.")
325 .create();
326
327 final Option nexusUrl = OptionBuilder.withArgName("url").hasArg().withLongOpt(ARGUMENT.NEXUS_URL)
328 .withDescription("The url to the Nexus Pro Server. If not set the Nexus Analyzer will be disabled.")
329 .create();
330
331 final Option nexusUsesProxy = OptionBuilder.withArgName("true/false").hasArg().withLongOpt(ARGUMENT.NEXUS_USES_PROXY)
332 .withDescription("Whether or not the configured proxy should be used when connecting to Nexus.")
333 .create();
334
335 final Option additionalZipExtensions = OptionBuilder.withArgName("extensions").hasArg()
336 .withLongOpt(ARGUMENT.ADDITIONAL_ZIP_EXTENSIONS)
337 .withDescription("A comma separated list of additional extensions to be scanned as ZIP files "
338 + "(ZIP, EAR, WAR are already treated as zip files)")
339 .create();
340
341 final Option pathToMono = OptionBuilder.withArgName("path").hasArg().withLongOpt(ARGUMENT.PATH_TO_MONO)
342 .withDescription("The path to Mono for .NET Assembly analysis on non-windows systems.")
343 .create();
344
345 options.addOption(proxyPort)
346 .addOption(proxyServer)
347 .addOption(proxyUsername)
348 .addOption(proxyPassword)
349 .addOption(connectionTimeout)
350 .addOption(connectionString)
351 .addOption(dbUser)
352 .addOption(data)
353 .addOption(dbPassword)
354 .addOption(dbDriver)
355 .addOption(dbDriverPath)
356 .addOption(disableJarAnalyzer)
357 .addOption(disableArchiveAnalyzer)
358 .addOption(disableAssemblyAnalyzer)
359 .addOption(disableNuspecAnalyzer)
360 .addOption(disableNexusAnalyzer)
361 .addOption(nexusUrl)
362 .addOption(nexusUsesProxy)
363 .addOption(additionalZipExtensions)
364 .addOption(pathToMono);
365 }
366
367
368
369
370
371
372
373
374 @SuppressWarnings("static-access")
375 private void addDeprecatedOptions(final Options options) throws IllegalArgumentException {
376
377 final Option proxyServer = OptionBuilder.withArgName("url").hasArg().withLongOpt(ARGUMENT.PROXY_URL)
378 .withDescription("The proxy url argument is deprecated, use proxyserver instead.")
379 .create();
380
381 options.addOption(proxyServer);
382 }
383
384
385
386
387
388
389 public boolean isGetVersion() {
390 return (line != null) && line.hasOption(ARGUMENT.VERSION);
391 }
392
393
394
395
396
397
398 public boolean isGetHelp() {
399 return (line != null) && line.hasOption(ARGUMENT.HELP);
400 }
401
402
403
404
405
406
407 public boolean isRunScan() {
408 return (line != null) && isValid && line.hasOption(ARGUMENT.SCAN);
409 }
410
411
412
413
414
415
416 public boolean isJarDisabled() {
417 return (line != null) && line.hasOption(ARGUMENT.DISABLE_JAR);
418 }
419
420
421
422
423
424
425 public boolean isArchiveDisabled() {
426 return (line != null) && line.hasOption(ARGUMENT.DISABLE_ARCHIVE);
427 }
428
429
430
431
432
433
434 public boolean isNuspecDisabled() {
435 return (line != null) && line.hasOption(ARGUMENT.DISABLE_NUSPEC);
436 }
437
438
439
440
441
442
443 public boolean isAssemblyDisabled() {
444 return (line != null) && line.hasOption(ARGUMENT.DISABLE_ASSEMBLY);
445 }
446
447
448
449
450
451
452 public boolean isNexusDisabled() {
453 return (line != null) && line.hasOption(ARGUMENT.DISABLE_NEXUS);
454 }
455
456
457
458
459
460
461 public String getNexusUrl() {
462 if (line == null || !line.hasOption(ARGUMENT.NEXUS_URL)) {
463 return null;
464 } else {
465 return line.getOptionValue(ARGUMENT.NEXUS_URL);
466 }
467 }
468
469
470
471
472
473
474
475 public boolean isNexusUsesProxy() {
476
477
478 if (line == null || !line.hasOption(ARGUMENT.NEXUS_USES_PROXY)) {
479 try {
480 return Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_PROXY);
481 } catch (InvalidSettingException ise) {
482 return true;
483 }
484 } else {
485 return Boolean.parseBoolean(line.getOptionValue(ARGUMENT.NEXUS_USES_PROXY));
486 }
487 }
488
489
490
491
492 public void printHelp() {
493 final HelpFormatter formatter = new HelpFormatter();
494 final Options options = new Options();
495 addStandardOptions(options);
496 if (line != null && line.hasOption(ARGUMENT.ADVANCED_HELP)) {
497 addAdvancedOptions(options);
498 }
499 final String helpMsg = String.format("%n%s"
500 + " can be used to identify if there are any known CVE vulnerabilities in libraries utilized by an application. "
501 + "%s will automatically update required data from the Internet, such as the CVE and CPE data files from nvd.nist.gov.%n%n",
502 Settings.getString("application.name", "DependencyCheck"),
503 Settings.getString("application.name", "DependencyCheck"));
504
505 formatter.printHelp(Settings.getString("application.name", "DependencyCheck"),
506 helpMsg,
507 options,
508 "",
509 true);
510 }
511
512
513
514
515
516
517 public String[] getScanFiles() {
518 return line.getOptionValues(ARGUMENT.SCAN);
519 }
520
521
522
523
524
525
526 public String[] getExcludeList() {
527 return line.getOptionValues(ARGUMENT.EXCLUDE);
528 }
529
530
531
532
533
534
535 public String getReportDirectory() {
536 return line.getOptionValue(ARGUMENT.OUT, ".");
537 }
538
539
540
541
542
543
544 public String getPathToMono() {
545 return line.getOptionValue(ARGUMENT.PATH_TO_MONO);
546 }
547
548
549
550
551
552
553 public String getReportFormat() {
554 return line.getOptionValue(ARGUMENT.OUTPUT_FORMAT, "HTML");
555 }
556
557
558
559
560
561
562 public String getApplicationName() {
563 return line.getOptionValue(ARGUMENT.APP_NAME);
564 }
565
566
567
568
569
570
571 public String getConnectionTimeout() {
572 return line.getOptionValue(ARGUMENT.CONNECTION_TIMEOUT);
573 }
574
575
576
577
578
579
580 public String getProxyServer() {
581
582 String server = line.getOptionValue(ARGUMENT.PROXY_SERVER);
583 if (server == null) {
584 server = line.getOptionValue(ARGUMENT.PROXY_URL);
585 if (server != null) {
586 LOGGER.warning("An old command line argument 'proxyurl' was detected; use proxyserver instead");
587 }
588 }
589 return server;
590 }
591
592
593
594
595
596
597 public String getProxyPort() {
598 return line.getOptionValue(ARGUMENT.PROXY_PORT);
599 }
600
601
602
603
604
605
606 public String getProxyUsername() {
607 return line.getOptionValue(ARGUMENT.PROXY_USERNAME);
608 }
609
610
611
612
613
614
615 public String getProxyPassword() {
616 return line.getOptionValue(ARGUMENT.PROXY_PASSWORD);
617 }
618
619
620
621
622
623
624 public String getDataDirectory() {
625 return line.getOptionValue(ARGUMENT.DATA_DIRECTORY);
626 }
627
628
629
630
631
632
633 public File getPropertiesFile() {
634 final String path = line.getOptionValue(ARGUMENT.PROP);
635 if (path != null) {
636 return new File(path);
637 }
638 return null;
639 }
640
641
642
643
644
645
646 public String getVerboseLog() {
647 return line.getOptionValue(ARGUMENT.VERBOSE_LOG);
648 }
649
650
651
652
653
654
655 public String getSuppressionFile() {
656 return line.getOptionValue(ARGUMENT.SUPPRESSION_FILE);
657 }
658
659
660
661
662
663
664
665 public void printVersionInfo() {
666 final String version = String.format("%s version %s",
667 Settings.getString("application.name", "DependencyCheck"),
668 Settings.getString("application.version", "Unknown"));
669 System.out.println(version);
670 }
671
672
673
674
675
676
677
678 public boolean isAutoUpdate() {
679 return (line == null) || !line.hasOption(ARGUMENT.DISABLE_AUTO_UPDATE);
680 }
681
682
683
684
685
686
687 public String getDatabaseDriverName() {
688 return line.getOptionValue(ARGUMENT.DB_DRIVER);
689 }
690
691
692
693
694
695
696 public String getDatabaseDriverPath() {
697 return line.getOptionValue(ARGUMENT.DB_DRIVER_PATH);
698 }
699
700
701
702
703
704
705 public String getConnectionString() {
706 return line.getOptionValue(ARGUMENT.CONNECTION_STRING);
707 }
708
709
710
711
712
713
714 public String getDatabaseUser() {
715 return line.getOptionValue(ARGUMENT.DB_NAME);
716 }
717
718
719
720
721
722
723 public String getDatabasePassword() {
724 return line.getOptionValue(ARGUMENT.DB_PASSWORD);
725 }
726
727
728
729
730
731
732 public String getAdditionalZipExtensions() {
733 return line.getOptionValue(ARGUMENT.ADDITIONAL_ZIP_EXTENSIONS);
734 }
735
736
737
738
739 public static class ARGUMENT {
740
741
742
743
744 public static final String SCAN = "scan";
745
746
747
748 public static final String SCAN_SHORT = "s";
749
750
751
752 public static final String DISABLE_AUTO_UPDATE = "noupdate";
753
754
755
756 public static final String DISABLE_AUTO_UPDATE_SHORT = "n";
757
758
759
760 public static final String OUT = "out";
761
762
763
764 public static final String OUT_SHORT = "o";
765
766
767
768 public static final String OUTPUT_FORMAT = "format";
769
770
771
772 public static final String OUTPUT_FORMAT_SHORT = "f";
773
774
775
776 public static final String APP_NAME = "app";
777
778
779
780 public static final String APP_NAME_SHORT = "a";
781
782
783
784 public static final String HELP = "help";
785
786
787
788 public static final String ADVANCED_HELP = "advancedHelp";
789
790
791
792 public static final String HELP_SHORT = "h";
793
794
795
796 public static final String VERSION_SHORT = "v";
797
798
799
800 public static final String VERSION = "version";
801
802
803
804 public static final String PROXY_PORT = "proxyport";
805
806
807
808 public static final String PROXY_SERVER = "proxyserver";
809
810
811
812
813
814 @Deprecated
815 public static final String PROXY_URL = "proxyurl";
816
817
818
819 public static final String PROXY_USERNAME = "proxyuser";
820
821
822
823 public static final String PROXY_PASSWORD = "proxypass";
824
825
826
827 public static final String CONNECTION_TIMEOUT_SHORT = "c";
828
829
830
831 public static final String CONNECTION_TIMEOUT = "connectiontimeout";
832
833
834
835 public static final String PROP_SHORT = "P";
836
837
838
839 public static final String PROP = "propertyfile";
840
841
842
843 public static final String DATA_DIRECTORY = "data";
844
845
846
847 public static final String DATA_DIRECTORY_SHORT = "d";
848
849
850
851 public static final String VERBOSE_LOG = "log";
852
853
854
855 public static final String VERBOSE_LOG_SHORT = "l";
856
857
858
859 public static final String SUPPRESSION_FILE = "suppression";
860
861
862
863 public static final String DISABLE_JAR = "disableJar";
864
865
866
867 public static final String DISABLE_ARCHIVE = "disableArchive";
868
869
870
871 public static final String DISABLE_ASSEMBLY = "disableAssembly";
872
873
874
875 public static final String DISABLE_NUSPEC = "disableNuspec";
876
877
878
879 public static final String DISABLE_NEXUS = "disableNexus";
880
881
882
883 public static final String NEXUS_URL = "nexus";
884
885
886
887 public static final String NEXUS_USES_PROXY = "nexusUsesProxy";
888
889
890
891 public static final String CONNECTION_STRING = "connectionString";
892
893
894
895 public static final String DB_NAME = "dbUser";
896
897
898
899 public static final String DB_PASSWORD = "dbPassword";
900
901
902
903 public static final String DB_DRIVER = "dbDriverName";
904
905
906
907 public static final String DB_DRIVER_PATH = "dbDriverPath";
908
909
910
911 public static final String PATH_TO_MONO = "mono";
912
913
914
915 public static final String ADDITIONAL_ZIP_EXTENSIONS = "zipExtensions";
916
917
918
919 public static final String EXCLUDE = "exclude";
920 }
921 }