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.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.logging.Level;
30  import java.util.logging.Logger;
31  import org.apache.commons.cli.ParseException;
32  import org.owasp.dependencycheck.data.nvdcve.CveDB;
33  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
34  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
35  import org.owasp.dependencycheck.dependency.Dependency;
36  import org.owasp.dependencycheck.org.apache.tools.ant.DirectoryScanner;
37  import org.owasp.dependencycheck.reporting.ReportGenerator;
38  import org.owasp.dependencycheck.utils.LogUtils;
39  import org.owasp.dependencycheck.utils.Settings;
40  
41  /**
42   * The command line interface for the DependencyCheck application.
43   *
44   * @author Jeremy Long
45   */
46  public class App {
47  
48      /**
49       * The location of the log properties configuration file.
50       */
51      private static final String LOG_PROPERTIES_FILE = "log.properties";
52  
53      /**
54       * The logger.
55       */
56      private static final Logger LOGGER = Logger.getLogger(App.class.getName());
57  
58      /**
59       * The main method for the application.
60       *
61       * @param args the command line arguments
62       */
63      public static void main(String[] args) {
64          try {
65              Settings.initialize();
66              final App app = new App();
67              app.run(args);
68          } finally {
69              Settings.cleanup(true);
70          }
71      }
72  
73      /**
74       * Main CLI entry-point into the application.
75       *
76       * @param args the command line arguments
77       */
78      public void run(String[] args) {
79          final CliParser cli = new CliParser();
80  
81          try {
82              cli.parse(args);
83          } catch (FileNotFoundException ex) {
84              System.err.println(ex.getMessage());
85              cli.printHelp();
86              return;
87          } catch (ParseException ex) {
88              System.err.println(ex.getMessage());
89              cli.printHelp();
90              return;
91          }
92  
93          final InputStream in = App.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE);
94          LogUtils.prepareLogger(in, cli.getVerboseLog());
95  
96          if (cli.isGetVersion()) {
97              cli.printVersionInfo();
98          } else if (cli.isUpdateOnly()) {
99              populateSettings(cli);
100             runUpdateOnly();
101         } else if (cli.isRunScan()) {
102             populateSettings(cli);
103             try {
104                 runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getApplicationName(), cli.getScanFiles(), cli.getExcludeList());
105             } catch (InvalidScanPathException ex) {
106                 LOGGER.log(Level.SEVERE, "An invalid scan path was detected; unable to scan '//*' paths");
107             }
108         } else {
109             cli.printHelp();
110         }
111     }
112 
113     /**
114      * Scans the specified directories and writes the dependency reports to the reportDirectory.
115      *
116      * @param reportDirectory the path to the directory where the reports will be written
117      * @param outputFormat the output format of the report
118      * @param applicationName the application name for the report
119      * @param files the files/directories to scan
120      * @param excludes the patterns for files/directories to exclude
121      *
122      * @throws InvalidScanPathException thrown if the path to scan starts with "//"
123      */
124     private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files,
125             String[] excludes) throws InvalidScanPathException {
126         Engine engine = null;
127         try {
128             engine = new Engine();
129             List<String> antStylePaths = new ArrayList<String>();
130             if (excludes == null || excludes.length == 0) {
131                 for (String file : files) {
132                     if (file.contains("*") || file.contains("?")) {
133                         antStylePaths.add(file);
134                     } else {
135                         engine.scan(file);
136                     }
137                 }
138             } else {
139                 antStylePaths = Arrays.asList(files);
140             }
141 
142             final Set<File> paths = new HashSet<File>();
143             for (String file : antStylePaths) {
144                 final DirectoryScanner scanner = new DirectoryScanner();
145                 String include = file.replace('\\', '/');
146                 File baseDir;
147 
148                 if (include.startsWith("//")) {
149                     throw new InvalidScanPathException("Unable to scan paths specified by //");
150                 } else if (include.startsWith("./")) {
151                     baseDir = new File(".");
152                     include = include.substring(2);
153                 } else if (include.startsWith("/")) {
154                     baseDir = new File("/");
155                     include = include.substring(1);
156                 } else if (include.contains("/")) {
157                     final int pos = include.indexOf('/');
158                     final String tmp = include.substring(0, pos);
159                     if (tmp.contains("*") || tmp.contains("?")) {
160                         baseDir = new File(".");
161                     } else {
162                         baseDir = new File(tmp);
163                         include = include.substring(pos + 1);
164                     }
165                 } else { //no path info - must just be a file in the working directory
166                     baseDir = new File(".");
167                 }
168                 scanner.setBasedir(baseDir);
169                 scanner.setIncludes(include);
170                 if (excludes != null && excludes.length > 0) {
171                     scanner.addExcludes(excludes);
172                 }
173                 scanner.scan();
174                 if (scanner.getIncludedFilesCount() > 0) {
175                     for (String s : scanner.getIncludedFiles()) {
176                         final File f = new File(baseDir, s);
177                         paths.add(f);
178                     }
179                 }
180             }
181             engine.scan(paths);
182 
183             engine.analyzeDependencies();
184             final List<Dependency> dependencies = engine.getDependencies();
185             DatabaseProperties prop = null;
186             CveDB cve = null;
187             try {
188                 cve = new CveDB();
189                 cve.open();
190                 prop = cve.getDatabaseProperties();
191             } catch (DatabaseException ex) {
192                 LOGGER.log(Level.FINE, "Unable to retrieve DB Properties", ex);
193             } finally {
194                 if (cve != null) {
195                     cve.close();
196                 }
197             }
198             final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop);
199             try {
200                 report.generateReports(reportDirectory, outputFormat);
201             } catch (IOException ex) {
202                 LOGGER.log(Level.SEVERE, "There was an IO error while attempting to generate the report.");
203                 LOGGER.log(Level.FINE, null, ex);
204             } catch (Throwable ex) {
205                 LOGGER.log(Level.SEVERE, "There was an error while attempting to generate the report.");
206                 LOGGER.log(Level.FINE, null, ex);
207             }
208         } catch (DatabaseException ex) {
209             LOGGER.log(Level.SEVERE, "Unable to connect to the dependency-check database; analysis has stopped");
210             LOGGER.log(Level.FINE, "", ex);
211         } finally {
212             if (engine != null) {
213                 engine.cleanup();
214             }
215         }
216     }
217 
218     /**
219      * Only executes the update phase of dependency-check.
220      */
221     private void runUpdateOnly() {
222         Engine engine = null;
223         try {
224             engine = new Engine();
225             engine.doUpdates();
226         } catch (DatabaseException ex) {
227             LOGGER.log(Level.SEVERE, "Unable to connect to the dependency-check database; analysis has stopped");
228             LOGGER.log(Level.FINE, "", ex);
229         } finally {
230             if (engine != null) {
231                 engine.cleanup();
232             }
233         }
234     }
235 
236     /**
237      * Updates the global Settings.
238      *
239      * @param cli a reference to the CLI Parser that contains the command line arguments used to set the corresponding settings in
240      * the core engine.
241      */
242     private void populateSettings(CliParser cli) {
243 
244         final boolean autoUpdate = cli.isAutoUpdate();
245         final String connectionTimeout = cli.getConnectionTimeout();
246         final String proxyServer = cli.getProxyServer();
247         final String proxyPort = cli.getProxyPort();
248         final String proxyUser = cli.getProxyUsername();
249         final String proxyPass = cli.getProxyPassword();
250         final String dataDirectory = cli.getDataDirectory();
251         final File propertiesFile = cli.getPropertiesFile();
252         final String suppressionFile = cli.getSuppressionFile();
253         final boolean jarDisabled = cli.isJarDisabled();
254         final boolean archiveDisabled = cli.isArchiveDisabled();
255         final boolean pyDistDisabled = cli.isPythonDistributionDisabled();
256         final boolean pyPkgDisabled = cli.isPythonPackageDisabled();
257         final boolean assemblyDisabled = cli.isAssemblyDisabled();
258         final boolean nuspecDisabled = cli.isNuspecDisabled();
259         final boolean centralDisabled = cli.isCentralDisabled();
260         final boolean nexusDisabled = cli.isNexusDisabled();
261         final String nexusUrl = cli.getNexusUrl();
262         final String databaseDriverName = cli.getDatabaseDriverName();
263         final String databaseDriverPath = cli.getDatabaseDriverPath();
264         final String connectionString = cli.getConnectionString();
265         final String databaseUser = cli.getDatabaseUser();
266         final String databasePassword = cli.getDatabasePassword();
267         final String additionalZipExtensions = cli.getAdditionalZipExtensions();
268         final String pathToMono = cli.getPathToMono();
269 
270         if (propertiesFile != null) {
271             try {
272                 Settings.mergeProperties(propertiesFile);
273             } catch (FileNotFoundException ex) {
274                 final String msg = String.format("Unable to load properties file '%s'", propertiesFile.getPath());
275                 LOGGER.log(Level.SEVERE, msg);
276                 LOGGER.log(Level.FINE, null, ex);
277             } catch (IOException ex) {
278                 final String msg = String.format("Unable to find properties file '%s'", propertiesFile.getPath());
279                 LOGGER.log(Level.SEVERE, msg);
280                 LOGGER.log(Level.FINE, null, ex);
281             }
282         }
283         // We have to wait until we've merged the properties before attempting to set whether we use
284         // the proxy for Nexus since it could be disabled in the properties, but not explicitly stated
285         // on the command line
286         final boolean nexusUsesProxy = cli.isNexusUsesProxy();
287         if (dataDirectory != null) {
288             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
289         } else if (System.getProperty("basedir") != null) {
290             final File dataDir = new File(System.getProperty("basedir"), "data");
291             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
292         } else {
293             final File jarPath = new File(App.class.getProtectionDomain().getCodeSource().getLocation().getPath());
294             final File base = jarPath.getParentFile();
295             final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
296             final File dataDir = new File(base, sub);
297             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
298         }
299         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
300         if (proxyServer != null && !proxyServer.isEmpty()) {
301             Settings.setString(Settings.KEYS.PROXY_SERVER, proxyServer);
302         }
303         if (proxyPort != null && !proxyPort.isEmpty()) {
304             Settings.setString(Settings.KEYS.PROXY_PORT, proxyPort);
305         }
306         if (proxyUser != null && !proxyUser.isEmpty()) {
307             Settings.setString(Settings.KEYS.PROXY_USERNAME, proxyUser);
308         }
309         if (proxyPass != null && !proxyPass.isEmpty()) {
310             Settings.setString(Settings.KEYS.PROXY_PASSWORD, proxyPass);
311         }
312         if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
313             Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
314         }
315         if (suppressionFile != null && !suppressionFile.isEmpty()) {
316             Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
317         }
318 
319         //File Type Analyzer Settings
320         Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, !jarDisabled);
321         Settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, !archiveDisabled);
322         Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, !pyDistDisabled);
323         Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED, !pyPkgDisabled);
324         Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, !nuspecDisabled);
325         Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, !assemblyDisabled);
326 
327         Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !centralDisabled);
328         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !nexusDisabled);
329         if (nexusUrl != null && !nexusUrl.isEmpty()) {
330             Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
331         }
332         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_PROXY, nexusUsesProxy);
333         if (databaseDriverName != null && !databaseDriverName.isEmpty()) {
334             Settings.setString(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
335         }
336         if (databaseDriverPath != null && !databaseDriverPath.isEmpty()) {
337             Settings.setString(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
338         }
339         if (connectionString != null && !connectionString.isEmpty()) {
340             Settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
341         }
342         if (databaseUser != null && !databaseUser.isEmpty()) {
343             Settings.setString(Settings.KEYS.DB_USER, databaseUser);
344         }
345         if (databasePassword != null && !databasePassword.isEmpty()) {
346             Settings.setString(Settings.KEYS.DB_PASSWORD, databasePassword);
347         }
348         if (additionalZipExtensions != null && !additionalZipExtensions.isEmpty()) {
349             Settings.setString(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, additionalZipExtensions);
350         }
351         if (pathToMono != null && !pathToMono.isEmpty()) {
352             Settings.setString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono);
353         }
354     }
355 }