Coverage Report - org.owasp.dependencycheck.analyzer.FalsePositiveAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
FalsePositiveAnalyzer
46%
107/228
25%
58/230
9.714
 
 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.analyzer;
 19  
 
 20  
 import java.io.FileFilter;
 21  
 import java.io.UnsupportedEncodingException;
 22  
 import java.net.URLEncoder;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Collections;
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 import java.util.ListIterator;
 28  
 import java.util.Set;
 29  
 import java.util.regex.Matcher;
 30  
 import java.util.regex.Pattern;
 31  
 import org.owasp.dependencycheck.Engine;
 32  
 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
 33  
 import org.owasp.dependencycheck.dependency.Dependency;
 34  
 import org.owasp.dependencycheck.dependency.Identifier;
 35  
 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
 36  
 import org.owasp.dependencycheck.utils.FileFilterBuilder;
 37  
 import org.owasp.dependencycheck.utils.Settings;
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 
 41  
 /**
 42  
  * This analyzer attempts to remove some well known false positives - specifically regarding the java runtime.
 43  
  *
 44  
  * @author Jeremy Long
 45  
  */
 46  11
 public class FalsePositiveAnalyzer extends AbstractAnalyzer {
 47  
 
 48  
     /**
 49  
      * The Logger.
 50  
      */
 51  1
     private static final Logger LOGGER = LoggerFactory.getLogger(FalsePositiveAnalyzer.class);
 52  
 
 53  
     /**
 54  
      * The file filter used to find DLL and EXE.
 55  
      */
 56  1
     private static final FileFilter DLL_EXE_FILTER = FileFilterBuilder.newInstance().addExtensions("dll", "exe").build();
 57  
 
 58  
     //<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
 59  
     /**
 60  
      * The name of the analyzer.
 61  
      */
 62  
     private static final String ANALYZER_NAME = "False Positive Analyzer";
 63  
     /**
 64  
      * The phase that this analyzer is intended to run in.
 65  
      */
 66  1
     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
 67  
 
 68  
     /**
 69  
      * Returns the name of the analyzer.
 70  
      *
 71  
      * @return the name of the analyzer.
 72  
      */
 73  
     @Override
 74  
     public String getName() {
 75  26
         return ANALYZER_NAME;
 76  
     }
 77  
 
 78  
     /**
 79  
      * Returns the phase that the analyzer is intended to run in.
 80  
      *
 81  
      * @return the phase that the analyzer is intended to run in.
 82  
      */
 83  
     @Override
 84  
     public AnalysisPhase getAnalysisPhase() {
 85  7
         return ANALYSIS_PHASE;
 86  
     }
 87  
     /**
 88  
      * <p>
 89  
      * Returns the setting key to determine if the analyzer is enabled.</p>
 90  
      *
 91  
      * @return the key for the analyzer's enabled property
 92  
      */
 93  
     @Override
 94  
     protected String getAnalyzerEnabledSettingKey() {
 95  2
         return Settings.KEYS.ANALYZER_FALSE_POSITIVE_ENABLED;
 96  
     }
 97  
     //</editor-fold>
 98  
 
 99  
     /**
 100  
      * Analyzes the dependencies and removes bad/incorrect CPE associations based on various heuristics.
 101  
      *
 102  
      * @param dependency the dependency to analyze.
 103  
      * @param engine the engine that is scanning the dependencies
 104  
      * @throws AnalysisException is thrown if there is an error reading the JAR file.
 105  
      */
 106  
     @Override
 107  
     protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
 108  4
         removeJreEntries(dependency);
 109  4
         removeBadMatches(dependency);
 110  4
         removeBadSpringMatches(dependency);
 111  4
         removeWrongVersionMatches(dependency);
 112  4
         removeSpuriousCPE(dependency);
 113  4
         removeDuplicativeEntriesFromJar(dependency, engine);
 114  4
         addFalseNegativeCPEs(dependency);
 115  5
     }
 116  
 
 117  
     /**
 118  
      * Removes inaccurate matches on springframework CPEs.
 119  
      *
 120  
      * @param dependency the dependency to test for and remove known inaccurate CPE matches
 121  
      */
 122  
     private void removeBadSpringMatches(Dependency dependency) {
 123  4
         String mustContain = null;
 124  4
         for (Identifier i : dependency.getIdentifiers()) {
 125  4
             if ("maven".contains(i.getType())) {
 126  0
                 if (i.getValue() != null && i.getValue().startsWith("org.springframework.")) {
 127  0
                     final int endPoint = i.getValue().indexOf(':', 19);
 128  0
                     if (endPoint >= 0) {
 129  0
                         mustContain = i.getValue().substring(19, endPoint).toLowerCase();
 130  0
                         break;
 131  
                     }
 132  
                 }
 133  
             }
 134  4
         }
 135  4
         if (mustContain != null) {
 136  0
             final Iterator<Identifier> itr = dependency.getIdentifiers().iterator();
 137  0
             while (itr.hasNext()) {
 138  0
                 final Identifier i = itr.next();
 139  0
                 if ("cpe".contains(i.getType())
 140  0
                         && i.getValue() != null
 141  0
                         && i.getValue().startsWith("cpe:/a:springsource:")
 142  0
                         && !i.getValue().toLowerCase().contains(mustContain)) {
 143  0
                     itr.remove();
 144  
                     //dependency.getIdentifiers().remove(i);
 145  
                 }
 146  0
             }
 147  
         }
 148  4
     }
 149  
 
 150  
     /**
 151  
      * <p>
 152  
      * Intended to remove spurious CPE entries. By spurious we mean duplicate, less specific CPE entries.</p>
 153  
      * <p>
 154  
      * Example:</p>
 155  
      * <code>
 156  
      * cpe:/a:some-vendor:some-product
 157  
      * cpe:/a:some-vendor:some-product:1.5
 158  
      * cpe:/a:some-vendor:some-product:1.5.2
 159  
      * </code>
 160  
      * <p>
 161  
      * Should be trimmed to:</p>
 162  
      * <code>
 163  
      * cpe:/a:some-vendor:some-product:1.5.2
 164  
      * </code>
 165  
      *
 166  
      * @param dependency the dependency being analyzed
 167  
      */
 168  
     @SuppressWarnings("null")
 169  
     private void removeSpuriousCPE(Dependency dependency) {
 170  5
         final List<Identifier> ids = new ArrayList<Identifier>(dependency.getIdentifiers());
 171  4
         Collections.sort(ids);
 172  4
         final ListIterator<Identifier> mainItr = ids.listIterator();
 173  8
         while (mainItr.hasNext()) {
 174  4
             final Identifier currentId = mainItr.next();
 175  4
             final VulnerableSoftware currentCpe = parseCpe(currentId.getType(), currentId.getValue());
 176  4
             if (currentCpe == null) {
 177  0
                 continue;
 178  
             }
 179  4
             final ListIterator<Identifier> subItr = ids.listIterator(mainItr.nextIndex());
 180  10
             while (subItr.hasNext()) {
 181  6
                 final Identifier nextId = subItr.next();
 182  6
                 final VulnerableSoftware nextCpe = parseCpe(nextId.getType(), nextId.getValue());
 183  6
                 if (nextCpe == null) {
 184  0
                     continue;
 185  
                 }
 186  
                 //TODO fix the version problem below
 187  6
                 if (currentCpe.getVendor().equals(nextCpe.getVendor())) {
 188  0
                     if (currentCpe.getProduct().equals(nextCpe.getProduct())) {
 189  
                         // see if one is contained in the other.. remove the contained one from dependency.getIdentifier
 190  0
                         final String currentVersion = currentCpe.getVersion();
 191  0
                         final String nextVersion = nextCpe.getVersion();
 192  0
                         if (currentVersion == null && nextVersion == null) {
 193  
                             //how did we get here?
 194  0
                             LOGGER.debug("currentVersion and nextVersion are both null?");
 195  0
                         } else if (currentVersion == null && nextVersion != null) {
 196  0
                             dependency.getIdentifiers().remove(currentId);
 197  0
                         } else if (nextVersion == null && currentVersion != null) {
 198  0
                             dependency.getIdentifiers().remove(nextId);
 199  0
                         } else if (currentVersion.length() < nextVersion.length()) {
 200  0
                             if (nextVersion.startsWith(currentVersion) || "-".equals(currentVersion)) {
 201  0
                                 dependency.getIdentifiers().remove(currentId);
 202  
                             }
 203  
                         } else {
 204  0
                             if (currentVersion.startsWith(nextVersion) || "-".equals(nextVersion)) {
 205  0
                                 dependency.getIdentifiers().remove(nextId);
 206  
                             }
 207  
                         }
 208  
                     }
 209  
                 }
 210  6
             }
 211  4
         }
 212  4
     }
 213  
     /**
 214  
      * Regex to identify core java libraries and a few other commonly misidentified ones.
 215  
      */
 216  1
     public static final Pattern CORE_JAVA = Pattern.compile("^cpe:/a:(sun|oracle|ibm):(j2[ems]e|"
 217  
             + "java(_platform_micro_edition|_runtime_environment|_se|virtual_machine|se_development_kit|fx)?|"
 218  
             + "jdk|jre|jsse)($|:.*)");
 219  
 
 220  
     /**
 221  
      * Regex to identify core jsf libraries.
 222  
      */
 223  1
     public static final Pattern CORE_JAVA_JSF = Pattern.compile("^cpe:/a:(sun|oracle|ibm):jsf($|:.*)");
 224  
     /**
 225  
      * Regex to identify core java library files. This is currently incomplete.
 226  
      */
 227  1
     public static final Pattern CORE_FILES = Pattern.compile("(^|/)((alt[-])?rt|jsse|jfxrt|jfr|jce|javaws|deploy|charsets)\\.jar$");
 228  
     /**
 229  
      * Regex to identify core jsf java library files. This is currently incomplete.
 230  
      */
 231  1
     public static final Pattern CORE_JSF_FILES = Pattern.compile("(^|/)jsf[-][^/]*\\.jar$");
 232  
 
 233  
     /**
 234  
      * Removes any CPE entries for the JDK/JRE unless the filename ends with rt.jar
 235  
      *
 236  
      * @param dependency the dependency to remove JRE CPEs from
 237  
      */
 238  
     private void removeJreEntries(Dependency dependency) {
 239  4
         final Set<Identifier> identifiers = dependency.getIdentifiers();
 240  4
         final Iterator<Identifier> itr = identifiers.iterator();
 241  9
         while (itr.hasNext()) {
 242  5
             final Identifier i = itr.next();
 243  5
             final Matcher coreCPE = CORE_JAVA.matcher(i.getValue());
 244  5
             final Matcher coreFiles = CORE_FILES.matcher(dependency.getFileName());
 245  5
             if (coreCPE.matches() && !coreFiles.matches()) {
 246  0
                 itr.remove();
 247  
             }
 248  5
             final Matcher coreJsfCPE = CORE_JAVA_JSF.matcher(i.getValue());
 249  5
             final Matcher coreJsfFiles = CORE_JSF_FILES.matcher(dependency.getFileName());
 250  5
             if (coreJsfCPE.matches() && !coreJsfFiles.matches()) {
 251  0
                 itr.remove();
 252  
             }
 253  5
         }
 254  4
     }
 255  
 
 256  
     /**
 257  
      * Parses a CPE string into an IndexEntry.
 258  
      *
 259  
      * @param type the type of identifier
 260  
      * @param value the cpe identifier to parse
 261  
      * @return an VulnerableSoftware object constructed from the identifier
 262  
      */
 263  
     private VulnerableSoftware parseCpe(String type, String value) {
 264  10
         if (!"cpe".equals(type)) {
 265  0
             return null;
 266  
         }
 267  10
         final VulnerableSoftware cpe = new VulnerableSoftware();
 268  
         try {
 269  10
             cpe.parseName(value);
 270  0
         } catch (UnsupportedEncodingException ex) {
 271  0
             LOGGER.trace("", ex);
 272  0
             return null;
 273  10
         }
 274  10
         return cpe;
 275  
     }
 276  
 
 277  
     /**
 278  
      * Removes bad CPE matches for a dependency. Unfortunately, right now these are hard-coded patches for specific problems
 279  
      * identified when testing this on a LARGE volume of jar files.
 280  
      *
 281  
      * @param dependency the dependency to analyze
 282  
      */
 283  
     private void removeBadMatches(Dependency dependency) {
 284  4
         final Set<Identifier> identifiers = dependency.getIdentifiers();
 285  4
         final Iterator<Identifier> itr = identifiers.iterator();
 286  
 
 287  
         /* TODO - can we utilize the pom's groupid and artifactId to filter??? most of
 288  
          * these are due to low quality data.  Other idea would be to say any CPE
 289  
          * found based on LOW confidence evidence should have a different CPE type? (this
 290  
          * might be a better solution then just removing the URL for "best-guess" matches).
 291  
          */
 292  
         //Set<Evidence> groupId = dependency.getVendorEvidence().getEvidence("pom", "groupid");
 293  
         //Set<Evidence> artifactId = dependency.getVendorEvidence().getEvidence("pom", "artifactid");
 294  9
         while (itr.hasNext()) {
 295  5
             final Identifier i = itr.next();
 296  
             //TODO move this startsWith expression to the base suppression file
 297  5
             if ("cpe".equals(i.getType())) {
 298  5
                 if ((i.getValue().matches(".*c\\+\\+.*")
 299  5
                         || i.getValue().startsWith("cpe:/a:file:file")
 300  4
                         || i.getValue().startsWith("cpe:/a:mozilla:mozilla")
 301  4
                         || i.getValue().startsWith("cpe:/a:cvs:cvs")
 302  4
                         || i.getValue().startsWith("cpe:/a:ftp:ftp")
 303  4
                         || i.getValue().startsWith("cpe:/a:tcp:tcp")
 304  4
                         || i.getValue().startsWith("cpe:/a:ssh:ssh")
 305  4
                         || i.getValue().startsWith("cpe:/a:lookup:lookup"))
 306  1
                         && (dependency.getFileName().toLowerCase().endsWith(".jar")
 307  1
                         || dependency.getFileName().toLowerCase().endsWith("pom.xml")
 308  0
                         || dependency.getFileName().toLowerCase().endsWith(".dll")
 309  0
                         || dependency.getFileName().toLowerCase().endsWith(".exe")
 310  0
                         || dependency.getFileName().toLowerCase().endsWith(".nuspec")
 311  0
                         || dependency.getFileName().toLowerCase().endsWith(".zip")
 312  0
                         || dependency.getFileName().toLowerCase().endsWith(".sar")
 313  0
                         || dependency.getFileName().toLowerCase().endsWith(".apk")
 314  0
                         || dependency.getFileName().toLowerCase().endsWith(".tar")
 315  0
                         || dependency.getFileName().toLowerCase().endsWith(".gz")
 316  0
                         || dependency.getFileName().toLowerCase().endsWith(".tgz")
 317  0
                         || dependency.getFileName().toLowerCase().endsWith(".ear")
 318  0
                         || dependency.getFileName().toLowerCase().endsWith(".war"))) {
 319  1
                     itr.remove();
 320  4
                 } else if ((i.getValue().startsWith("cpe:/a:jquery:jquery")
 321  4
                         || i.getValue().startsWith("cpe:/a:prototypejs:prototype")
 322  4
                         || i.getValue().startsWith("cpe:/a:yahoo:yui"))
 323  0
                         && (dependency.getFileName().toLowerCase().endsWith(".jar")
 324  0
                         || dependency.getFileName().toLowerCase().endsWith("pom.xml")
 325  0
                         || dependency.getFileName().toLowerCase().endsWith(".dll")
 326  0
                         || dependency.getFileName().toLowerCase().endsWith(".exe"))) {
 327  0
                     itr.remove();
 328  4
                 } else if ((i.getValue().startsWith("cpe:/a:microsoft:excel")
 329  4
                         || i.getValue().startsWith("cpe:/a:microsoft:word")
 330  4
                         || i.getValue().startsWith("cpe:/a:microsoft:visio")
 331  4
                         || i.getValue().startsWith("cpe:/a:microsoft:powerpoint")
 332  4
                         || i.getValue().startsWith("cpe:/a:microsoft:office")
 333  4
                         || i.getValue().startsWith("cpe:/a:core_ftp:core_ftp"))
 334  0
                         && (dependency.getFileName().toLowerCase().endsWith(".jar")
 335  0
                         || dependency.getFileName().toLowerCase().endsWith(".ear")
 336  0
                         || dependency.getFileName().toLowerCase().endsWith(".war")
 337  0
                         || dependency.getFileName().toLowerCase().endsWith("pom.xml"))) {
 338  0
                     itr.remove();
 339  4
                 } else if (i.getValue().startsWith("cpe:/a:apache:maven")
 340  0
                         && !dependency.getFileName().toLowerCase().matches("maven-core-[\\d\\.]+\\.jar")) {
 341  0
                     itr.remove();
 342  4
                 } else if (i.getValue().startsWith("cpe:/a:m-core:m-core")
 343  0
                         && !dependency.getEvidenceUsed().containsUsedString("m-core")) {
 344  0
                     itr.remove();
 345  4
                 } else if (i.getValue().startsWith("cpe:/a:jboss:jboss")
 346  0
                         && !dependency.getFileName().toLowerCase().matches("jboss-?[\\d\\.-]+(GA)?\\.jar")) {
 347  0
                     itr.remove();
 348  
                 }
 349  
             }
 350  5
         }
 351  4
     }
 352  
 
 353  
     /**
 354  
      * Removes CPE matches for the wrong version of a dependency. Currently, this only covers Axis 1 & 2.
 355  
      *
 356  
      * @param dependency the dependency to analyze
 357  
      */
 358  
     private void removeWrongVersionMatches(Dependency dependency) {
 359  4
         final Set<Identifier> identifiers = dependency.getIdentifiers();
 360  5
         final Iterator<Identifier> itr = identifiers.iterator();
 361  
 
 362  5
         final String fileName = dependency.getFileName();
 363  4
         if (fileName != null && fileName.contains("axis2")) {
 364  0
             while (itr.hasNext()) {
 365  0
                 final Identifier i = itr.next();
 366  0
                 if ("cpe".equals(i.getType())) {
 367  0
                     final String cpe = i.getValue();
 368  0
                     if (cpe != null && (cpe.startsWith("cpe:/a:apache:axis:") || "cpe:/a:apache:axis".equals(cpe))) {
 369  0
                         itr.remove();
 370  
                     }
 371  
                 }
 372  0
             }
 373  4
         } else if (fileName != null && fileName.contains("axis")) {
 374  0
             while (itr.hasNext()) {
 375  0
                 final Identifier i = itr.next();
 376  0
                 if ("cpe".equals(i.getType())) {
 377  0
                     final String cpe = i.getValue();
 378  0
                     if (cpe != null && (cpe.startsWith("cpe:/a:apache:axis2:") || "cpe:/a:apache:axis2".equals(cpe))) {
 379  0
                         itr.remove();
 380  
                     }
 381  
                 }
 382  0
             }
 383  
         }
 384  4
     }
 385  
 
 386  
     /**
 387  
      * There are some known CPE entries, specifically regarding sun and oracle products due to the acquisition and changes in
 388  
      * product names, that based on given evidence we can add the related CPE entries to ensure a complete list of CVE entries.
 389  
      *
 390  
      * @param dependency the dependency being analyzed
 391  
      */
 392  
     private void addFalseNegativeCPEs(Dependency dependency) {
 393  
         //TODO move this to the hint analyzer
 394  4
         for (final Identifier identifier : dependency.getIdentifiers()) {
 395  4
             if ("cpe".equals(identifier.getType()) && identifier.getValue() != null
 396  4
                     && (identifier.getValue().startsWith("cpe:/a:oracle:opensso:")
 397  4
                     || identifier.getValue().startsWith("cpe:/a:oracle:opensso_enterprise:")
 398  4
                     || identifier.getValue().startsWith("cpe:/a:sun:opensso_enterprise:")
 399  4
                     || identifier.getValue().startsWith("cpe:/a:sun:opensso:"))) {
 400  0
                 final String newCpe = String.format("cpe:/a:sun:opensso_enterprise:%s", identifier.getValue().substring(22));
 401  0
                 final String newCpe2 = String.format("cpe:/a:oracle:opensso_enterprise:%s", identifier.getValue().substring(22));
 402  0
                 final String newCpe3 = String.format("cpe:/a:sun:opensso:%s", identifier.getValue().substring(22));
 403  0
                 final String newCpe4 = String.format("cpe:/a:oracle:opensso:%s", identifier.getValue().substring(22));
 404  
                 try {
 405  0
                     dependency.addIdentifier("cpe",
 406  
                             newCpe,
 407  0
                             String.format(CPEAnalyzer.NVD_SEARCH_URL, URLEncoder.encode(newCpe, "UTF-8")));
 408  0
                     dependency.addIdentifier("cpe",
 409  
                             newCpe2,
 410  0
                             String.format(CPEAnalyzer.NVD_SEARCH_URL, URLEncoder.encode(newCpe2, "UTF-8")));
 411  0
                     dependency.addIdentifier("cpe",
 412  
                             newCpe3,
 413  0
                             String.format(CPEAnalyzer.NVD_SEARCH_URL, URLEncoder.encode(newCpe3, "UTF-8")));
 414  0
                     dependency.addIdentifier("cpe",
 415  
                             newCpe4,
 416  0
                             String.format(CPEAnalyzer.NVD_SEARCH_URL, URLEncoder.encode(newCpe4, "UTF-8")));
 417  0
                 } catch (UnsupportedEncodingException ex) {
 418  0
                     LOGGER.debug("", ex);
 419  0
                 }
 420  
             }
 421  4
         }
 422  4
     }
 423  
 
 424  
     /**
 425  
      * Removes duplicate entries identified that are contained within JAR files. These occasionally crop up due to POM entries or
 426  
      * other types of files (such as DLLs and EXEs) being contained within the JAR.
 427  
      *
 428  
      * @param dependency the dependency that might be a duplicate
 429  
      * @param engine the engine used to scan all dependencies
 430  
      */
 431  
     private void removeDuplicativeEntriesFromJar(Dependency dependency, Engine engine) {
 432  4
         if (dependency.getFileName().toLowerCase().endsWith("pom.xml")
 433  4
                 || DLL_EXE_FILTER.accept(dependency.getActualFile())) {
 434  1
             String parentPath = dependency.getFilePath().toLowerCase();
 435  1
             if (parentPath.contains(".jar")) {
 436  0
                 parentPath = parentPath.substring(0, parentPath.indexOf(".jar") + 4);
 437  0
                 final List<Dependency> dependencies = engine.getDependencies();
 438  0
                 synchronized (dependencies) {
 439  0
                     final Dependency parent = findDependency(parentPath, dependencies);
 440  0
                     if (parent != null) {
 441  0
                         boolean remove = false;
 442  0
                         for (Identifier i : dependency.getIdentifiers()) {
 443  0
                             if ("cpe".equals(i.getType())) {
 444  0
                                 final String trimmedCPE = trimCpeToVendor(i.getValue());
 445  0
                                 for (Identifier parentId : parent.getIdentifiers()) {
 446  0
                                     if ("cpe".equals(parentId.getType()) && parentId.getValue().startsWith(trimmedCPE)) {
 447  0
                                         remove |= true;
 448  
                                     }
 449  0
                                 }
 450  
                             }
 451  0
                             if (!remove) { //we can escape early
 452  0
                                 return;
 453  
                             }
 454  0
                         }
 455  0
                         if (remove) {
 456  0
                             dependencies.remove(dependency);
 457  
                         }
 458  
                     }
 459  0
                 }
 460  
             }
 461  
         }
 462  4
     }
 463  
 
 464  
     /**
 465  
      * Retrieves a given dependency, based on a given path, from a list of dependencies.
 466  
      *
 467  
      * @param dependencyPath the path of the dependency to return
 468  
      * @param dependencies the collection of dependencies to search
 469  
      * @return the dependency object for the given path, otherwise null
 470  
      */
 471  
     private Dependency findDependency(String dependencyPath, List<Dependency> dependencies) {
 472  0
         for (Dependency d : dependencies) {
 473  0
             if (d.getFilePath().equalsIgnoreCase(dependencyPath)) {
 474  0
                 return d;
 475  
             }
 476  0
         }
 477  0
         return null;
 478  
     }
 479  
 
 480  
     /**
 481  
      * Takes a full CPE and returns the CPE trimmed to include only vendor and product.
 482  
      *
 483  
      * @param value the CPE value to trim
 484  
      * @return a CPE value that only includes the vendor and product
 485  
      */
 486  
     private String trimCpeToVendor(String value) {
 487  
         //cpe:/a:jruby:jruby:1.0.8
 488  0
         final int pos1 = value.indexOf(':', 7); //right of vendor
 489  0
         final int pos2 = value.indexOf(':', pos1 + 1); //right of product
 490  0
         if (pos2 < 0) {
 491  0
             return value;
 492  
         } else {
 493  0
             return value.substring(0, pos2);
 494  
         }
 495  
     }
 496  
 }