##### Coverage Report - org.owasp.dependencycheck.Engine

Classes in this File | Line Coverage | Branch Coverage | Complexity
Engine |
60% | 98/162
70% | 49/70
| 3.7222222222222223;3.722

1 |  |
     /*
2 |  |
      * This file is part of dependency-check-core.
3 |  |
      *
4 |  |
      * Dependency-check-core 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-core 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-core. If not, see http://www.gnu.org/licenses/.
16 |  |
      *
17 |  |
      * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
18 |  |
      */
19 |  |
     package org.owasp.dependencycheck;
20 |  |
21 |  |
     import java.util.EnumMap;
22 |  |
     import java.io.File;
23 |  |
     import java.io.IOException;
24 |  |
     import java.util.ArrayList;
25 |  |
     import java.util.HashSet;
26 |  |
     import java.util.Iterator;
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.owasp.dependencycheck.analyzer.AnalysisException;
32 |  |
     import org.owasp.dependencycheck.analyzer.AnalysisPhase;
33 |  |
     import org.owasp.dependencycheck.analyzer.Analyzer;
34 |  |
     import org.owasp.dependencycheck.analyzer.AnalyzerService;
35 |  |
     import org.owasp.dependencycheck.data.CachedWebDataSource;
36 |  |
     import org.owasp.dependencycheck.data.NoDataException;
37 |  |
     import org.owasp.dependencycheck.data.UpdateException;
38 |  |
     import org.owasp.dependencycheck.data.UpdateService;
39 |  |
     import org.owasp.dependencycheck.data.cpe.CpeIndexReader;
40 |  |
     import org.owasp.dependencycheck.dependency.Dependency;
41 |  |
     import org.owasp.dependencycheck.utils.FileUtils;
42 |  |
     import org.owasp.dependencycheck.utils.InvalidSettingException;
43 |  |
     import org.owasp.dependencycheck.utils.Settings;
44 |  |
45 |  |
     /**
46 |  |
      * Scans files, directories, etc. for Dependencies. Analyzers are loaded and
47 |  |
      * used to process the files found by the scan, if a file is encountered and an
48 |  |
      * Analyzer is associated with the file type then the file is turned into a
49 |  |
      * dependency.
50 |  |
      *
51 |  |
      * @author Jeremy Long (jeremy.long@owasp.org)
52 |  |
      */
53 |  |
     public class Engine {
54 |  |
55 |  |
         /**
56 |  |
          * The list of dependencies.
57 |  |
          */
58 | 6 |
         private final List<Dependency> dependencies = new ArrayList<Dependency>();
59 |  |
         /**
60 |  |
          * A Map of analyzers grouped by Analysis phase.
61 |  |
          */
62 | 6 |
         private final EnumMap<AnalysisPhase, List<Analyzer>> analyzers =
63 |  |
                 new EnumMap<AnalysisPhase, List<Analyzer>>(AnalysisPhase.class);
64 |  |
         /**
65 |  |
          * A set of extensions supported by the analyzers.
66 |  |
          */
67 | 6 |
         private final Set<String> extensions = new HashSet<String>();
68 |  |
69 |  |
         /**
70 |  |
          * Creates a new Engine.
71 |  |
          */
72 | 6 |
         public Engine() {
73 | 6 |
             boolean autoUpdate = true;
74 |  |
             try {
75 | 6 |
                 autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
76 | 0 |
             } catch (InvalidSettingException ex) {
77 | 0 |
                 Logger.getLogger(Engine.class.getName()).log(Level.FINE, "Invalid setting for auto-update; using true.");
78 | 6 |
             }
79 | 6 |
             if (autoUpdate) {
80 | 0 |
                 doUpdates();
81 |  |
             }
82 | 6 |
             loadAnalyzers();
83 | 6 |
         }
84 |  |
85 |  |
         /**
86 |  |
          * Creates a new Engine.
87 |  |
          *
88 |  |
          * @param autoUpdate indicates whether or not data should be updated from
89 |  |
          * the Internet
90 |  |
          * @deprecated This function should no longer be used; the autoupdate flag
91 |  |
          * should be set using:
92 |  |
          * <code>Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, value);</code>
93 |  |
          */
94 |  |
         @Deprecated
95 | 0 |
         public Engine(boolean autoUpdate) {
96 | 0 |
             if (autoUpdate) {
97 | 0 |
                 doUpdates();
98 |  |
             }
99 | 0 |
             loadAnalyzers();
100 | 0 |
         }
101 |  |
102 |  |
         /**
103 |  |
          * Loads the analyzers specified in the configuration file (or system
104 |  |
          * properties).
105 |  |
          */
106 |  |
         private void loadAnalyzers() {
107 |  |
108 | 60 |
             for (AnalysisPhase phase : AnalysisPhase.values()) {
109 | 54 |
                 analyzers.put(phase, new ArrayList<Analyzer>());
110 |  |
             }
111 |  |
112 | 6 |
             final AnalyzerService service = AnalyzerService.getInstance();
113 | 6 |
             final Iterator<Analyzer> iterator = service.getAnalyzers();
114 | 54 |
             while (iterator.hasNext()) {
115 | 48 |
                 final Analyzer a = iterator.next();
116 | 48 |
                 analyzers.get(a.getAnalysisPhase()).add(a);
117 | 48 |
                 if (a.getSupportedExtensions() != null) {
118 | 12 |
                     extensions.addAll(a.getSupportedExtensions());
119 |  |
                 }
120 | 48 |
             }
121 | 6 |
         }
122 |  |
123 |  |
         /**
124 |  |
          * Get the List of the analyzers for a specific phase of analysis.
125 |  |
          *
126 |  |
          * @param phase the phase to get the configured analyzers.
127 |  |
          * @return the analyzers loaded
128 |  |
          */
129 |  |
         public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
130 | 0 |
             return analyzers.get(phase);
131 |  |
         }
