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