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