132 |  |
133 |  |
         /**
134 |  |
          * Get the dependencies identified.
135 |  |
          *
136 |  |
          * @return the dependencies identified
137 |  |
          */
138 |  |
         public List<Dependency> getDependencies() {
139 | 38 |
             return dependencies;
140 |  |
         }
141 |  |
142 |  |
         /**
143 |  |
          * Scans an array of files or directories. If a directory is specified, it
144 |  |
          * will be scanned recursively. Any dependencies identified are added to the
145 |  |
          * dependency collection.
146 |  |
          *
147 |  |
          * @since v0.3.2.5
148 |  |
          *
149 |  |
          * @param paths an array of paths to files or directories to be analyzed.
150 |  |
          */
151 |  |
         public void scan(String[] paths) {
152 | 0 |
             for (String path : paths) {
153 | 0 |
                 final File file = new File(path);
154 | 0 |
                 scan(file);
155 |  |
             }
156 | 0 |
         }
157 |  |
158 |  |
         /**
159 |  |
          * Scans a given file or directory. If a directory is specified, it will be
160 |  |
          * scanned recursively. Any dependencies identified are added to the
161 |  |
          * dependency collection.
162 |  |
          *
163 |  |
          * @param path the path to a file or directory to be analyzed.
164 |  |
          */
165 |  |
         public void scan(String path) {
166 | 0 |
             final File file = new File(path);
167 | 0 |
             scan(file);
168 | 0 |
         }
169 |  |
170 |  |
         /**
171 |  |
          * Scans an array of files or directories. If a directory is specified, it
172 |  |
          * will be scanned recursively. Any dependencies identified are added to the
173 |  |
          * dependency collection.
174 |  |
          *
175 |  |
          * @since v0.3.2.5
176 |  |
          *
177 |  |
          * @param files an array of paths to files or directories to be analyzed.
178 |  |
          */
179 |  |
         public void scan(File[] files) {
180 | 0 |
             for (File file : files) {
181 | 0 |
                 scan(file);
182 |  |
             }
183 | 0 |
         }
184 |  |
185 |  |
         /**
186 |  |
          * Scans a list of files or directories. If a directory is specified, it
187 |  |
          * will be scanned recursively. Any dependencies identified are added to the
188 |  |
          * dependency collection.
189 |  |
          *
190 |  |
          * @since v0.3.2.5
191 |  |
          *
192 |  |
          * @param files a set of paths to files or directories to be analyzed.
193 |  |
          */
