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