View Javadoc
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) 2017 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.analyzer;
19  
20  import java.util.Arrays;
21  import java.util.HashSet;
22  import java.util.Set;
23  import javax.annotation.concurrent.ThreadSafe;
24  import org.owasp.dependencycheck.Engine;
25  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
26  import org.owasp.dependencycheck.dependency.Dependency;
27  
28  /**
29   * <p>
30   * This analyzer ensures dependencies that should be grouped together, to remove
31   * excess noise from the report, are grouped. An example would be Spring, Spring
32   * Beans, Spring MVC, etc. If they are all for the same version and have the
33   * same relative path then these should be grouped into a single dependency
34   * under the core/main library.</p>
35   * <p>
36   * Note, this grouping only works on dependencies with identified CVE
37   * entries</p>
38   *
39   * @author Jeremy Long
40   */
41  @ThreadSafe
42  public abstract class AbstractDependencyComparingAnalyzer extends AbstractAnalyzer {
43  
44      /**
45       * a flag indicating if this analyzer has run. This analyzer only runs once.
46       */
47      private boolean analyzed = false;
48  
49      /**
50       * Returns a flag indicating if this analyzer has run. This analyzer only
51       * runs once. Note this is currently only used in the unit tests.
52       *
53       * @return a flag indicating if this analyzer has run. This analyzer only
54       * runs once
55       */
56      protected synchronized boolean getAnalyzed() {
57          return analyzed;
58      }
59  
60      /**
61       * Does not support parallel processing as it only runs once and then
62       * operates on <em>all</em> dependencies.
63       *
64       * @return whether or not parallel processing is enabled
65       * @see #analyze(Dependency, Engine)
66       */
67      @Override
68      public final boolean supportsParallelProcessing() {
69          return false;
70      }
71  
72      /**
73       * Analyzes a set of dependencies. If they have been found to have the same
74       * base path and the same set of identifiers they are likely related. The
75       * related dependencies are bundled into a single reportable item.
76       *
77       * @param ignore this analyzer ignores the dependency being analyzed
78       * @param engine the engine that is scanning the dependencies
79       * @throws AnalysisException is thrown if there is an error reading the JAR
80       * file.
81       */
82      @Override
83      protected synchronized void analyzeDependency(Dependency ignore, Engine engine) throws AnalysisException {
84          if (!analyzed) {
85              analyzed = true;
86              final Set<Dependency> dependenciesToRemove = new HashSet<>();
87  
88              final Dependency[] dependencies = engine.getDependencies();
89              if (dependencies.length < 2) {
90                  return;
91              }
92              Arrays.sort(dependencies, Dependency.NAME_COMPARATOR);
93              for (int x = 0; x < dependencies.length - 1; x++) {
94                  final Dependency dependency = dependencies[x];
95                  if (!dependenciesToRemove.contains(dependency)) {
96                      for (int y = x + 1; y < dependencies.length; y++) {
97                          final Dependency nextDependency = dependencies[y];
98                          if (evaluateDependencies(dependency, nextDependency, dependenciesToRemove)) {
99                              break;
100                         }
101                     }
102                 }
103             }
104             dependenciesToRemove.forEach(engine::removeDependency);
105         }
106     }
107 
108     /**
109      * Evaluates the dependencies
110      *
111      * @param dependency a dependency to compare
112      * @param nextDependency a dependency to compare
113      * @param dependenciesToRemove a set of dependencies that will be removed
114      * @return true if a dependency is removed; otherwise false
115      */
116     protected abstract boolean evaluateDependencies(Dependency dependency,
117             Dependency nextDependency, Set<Dependency> dependenciesToRemove);
118 }