194 |  |
         public void scan(Set<File> files) {
195 | 0 |
             for (File file : files) {
196 | 0 |
                 scan(file);
197 |  |
             }
198 | 0 |
         }
199 |  |
200 |  |
         /**
201 |  |
          * Scans a list of files or directories. If a directory is specified, it
202 |  |
          * will be scanned recursively. Any dependencies identified are added to the
203 |  |
          * dependency collection.
204 |  |
          *
205 |  |
          * @since v0.3.2.5
206 |  |
          *
207 |  |
          * @param files a set of paths to files or directories to be analyzed.
208 |  |
          */
209 |  |
         public void scan(List<File> files) {
210 | 0 |
             for (File file : files) {
211 | 0 |
                 scan(file);
212 |  |
             }
213 | 0 |
         }
214 |  |
215 |  |
         /**
216 |  |
          * Scans a given file or directory. If a directory is specified, it will be
217 |  |
          * scanned recursively. Any dependencies identified are added to the
218 |  |
          * dependency collection.
219 |  |
          *
220 |  |
          * @since v0.3.2.4
221 |  |
          *
222 |  |
          * @param file the path to a file or directory to be analyzed.
223 |  |
          */
224 |  |
         public void scan(File file) {
225 | 13 |
             if (file.exists()) {
226 | 13 |
                 if (file.isDirectory()) {
227 | 8 |
                     scanDirectory(file);
228 |  |
                 } else {
229 | 5 |
                     scanFile(file);
230 |  |
                 }
231 |  |
             }
232 | 13 |
         }
233 |  |
234 |  |
         /**
235 |  |
          * Recursively scans files and directories. Any dependencies identified are
236 |  |
          * added to the dependency collection.
237 |  |
          *
238 |  |
          * @param dir the directory to scan.
239 |  |
          */
240 |  |
         protected void scanDirectory(File dir) {
241 | 33 |
             final File[] files = dir.listFiles();
242 | 33 |
             if (files != null) {
243 | 68 |
                 for (File f : files) {
244 | 35 |
                     if (f.isDirectory()) {
245 | 25 |
                         scanDirectory(f);
246 |  |
                     } else {
247 | 10 |
                         scanFile(f);
248 |  |
                     }
249 |  |
                 }
250 |  |
             }
251 | 33 |
         }
252 |  |
253 |  |
         /**
254 |  |
          * Scans a specified file. If a dependency is identified it is added to the
255 |  |
          * dependency collection.
256 |  |
          *
257 |  |
          * @param file The file to scan.
258 |  |
          */
259 |  |
         protected void scanFile(File file) {
260 | 15 |
             if (!file.isFile()) {
261 | 0 |
                 final String msg = String.format("Path passed to scanFile(File) is not a file: %s. Skipping the file.", file.toString());
262 | 0 |
                 Logger.getLogger(Engine.class.getName()).log(Level.FINE, msg);
263 | 0 |
                 return;
264 |  |
             }
265 | 15 |
             final String fileName = file.getName();
266 | 15 |
             final String extension = FileUtils.getFileExtension(fileName);
267 | 15 |
             if (extension != null) {
268 | 15 |
                 if (extensions.contains(extension)) {
269 | 15 |
                     final Dependency dependency = new Dependency(file);
270 | 15 |
                     dependencies.add(dependency);
271 | 15 |
                 }
272 |  |
             } else {
273 | 0 |
                 final String msg = String.format("No file extension found on file '%s'. The file was not analyzed.",
274 |  |
                         file.toString());
275 | 0 |
                 Logger.getLogger(Engine.class.getName()).log(Level.FINEST, msg);
276 |  |
             }
277 | 15 |
         }
278 |  |
279 |  |
         /**
280 |  |
          * Runs the analyzers against all of the dependencies.
281 |  |
          */
282 |  |
         public void analyzeDependencies() {
283 |  |
             //need to ensure that data exists
284 |  |
             try {
285 | 3 |
                 ensureDataExists();
286 | 0 |
             } catch (NoDataException ex) {
287 | 0 |
                 final String msg = String.format("%n%n%s%n%nUnable to continue dependency-check analysis.", ex.getMessage());
288 | 0 |
                 Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, msg);
289 | 0 |
                 Logger.getLogger(Engine.class.getName()).log(Level.FINE, null, ex);
290 | 0 |
                 return;
291 | 3 |
             }
