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