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