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