292 |  |
293 |  |
             //phase one initialize
294 | 30 |
             for (AnalysisPhase phase : AnalysisPhase.values()) {
295 | 27 |
                 final List<Analyzer> analyzerList = analyzers.get(phase);
296 | 27 |
                 for (Analyzer a : analyzerList) {
297 |  |
                     try {
298 | 24 |
                         final String msg = String.format("Initializing %s", a.getName());
299 | 24 |
                         Logger.getLogger(Engine.class.getName()).log(Level.FINE, msg);
300 | 24 |
                         a.initialize();
301 | 0 |
                     } catch (Exception ex) {
302 | 0 |
                         final String msg = String.format("Exception occurred initializing %s.", a.getName());
303 | 0 |
                         Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, msg);
304 | 0 |
                         Logger.getLogger(Engine.class.getName()).log(Level.INFO, null, ex);
305 |  |
                         try {
306 | 0 |
                             a.close();
307 | 0 |
                         } catch (Exception ex1) {
308 | 0 |
                             Logger.getLogger(Engine.class.getName()).log(Level.FINEST, null, ex1);
309 | 0 |
                         }
310 | 48 |
                     }
311 |  |
                 }
312 |  |
             }
313 |  |
314 |  |
             // analysis phases
315 | 30 |
             for (AnalysisPhase phase : AnalysisPhase.values()) {
316 | 27 |
                 final List<Analyzer> analyzerList = analyzers.get(phase);
317 |  |
318 | 27 |
                 for (Analyzer a : analyzerList) {
319 |  |
                     /* need to create a copy of the collection because some of the
320 |  |
                      * analyzers may modify it. This prevents ConcurrentModificationExceptions.
321 |  |
                      * This is okay for adds/deletes because it happens per analyzer.
322 |  |
                      */
323 | 24 |
                     final String msg = String.format("Begin Analyzer '%s'", a.getName());
324 | 24 |
                     Logger.getLogger(Engine.class.getName()).log(Level.FINE, msg);
325 | 24 |
                     final Set<Dependency> dependencySet = new HashSet<Dependency>();
326 | 24 |
                     dependencySet.addAll(dependencies);
327 | 24 |
                     for (Dependency d : dependencySet) {
328 | 68 |
                         final String msgFile = String.format("Begin Analysis of '%s'", d.getActualFilePath());
329 | 68 |
                         Logger.getLogger(Engine.class.getName()).log(Level.FINE, msgFile);
330 | 68 |
                         if (a.supportsExtension(d.getFileExtension())) {
331 |  |
                             try {
332 | 61 |
                                 a.analyze(d, this);
333 | 0 |
                             } catch (AnalysisException ex) {
334 | 0 |
                                 d.addAnalysisException(ex);
335 | 61 |
                             }
336 |  |
                         }
337 | 68 |
                     }
338 | 24 |
                 }
339 |  |
             }
340 |  |
341 |  |
             //close/cleanup
342 | 30 |
             for (AnalysisPhase phase : AnalysisPhase.values()) {
343 | 27 |
                 final List<Analyzer> analyzerList = analyzers.get(phase);
344 | 27 |
                 for (Analyzer a : analyzerList) {
345 | 24 |
                     final String msg = String.format("Closing Analyzer '%s'", a.getName());
346 | 24 |
                     Logger.getLogger(Engine.class.getName()).log(Level.FINE, msg);
347 |  |
                     try {
348 | 24 |
                         a.close();
349 | 0 |
                     } catch (Exception ex) {
350 | 0 |
                         Logger.getLogger(Engine.class.getName()).log(Level.FINEST, null, ex);
351 | 24 |
                     }
352 | 24 |
                 }
353 |  |
             }
354 | 3 |
         }
355 |  |
356 |  |
         /**
357 |  |
          * Cycles through the cached web data sources and calls update on all of
358 |  |
          * them.
359 |  |
          */
