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