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