360 |  |
         private void doUpdates() {
361 | 0 |
             final UpdateService service = UpdateService.getInstance();
362 | 0 |
             final Iterator<CachedWebDataSource> iterator = service.getDataSources();
363 | 0 |
             while (iterator.hasNext()) {
364 | 0 |
                 final CachedWebDataSource source = iterator.next();
365 |  |
                 try {
366 | 0 |
                     source.update();
367 | 0 |
                 } catch (UpdateException ex) {
368 | 0 |
                     Logger.getLogger(Engine.class.getName()).log(Level.WARNING,
369 |  |
                             "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities.");
370 | 0 |
                     Logger.getLogger(Engine.class.getName()).log(Level.FINE,
371 |  |
                             String.format("Unable to update details for %s", source.getClass().getName()), ex);
372 | 0 |
                 }
373 | 0 |
             }
374 | 0 |
         }
375 |  |
376 |  |
         /**
377 |  |
          * Returns a full list of all of the analyzers. This is useful for reporting
378 |  |
          * which analyzers where used.
379 |  |
          *
380 |  |
          * @return a list of Analyzers
381 |  |
          */
382 |  |
         public List<Analyzer> getAnalyzers() {
383 | 1 |
             final List<Analyzer> ret = new ArrayList<Analyzer>();
384 | 10 |
             for (AnalysisPhase phase : AnalysisPhase.values()) {
385 | 9 |
                 final List<Analyzer> analyzerList = analyzers.get(phase);
386 | 9 |
                 ret.addAll(analyzerList);
387 |  |
             }
388 | 1 |
             return ret;
389 |  |
         }
390 |  |
391 |  |
         /**
392 |  |
          * Checks all analyzers to see if an extension is supported.
393 |  |
          *
394 |  |
          * @param ext a file extension
395 |  |
          * @return true or false depending on whether or not the file extension is
396 |  |
          * supported
397 |  |
          */
398 |  |
         public boolean supportsExtension(String ext) {
399 | 143 |
             if (ext == null) {
400 | 8 |
                 return false;
401 |  |
             }
402 | 1267 |
             for (AnalysisPhase phase : AnalysisPhase.values()) {
403 | 1142 |
                 final List<Analyzer> analyzerList = analyzers.get(phase);
404 | 1142 |
                 for (Analyzer a : analyzerList) {
405 | 1017 |
                     if (a.getSupportedExtensions() != null && a.supportsExtension(ext)) {
406 | 10 |
                         return true;
407 |  |
                     }
408 |  |
                 }
409 |  |
             }
410 | 125 |
             return false;
411 |  |
         }
412 |  |
413 |  |
         /**
414 |  |
          * Checks the CPE Index to ensure documents exists. If none exist a
415 |  |
          * NoDataException is thrown.
416 |  |
          *
417 |  |
          * @throws NoDataException thrown if no data exists in the CPE Index
418 |  |
          */
419 |  |
         private void ensureDataExists() throws NoDataException {
420 | 3 |
             CpeIndexReader cpe = null;
421 | 3 |
             boolean noDataExists = false;
422 |  |
             try {
423 | 3 |
                 cpe = new CpeIndexReader();
424 | 3 |
                 cpe.open();
425 | 3 |
                 if (cpe.numDocs() <= 0) {
426 | 0 |
                     noDataExists = true;
427 |  |
                 }
428 | 0 |
             } catch (IOException ex) {
429 | 0 |
                 noDataExists = true;
430 | 0 |
             } catch (NullPointerException ex) {
431 | 0 |
                 noDataExists = true;
432 |  |
             } finally {
433 | 3 |
                 if (cpe != null) {
434 | 3 |
                     cpe.close();
435 |  |
                 }
436 |  |
             }
437 | 3 |
             if (noDataExists) {
438 | 0 |
                 throw new NoDataException("No data exists in the data store. Please check that you are able to connect "
439 |  |
                         + "to the Internet and re-run dependency-check. If the problem persists determine whether you need "
440 |  |
                         + "to set a proxy url and port.\\n\\nIf you are unable to solve this problem please contact the mailing "
441 |  |
                         + "list for help: dependency-check@googlegroups.com");
442 |  |
443 |  |
             }
444 | 3 |
         }
445 |  |
     }