Coverage Report - org.owasp.dependencycheck.Engine
 
Classes in this File Line Coverage Branch Coverage Complexity
Engine
52%
102/195
54%
37/68
3.167
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 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 org.owasp.dependencycheck.analyzer.AnalysisPhase;
 21  
 import org.owasp.dependencycheck.analyzer.Analyzer;
 22  
 import org.owasp.dependencycheck.analyzer.AnalyzerService;
 23  
 import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
 24  
 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
 25  
 import org.owasp.dependencycheck.data.nvdcve.ConnectionFactory;
 26  
 import org.owasp.dependencycheck.data.nvdcve.CveDB;
 27  
 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
 28  
 import org.owasp.dependencycheck.data.update.CachedWebDataSource;
 29  
 import org.owasp.dependencycheck.data.update.UpdateService;
 30  
 import org.owasp.dependencycheck.data.update.exception.UpdateException;
 31  
 import org.owasp.dependencycheck.dependency.Dependency;
 32  
 import org.owasp.dependencycheck.exception.NoDataException;
 33  
 import org.owasp.dependencycheck.utils.InvalidSettingException;
 34  
 import org.owasp.dependencycheck.utils.Settings;
 35  
 import org.slf4j.Logger;
 36  
 import org.slf4j.LoggerFactory;
 37  
 
 38  
 import java.io.File;
 39  
 import java.io.FileFilter;
 40  
 import java.util.ArrayList;
 41  
 import java.util.EnumMap;
 42  
 import java.util.HashSet;
 43  
 import java.util.Iterator;
 44  
 import java.util.List;
 45  
 import java.util.Map;
 46  
 import java.util.Set;
 47  
 
 48  
 /**
 49  
  * Scans files, directories, etc. for Dependencies. Analyzers are loaded and used to process the files found by the scan, if a
 50  
  * file is encountered and an Analyzer is associated with the file type then the file is turned into a dependency.
 51  
  *
 52  
  * @author Jeremy Long
 53  
  */
 54  
 public class Engine implements FileFilter {
 55  
 
 56  
     /**
 57  
      * The list of dependencies.
 58  
      */
 59  3
     private List<Dependency> dependencies = new ArrayList<Dependency>();
 60  
     /**
 61  
      * A Map of analyzers grouped by Analysis phase.
 62  
      */
 63  3
     private Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<AnalysisPhase, List<Analyzer>>(AnalysisPhase.class);
 64  
 
 65  
     /**
 66  
      * A Map of analyzers grouped by Analysis phase.
 67  
      */
 68  3
     private Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<FileTypeAnalyzer>();
 69  
 
 70  
     /**
 71  
      * The ClassLoader to use when dynamically loading Analyzer and Update services.
 72  
      */
 73  3
     private ClassLoader serviceClassLoader = Thread.currentThread().getContextClassLoader();
 74  
     /**
 75  
      * The Logger for use throughout the class.
 76  
      */
 77  1
     private static final Logger LOGGER = LoggerFactory.getLogger(Engine.class);
 78  
 
 79  
     /**
 80  
      * Creates a new Engine.
 81  
      *
 82  
      * @throws DatabaseException thrown if there is an error connecting to the database
 83  
      */
 84  3
     public Engine() throws DatabaseException {
 85  3
         initializeEngine();
 86  3
     }
 87  
 
 88  
     /**
 89  
      * Creates a new Engine.
 90  
      *
 91  
      * @param serviceClassLoader a reference the class loader being used
 92  
      * @throws DatabaseException thrown if there is an error connecting to the database
 93  
      */
 94  0
     public Engine(ClassLoader serviceClassLoader) throws DatabaseException {
 95  0
         this.serviceClassLoader = serviceClassLoader;
 96  0
         initializeEngine();
 97  0
     }
 98  
 
 99  
     /**
 100  
      * Creates a new Engine using the specified classloader to dynamically load Analyzer and Update services.
 101  
      *
 102  
      * @throws DatabaseException thrown if there is an error connecting to the database
 103  
      */
 104  
     protected final void initializeEngine() throws DatabaseException {
 105  3
         ConnectionFactory.initialize();
 106  3
         loadAnalyzers();
 107  3
     }
 108  
 
 109  
     /**
 110  
      * Properly cleans up resources allocated during analysis.
 111  
      */
 112  
     public void cleanup() {
 113  0
         ConnectionFactory.cleanup();
 114  0
     }
 115  
 
 116  
     /**
 117  
      * Loads the analyzers specified in the configuration file (or system properties).
 118  
      */
 119  
     private void loadAnalyzers() {
 120  3
         if (!analyzers.isEmpty()) {
 121  0
             return;
 122  
         }
 123  30
         for (AnalysisPhase phase : AnalysisPhase.values()) {
 124  27
             analyzers.put(phase, new ArrayList<Analyzer>());
 125  
         }
 126  
 
 127  3
         final AnalyzerService service = new AnalyzerService(serviceClassLoader);
 128  3
         final Iterator<Analyzer> iterator = service.getAnalyzers();
 129  69
         while (iterator.hasNext()) {
 130  66
             final Analyzer a = iterator.next();
 131  66
             analyzers.get(a.getAnalysisPhase()).add(a);
 132  66
             if (a instanceof FileTypeAnalyzer) {
 133  42
                 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
 134  
             }
 135  66
         }
 136  3
     }
 137  
 
 138  
     /**
 139  
      * Get the List of the analyzers for a specific phase of analysis.
 140  
      *
 141  
      * @param phase the phase to get the configured analyzers.
 142  
      * @return the analyzers loaded
 143  
      */
 144  
     public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
 145  0
         return analyzers.get(phase);
 146  
     }
 147  
 
 148  
     /**
 149  
      * Get the dependencies identified.
 150  
      *
 151  
      * @return the dependencies identified
 152  
      */
 153  
     public List<Dependency> getDependencies() {
 154  45
         return dependencies;
 155  
     }
 156  
 
 157  
     /**
 158  
      * Sets the dependencies.
 159  
      *
 160  
      * @param dependencies the dependencies
 161  
      */
 162  
     public void setDependencies(List<Dependency> dependencies) {
 163  0
         this.dependencies = dependencies;
 164  0
     }
 165  
 
 166  
     /**
 167  
      * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
 168  
      * identified are added to the dependency collection.
 169  
      *
 170  
      * @param paths an array of paths to files or directories to be analyzed
 171  
      * @return the list of dependencies scanned
 172  
      * @since v0.3.2.5
 173  
      */
 174  
     public List<Dependency> scan(String[] paths) {
 175  0
         final List<Dependency> deps = new ArrayList<Dependency>();
 176  0
         for (String path : paths) {
 177  0
             final File file = new File(path);
 178  0
             final List<Dependency> d = scan(file);
 179  0
             if (d != null) {
 180  0
                 deps.addAll(d);
 181  
             }
 182  
         }
 183  0
         return deps;
 184  
     }
 185  
 
 186  
     /**
 187  
      * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
 188  
      * are added to the dependency collection.
 189  
      *
 190  
      * @param path the path to a file or directory to be analyzed
 191  
      * @return the list of dependencies scanned
 192  
      */
 193  
     public List<Dependency> scan(String path) {
 194  0
         final File file = new File(path);
 195  0
         return scan(file);
 196  
     }
 197  
 
 198  
     /**
 199  
      * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
 200  
      * identified are added to the dependency collection.
 201  
      *
 202  
      * @param files an array of paths to files or directories to be analyzed.
 203  
      * @return the list of dependencies
 204  
      * @since v0.3.2.5
 205  
      */
 206  
     public List<Dependency> scan(File[] files) {
 207  0
         final List<Dependency> deps = new ArrayList<Dependency>();
 208  0
         for (File file : files) {
 209  0
             final List<Dependency> d = scan(file);
 210  0
             if (d != null) {
 211  0
                 deps.addAll(d);
 212  
             }
 213  
         }
 214  0
         return deps;
 215  
     }
 216  
 
 217  
     /**
 218  
      * Scans a list of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
 219  
      * identified are added to the dependency collection.
 220  
      *
 221  
      * @param files a set of paths to files or directories to be analyzed
 222  
      * @return the list of dependencies scanned
 223  
      * @since v0.3.2.5
 224  
      */
 225  
     public List<Dependency> scan(Set<File> files) {
 226  0
         final List<Dependency> deps = new ArrayList<Dependency>();
 227  0
         for (File file : files) {
 228  0
             final List<Dependency> d = scan(file);
 229  0
             if (d != null) {
 230  0
                 deps.addAll(d);
 231  
             }
 232  0
         }
 233  0
         return deps;
 234  
     }
 235  
 
 236  
     /**
 237  
      * Scans a list of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
 238  
      * identified are added to the dependency collection.
 239  
      *
 240  
      * @param files a set of paths to files or directories to be analyzed
 241  
      * @return the list of dependencies scanned
 242  
      * @since v0.3.2.5
 243  
      */
 244  
     public List<Dependency> scan(List<File> files) {
 245  0
         final List<Dependency> deps = new ArrayList<Dependency>();
 246  0
         for (File file : files) {
 247  0
             final List<Dependency> d = scan(file);
 248  0
             if (d != null) {
 249  0
                 deps.addAll(d);
 250  
             }
 251  0
         }
 252  0
         return deps;
 253  
     }
 254  
 
 255  
     /**
 256  
      * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
 257  
      * are added to the dependency collection.
 258  
      *
 259  
      * @param file the path to a file or directory to be analyzed
 260  
      * @return the list of dependencies scanned
 261  
      * @since v0.3.2.4
 262  
      */
 263  
     public List<Dependency> scan(File file) {
 264  4
         if (file.exists()) {
 265  4
             if (file.isDirectory()) {
 266  2
                 return scanDirectory(file);
 267  
             } else {
 268  2
                 final Dependency d = scanFile(file);
 269  2
                 if (d != null) {
 270  2
                     final List<Dependency> deps = new ArrayList<Dependency>();
 271  2
                     deps.add(d);
 272  2
                     return deps;
 273  
                 }
 274  
             }
 275  
         }
 276  0
         return null;
 277  
     }
 278  
 
 279  
     /**
 280  
      * Recursively scans files and directories. Any dependencies identified are added to the dependency collection.
 281  
      *
 282  
      * @param dir the directory to scan
 283  
      * @return the list of Dependency objects scanned
 284  
      */
 285  
     protected List<Dependency> scanDirectory(File dir) {
 286  38
         final File[] files = dir.listFiles();
 287  38
         final List<Dependency> deps = new ArrayList<Dependency>();
 288  38
         if (files != null) {
 289  74
             for (File f : files) {
 290  36
                 if (f.isDirectory()) {
 291  36
                     final List<Dependency> d = scanDirectory(f);
 292  36
                     if (d != null) {
 293  36
                         deps.addAll(d);
 294  
                     }
 295  36
                 } else {
 296  0
                     final Dependency d = scanFile(f);
 297  0
                     deps.add(d);
 298  
                 }
 299  
             }
 300  
         }
 301  38
         return deps;
 302  
     }
 303  
 
 304  
     /**
 305  
      * Scans a specified file. If a dependency is identified it is added to the dependency collection.
 306  
      *
 307  
      * @param file The file to scan
 308  
      * @return the scanned dependency
 309  
      */
 310  
     protected Dependency scanFile(File file) {
 311  2
         Dependency dependency = null;
 312  2
         if (file.isFile()) {
 313  2
             if (accept(file)) {
 314  2
                 dependency = new Dependency(file);
 315  2
                 dependencies.add(dependency);
 316  
             }
 317  
         } else {
 318  0
             LOGGER.debug("Path passed to scanFile(File) is not a file: {}. Skipping the file.", file);
 319  
         }
 320  2
         return dependency;
 321  
     }
 322  
 
 323  
     /**
 324  
      * Runs the analyzers against all of the dependencies. Since the mutable dependencies list is exposed via
 325  
      * {@link #getDependencies()}, this method iterates over a copy of the dependencies list. Thus, the potential for
 326  
      * {@link java.util.ConcurrentModificationException}s is avoided, and analyzers may safely add or remove entries from the
 327  
      * dependencies list.
 328  
      */
 329  
     public void analyzeDependencies() {
 330  1
         boolean autoUpdate = true;
 331  
         try {
 332  1
             autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
 333  0
         } catch (InvalidSettingException ex) {
 334  0
             LOGGER.debug("Invalid setting for auto-update; using true.");
 335  1
         }
 336  1
         if (autoUpdate) {
 337  0
             doUpdates();
 338  
         }
 339  
 
 340  
         //need to ensure that data exists
 341  
         try {
 342  1
             ensureDataExists();
 343  0
         } catch (NoDataException ex) {
 344  0
             LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
 345  0
             LOGGER.debug("", ex);
 346  0
             return;
 347  0
         } catch (DatabaseException ex) {
 348  0
             LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
 349  0
             LOGGER.debug("", ex);
 350  0
             return;
 351  
 
 352  1
         }
 353  
 
 354  1
         LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
 355  1
         LOGGER.info("Analysis Starting");
 356  1
         final long analysisStart = System.currentTimeMillis();
 357  
 
 358  
         // analysis phases
 359  10
         for (AnalysisPhase phase : AnalysisPhase.values()) {
 360  9
             final List<Analyzer> analyzerList = analyzers.get(phase);
 361  
 
 362  9
             for (Analyzer a : analyzerList) {
 363  22
                 a = initializeAnalyzer(a);
 364  
 
 365  
                 /* need to create a copy of the collection because some of the
 366  
                  * analyzers may modify it. This prevents ConcurrentModificationExceptions.
 367  
                  * This is okay for adds/deletes because it happens per analyzer.
 368  
                  */
 369  22
                 LOGGER.debug("Begin Analyzer '{}'", a.getName());
 370  22
                 final Set<Dependency> dependencySet = new HashSet<Dependency>(dependencies);
 371  22
                 for (Dependency d : dependencySet) {
 372  44
                     boolean shouldAnalyze = true;
 373  44
                     if (a instanceof FileTypeAnalyzer) {
 374  28
                         final FileTypeAnalyzer fAnalyzer = (FileTypeAnalyzer) a;
 375  28
                         shouldAnalyze = fAnalyzer.accept(d.getActualFile());
 376  
                     }
 377  44
                     if (shouldAnalyze) {
 378  20
                         LOGGER.debug("Begin Analysis of '{}'", d.getActualFilePath());
 379  
                         try {
 380  20
                             a.analyze(d, this);
 381  0
                         } catch (AnalysisException ex) {
 382  0
                             LOGGER.warn("An error occurred while analyzing '{}'.", d.getActualFilePath());
 383  0
                             LOGGER.debug("", ex);
 384  0
                         } catch (Throwable ex) {
 385  
                             //final AnalysisException ax = new AnalysisException(axMsg, ex);
 386  0
                             LOGGER.warn("An unexpected error occurred during analysis of '{}'", d.getActualFilePath());
 387  0
                             LOGGER.debug("", ex);
 388  20
                         }
 389  
                     }
 390  44
                 }
 391  22
             }
 392  
         }
 393  10
         for (AnalysisPhase phase : AnalysisPhase.values()) {
 394  9
             final List<Analyzer> analyzerList = analyzers.get(phase);
 395  
 
 396  9
             for (Analyzer a : analyzerList) {
 397  22
                 closeAnalyzer(a);
 398  22
             }
 399  
         }
 400  
 
 401  1
         LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
 402  1
         LOGGER.info("Analysis Complete ({} ms)", System.currentTimeMillis() - analysisStart);
 403  1
     }
 404  
 
 405  
     /**
 406  
      * Initializes the given analyzer.
 407  
      *
 408  
      * @param analyzer the analyzer to initialize
 409  
      * @return the initialized analyzer
 410  
      */
 411  
     protected Analyzer initializeAnalyzer(Analyzer analyzer) {
 412  
         try {
 413  22
             LOGGER.debug("Initializing {}", analyzer.getName());
 414  22
             analyzer.initialize();
 415  0
         } catch (Throwable ex) {
 416  0
             LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
 417  0
             LOGGER.debug("", ex);
 418  
             try {
 419  0
                 analyzer.close();
 420  0
             } catch (Throwable ex1) {
 421  0
                 LOGGER.trace("", ex1);
 422  0
             }
 423  22
         }
 424  22
         return analyzer;
 425  
     }
 426  
 
 427  
     /**
 428  
      * Closes the given analyzer.
 429  
      *
 430  
      * @param analyzer the analyzer to close
 431  
      */
 432  
     protected void closeAnalyzer(Analyzer analyzer) {
 433  22
         LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
 434  
         try {
 435  22
             analyzer.close();
 436  0
         } catch (Throwable ex) {
 437  0
             LOGGER.trace("", ex);
 438  22
         }
 439  22
     }
 440  
 
 441  
     /**
 442  
      * Cycles through the cached web data sources and calls update on all of them.
 443  
      */
 444  
     public void doUpdates() {
 445  0
         LOGGER.info("Checking for updates");
 446  0
         final long updateStart = System.currentTimeMillis();
 447  0
         final UpdateService service = new UpdateService(serviceClassLoader);
 448  0
         final Iterator<CachedWebDataSource> iterator = service.getDataSources();
 449  0
         while (iterator.hasNext()) {
 450  0
             final CachedWebDataSource source = iterator.next();
 451  
             try {
 452  0
                 source.update();
 453  0
             } catch (UpdateException ex) {
 454  0
                 LOGGER.warn(
 455  
                         "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities.");
 456  0
                 LOGGER.debug("Unable to update details for {}", source.getClass().getName(), ex);
 457  0
             }
 458  0
         }
 459  0
         LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
 460  0
     }
 461  
 
 462  
     /**
 463  
      * Returns a full list of all of the analyzers. This is useful for reporting which analyzers where used.
 464  
      *
 465  
      * @return a list of Analyzers
 466  
      */
 467  
     public List<Analyzer> getAnalyzers() {
 468  0
         final List<Analyzer> ret = new ArrayList<Analyzer>();
 469  0
         for (AnalysisPhase phase : AnalysisPhase.values()) {
 470  0
             final List<Analyzer> analyzerList = analyzers.get(phase);
 471  0
             ret.addAll(analyzerList);
 472  
         }
 473  0
         return ret;
 474  
     }
 475  
 
 476  
     /**
 477  
      * Checks all analyzers to see if an extension is supported.
 478  
      *
 479  
      * @param file a file extension
 480  
      * @return true or false depending on whether or not the file extension is supported
 481  
      */
 482  
     @Override
 483  
     public boolean accept(File file) {
 484  851
         if (file == null) {
 485  0
             return false;
 486  
         }
 487  851
         boolean scan = false;
 488  851
         for (FileTypeAnalyzer a : this.fileTypeAnalyzers) {
 489  
             /* note, we can't break early on this loop as the analyzers need to know if
 490  
              they have files to work on prior to initialization */
 491  11914
             scan |= a.accept(file);
 492  11914
         }
 493  851
         return scan;
 494  
     }
 495  
 
 496  
     /**
 497  
      * Returns the set of file type analyzers.
 498  
      *
 499  
      * @return the set of file type analyzers
 500  
      */
 501  
     public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
 502  0
         return this.fileTypeAnalyzers;
 503  
     }
 504  
 
 505  
     /**
 506  
      * Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown.
 507  
      *
 508  
      * @throws NoDataException thrown if no data exists in the CPE Index
 509  
      * @throws DatabaseException thrown if there is an exception opening the database
 510  
      */
 511  
     private void ensureDataExists() throws NoDataException, DatabaseException {
 512  1
         final CveDB cve = new CveDB();
 513  
         try {
 514  1
             cve.open();
 515  1
             if (!cve.dataExists()) {
 516  0
                 throw new NoDataException("No documents exist");
 517  
             }
 518  0
         } catch (DatabaseException ex) {
 519  0
             throw new NoDataException(ex.getMessage(), ex);
 520  
         } finally {
 521  1
             cve.close();
 522  1
         }
 523  1
     }
 524  
 }