Coverage Report - org.owasp.dependencycheck.analyzer.JarAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
JarAnalyzer
68%
365/532
55%
199/360
8.207
JarAnalyzer$ClassNameInformation
80%
17/21
80%
8/10
8.207
 
 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.File;
 21  
 import java.io.FileFilter;
 22  
 import java.io.FileOutputStream;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.io.InputStreamReader;
 26  
 import java.io.Reader;
 27  
 import java.io.UnsupportedEncodingException;
 28  
 import java.util.ArrayList;
 29  
 import java.util.Enumeration;
 30  
 import java.util.HashMap;
 31  
 import java.util.List;
 32  
 import java.util.Map;
 33  
 import java.util.Map.Entry;
 34  
 import java.util.Properties;
 35  
 import java.util.Set;
 36  
 import java.util.StringTokenizer;
 37  
 import java.util.concurrent.atomic.AtomicInteger;
 38  
 import java.util.jar.Attributes;
 39  
 import java.util.jar.JarEntry;
 40  
 import java.util.jar.JarFile;
 41  
 import java.util.jar.Manifest;
 42  
 import java.util.regex.Pattern;
 43  
 import java.util.zip.ZipEntry;
 44  
 import org.apache.commons.compress.utils.IOUtils;
 45  
 import org.apache.commons.io.FilenameUtils;
 46  
 import org.jsoup.Jsoup;
 47  
 import org.owasp.dependencycheck.Engine;
 48  
 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
 49  
 import org.owasp.dependencycheck.dependency.Confidence;
 50  
 import org.owasp.dependencycheck.dependency.Dependency;
 51  
 import org.owasp.dependencycheck.dependency.EvidenceCollection;
 52  
 import org.owasp.dependencycheck.exception.InitializationException;
 53  
 import org.owasp.dependencycheck.utils.FileFilterBuilder;
 54  
 import org.owasp.dependencycheck.xml.pom.License;
 55  
 import org.owasp.dependencycheck.xml.pom.PomUtils;
 56  
 import org.owasp.dependencycheck.xml.pom.Model;
 57  
 import org.owasp.dependencycheck.utils.FileUtils;
 58  
 import org.owasp.dependencycheck.utils.Settings;
 59  
 import org.slf4j.Logger;
 60  
 import org.slf4j.LoggerFactory;
 61  
 
 62  
 /**
 63  
  * Used to load a JAR file and collect information that can be used to determine
 64  
  * the associated CPE.
 65  
  *
 66  
  * @author Jeremy Long
 67  
  */
 68  
 public class JarAnalyzer extends AbstractFileTypeAnalyzer {
 69  
 
 70  
     //<editor-fold defaultstate="collapsed" desc="Constants and Member Variables">
 71  
     /**
 72  
      * The logger.
 73  
      */
 74  1
     private static final Logger LOGGER = LoggerFactory.getLogger(JarAnalyzer.class);
 75  
     /**
 76  
      * The count of directories created during analysis. This is used for
 77  
      * creating temporary directories.
 78  
      */
 79  1
     private static final AtomicInteger DIR_COUNT = new AtomicInteger(0);
 80  
     /**
 81  
      * The system independent newline character.
 82  
      */
 83  1
     private static final String NEWLINE = System.getProperty("line.separator");
 84  
     /**
 85  
      * A list of values in the manifest to ignore as they only result in false
 86  
      * positives.
 87  
      */
 88  1
     private static final Set<String> IGNORE_VALUES = newHashSet(
 89  
             "Sun Java System Application Server");
 90  
     /**
 91  
      * A list of elements in the manifest to ignore.
 92  
      */
 93  1
     private static final Set<String> IGNORE_KEYS = newHashSet(
 94  
             "built-by",
 95  
             "created-by",
 96  
             "builtby",
 97  
             "createdby",
 98  
             "build-jdk",
 99  
             "buildjdk",
 100  
             "ant-version",
 101  
             "antversion",
 102  
             "dynamicimportpackage",
 103  
             "dynamicimport-package",
 104  
             "dynamic-importpackage",
 105  
             "dynamic-import-package",
 106  
             "import-package",
 107  
             "ignore-package",
 108  
             "export-package",
 109  
             "importpackage",
 110  
             "ignorepackage",
 111  
             "exportpackage",
 112  
             "sealed",
 113  
             "manifest-version",
 114  
             "archiver-version",
 115  
             "manifestversion",
 116  
             "archiverversion",
 117  
             "classpath",
 118  
             "class-path",
 119  
             "tool",
 120  
             "bundle-manifestversion",
 121  
             "bundlemanifestversion",
 122  
             "bundle-vendor",
 123  
             "include-resource",
 124  
             "embed-dependency",
 125  
             "ipojo-components",
 126  
             "ipojo-extension",
 127  
             "eclipse-sourcereferences");
 128  
     /**
 129  
      * Deprecated Jar manifest attribute, that is, nonetheless, useful for
 130  
      * analysis.
 131  
      */
 132  
     @SuppressWarnings("deprecation")
 133  1
     private static final String IMPLEMENTATION_VENDOR_ID = Attributes.Name.IMPLEMENTATION_VENDOR_ID
 134  1
             .toString();
 135  
     /**
 136  
      * item in some manifest, should be considered medium confidence.
 137  
      */
 138  
     private static final String BUNDLE_VERSION = "Bundle-Version"; //: 2.1.2
 139  
     /**
 140  
      * item in some manifest, should be considered medium confidence.
 141  
      */
 142  
     private static final String BUNDLE_DESCRIPTION = "Bundle-Description"; //: Apache Struts 2
 143  
     /**
 144  
      * item in some manifest, should be considered medium confidence.
 145  
      */
 146  
     private static final String BUNDLE_NAME = "Bundle-Name"; //: Struts 2 Core
 147  
     /**
 148  
      * A pattern to detect HTML within text.
 149  
      */
 150  1
     private static final Pattern HTML_DETECTION_PATTERN = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE);
 151  
 
 152  
     //</editor-fold>
 153  
     /**
 154  
      * Constructs a new JarAnalyzer.
 155  
      */
 156  13
     public JarAnalyzer() {
 157  13
     }
 158  
 
 159  
     //<editor-fold defaultstate="collapsed" desc="All standard implmentation details of Analyzer">
 160  
     /**
 161  
      * The name of the analyzer.
 162  
      */
 163  
     private static final String ANALYZER_NAME = "Jar Analyzer";
 164  
     /**
 165  
      * The phase that this analyzer is intended to run in.
 166  
      */
 167  1
     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
 168  
     /**
 169  
      * The set of file extensions supported by this analyzer.
 170  
      */
 171  1
     private static final String[] EXTENSIONS = {"jar", "war"};
 172  
 
 173  
     /**
 174  
      * The file filter used to determine which files this analyzer supports.
 175  
      */
 176  1
     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build();
 177  
 
 178  
     /**
 179  
      * Returns the FileFilter.
 180  
      *
 181  
      * @return the FileFilter
 182  
      */
 183  
     @Override
 184  
     protected FileFilter getFileFilter() {
 185  865
         return FILTER;
 186  
     }
 187  
 
 188  
     /**
 189  
      * Returns the name of the analyzer.
 190  
      *
 191  
      * @return the name of the analyzer.
 192  
      */
 193  
     @Override
 194  
     public String getName() {
 195  23
         return ANALYZER_NAME;
 196  
     }
 197  
 
 198  
     /**
 199  
      * Returns the phase that the analyzer is intended to run in.
 200  
      *
 201  
      * @return the phase that the analyzer is intended to run in.
 202  
      */
 203  
     @Override
 204  
     public AnalysisPhase getAnalysisPhase() {
 205  6
         return ANALYSIS_PHASE;
 206  
     }
 207  
     //</editor-fold>
 208  
 
 209  
     /**
 210  
      * Returns the key used in the properties file to reference the analyzer's
 211  
      * enabled property.
 212  
      *
 213  
      * @return the analyzer's enabled property setting key
 214  
      */
 215  
     @Override
 216  
     protected String getAnalyzerEnabledSettingKey() {
 217  3
         return Settings.KEYS.ANALYZER_JAR_ENABLED;
 218  
     }
 219  
 
 220  
     /**
 221  
      * Loads a specified JAR file and collects information from the manifest and
 222  
      * checksums to identify the correct CPE information.
 223  
      *
 224  
      * @param dependency the dependency to analyze.
 225  
      * @param engine the engine that is scanning the dependencies
 226  
      * @throws AnalysisException is thrown if there is an error reading the JAR
 227  
      * file.
 228  
      */
 229  
     @Override
 230  
     public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
 231  
         try {
 232  6
             final List<ClassNameInformation> classNames = collectClassNames(dependency);
 233  6
             final String fileName = dependency.getFileName().toLowerCase();
 234  6
             if (classNames.isEmpty()
 235  1
                     && (fileName.endsWith("-sources.jar")
 236  1
                     || fileName.endsWith("-javadoc.jar")
 237  1
                     || fileName.endsWith("-src.jar")
 238  1
                     || fileName.endsWith("-doc.jar"))) {
 239  0
                 engine.getDependencies().remove(dependency);
 240  
             }
 241  6
             final boolean hasManifest = parseManifest(dependency, classNames);
 242  6
             final boolean hasPOM = analyzePOM(dependency, classNames, engine);
 243  6
             final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
 244  6
             analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
 245  0
         } catch (IOException ex) {
 246  0
             throw new AnalysisException("Exception occurred reading the JAR file (" + dependency.getFileName() + ").", ex);
 247  6
         }
 248  6
     }
 249  
 
 250  
     /**
 251  
      * Attempts to find a pom.xml within the JAR file. If found it extracts
 252  
      * information and adds it to the evidence. This will attempt to interpolate
 253  
      * the strings contained within the pom.properties if one exists.
 254  
      *
 255  
      * @param dependency the dependency being analyzed
 256  
      * @param classes a collection of class name information
 257  
      * @param engine the analysis engine, used to add additional dependencies
 258  
      * @throws AnalysisException is thrown if there is an exception parsing the
 259  
      * pom
 260  
      * @return whether or not evidence was added to the dependency
 261  
      */
 262  
     protected boolean analyzePOM(Dependency dependency, List<ClassNameInformation> classes, Engine engine) throws AnalysisException {
 263  6
         JarFile jar = null;
 264  6
         List<String> pomEntries = null;
 265  
         try {
 266  6
             jar = new JarFile(dependency.getActualFilePath());
 267  6
             pomEntries = retrievePomListing(jar);
 268  0
         } catch (IOException ex) {
 269  0
             LOGGER.warn("Unable to read JarFile '{}'.", dependency.getActualFilePath());
 270  0
             LOGGER.trace("", ex);
 271  0
             if (jar != null) {
 272  
                 try {
 273  0
                     jar.close();
 274  0
                 } catch (IOException ex1) {
 275  0
                     LOGGER.trace("", ex1);
 276  0
                 }
 277  
             }
 278  0
             return false;
 279  6
         }
 280  6
         if (pomEntries != null && pomEntries.size() <= 1) {
 281  
             try {
 282  6
                 String path = null;
 283  6
                 Properties pomProperties = null;
 284  6
                 File pomFile = null;
 285  6
                 if (pomEntries.size() == 1) {
 286  2
                     path = pomEntries.get(0);
 287  2
                     pomFile = extractPom(path, jar);
 288  2
                     pomProperties = retrievePomProperties(path, jar);
 289  
                 } else {
 290  4
                     path = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
 291  4
                     pomFile = new File(path);
 292  
                 }
 293  6
                 if (pomFile.isFile()) {
 294  2
                     final Model pom = PomUtils.readPom(pomFile);
 295  2
                     if (pom != null && pomProperties != null) {
 296  0
                         pom.processProperties(pomProperties);
 297  
                     }
 298  2
                     if (pom != null) {
 299  4
                         return setPomEvidence(dependency, pom, classes);
 300  
                     }
 301  0
                     return false;
 302  
                 } else {
 303  8
                     return false;
 304  
                 }
 305  
             } finally {
 306  0
                 try {
 307  6
                     jar.close();
 308  0
                 } catch (IOException ex) {
 309  0
                     LOGGER.trace("", ex);
 310  6
                 }
 311  
             }
 312  
         }
 313  
 
 314  
         //reported possible null dereference on pomEntries is on a non-feasible path
 315  0
         for (String path : pomEntries) {
 316  
             //TODO - one of these is likely the pom for the main JAR we are analyzing
 317  0
             LOGGER.debug("Reading pom entry: {}", path);
 318  
             try {
 319  
                 //extract POM to its own directory and add it as its own dependency
 320  0
                 final Properties pomProperties = retrievePomProperties(path, jar);
 321  0
                 final File pomFile = extractPom(path, jar);
 322  0
                 final Model pom = PomUtils.readPom(pomFile);
 323  0
                 pom.processProperties(pomProperties);
 324  
 
 325  0
                 final String displayPath = String.format("%s%s%s",
 326  0
                         dependency.getFilePath(),
 327  
                         File.separator,
 328  
                         path);
 329  0
                 final String displayName = String.format("%s%s%s",
 330  0
                         dependency.getFileName(),
 331  
                         File.separator,
 332  
                         path);
 333  0
                 final Dependency newDependency = new Dependency();
 334  0
                 newDependency.setActualFilePath(pomFile.getAbsolutePath());
 335  0
                 newDependency.setFileName(displayName);
 336  0
                 newDependency.setFilePath(displayPath);
 337  0
                 setPomEvidence(newDependency, pom, null);
 338  0
                 engine.getDependencies().add(newDependency);
 339  0
             } catch (AnalysisException ex) {
 340  0
                 LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath());
 341  0
                 LOGGER.trace("", ex);
 342  0
             }
 343  0
         }
 344  
         try {
 345  0
             jar.close();
 346  0
         } catch (IOException ex) {
 347  0
             LOGGER.trace("", ex);
 348  0
         }
 349  0
         return false;
 350  
     }
 351  
 
 352  
     /**
 353  
      * Given a path to a pom.xml within a JarFile, this method attempts to load
 354  
      * a sibling pom.properties if one exists.
 355  
      *
 356  
      * @param path the path to the pom.xml within the JarFile
 357  
      * @param jar the JarFile to load the pom.properties from
 358  
      * @return a Properties object or null if no pom.properties was found
 359  
      * @throws IOException thrown if there is an exception reading the
 360  
      * pom.properties
 361  
      */
 362  
     private Properties retrievePomProperties(String path, final JarFile jar) {
 363  2
         Properties pomProperties = null;
 364  2
         final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
 365  2
         final ZipEntry propEntry = jar.getEntry(propPath);
 366  2
         if (propEntry != null) {
 367  0
             Reader reader = null;
 368  
             try {
 369  0
                 reader = new InputStreamReader(jar.getInputStream(propEntry), "UTF-8");
 370  0
                 pomProperties = new Properties();
 371  0
                 pomProperties.load(reader);
 372  0
                 LOGGER.debug("Read pom.properties: {}", propPath);
 373  0
             } catch (UnsupportedEncodingException ex) {
 374  0
                 LOGGER.trace("UTF-8 is not supported", ex);
 375  0
             } catch (IOException ex) {
 376  0
                 LOGGER.trace("Unable to read the POM properties", ex);
 377  
             } finally {
 378  0
                 if (reader != null) {
 379  
                     try {
 380  0
                         reader.close();
 381  0
                     } catch (IOException ex) {
 382  0
                         LOGGER.trace("close error", ex);
 383  0
                     }
 384  
                 }
 385  
             }
 386  
         }
 387  2
         return pomProperties;
 388  
     }
 389  
 
 390  
     /**
 391  
      * Searches a JarFile for pom.xml entries and returns a listing of these
 392  
      * entries.
 393  
      *
 394  
      * @param jar the JarFile to search
 395  
      * @return a list of pom.xml entries
 396  
      * @throws IOException thrown if there is an exception reading a JarEntry
 397  
      */
 398  
     private List<String> retrievePomListing(final JarFile jar) throws IOException {
 399  6
         final List<String> pomEntries = new ArrayList<String>();
 400  6
         final Enumeration<JarEntry> entries = jar.entries();
 401  1852
         while (entries.hasMoreElements()) {
 402  1846
             final JarEntry entry = entries.nextElement();
 403  1846
             final String entryName = (new File(entry.getName())).getName().toLowerCase();
 404  1846
             if (!entry.isDirectory() && "pom.xml".equals(entryName)) {
 405  2
                 LOGGER.trace("POM Entry found: {}", entry.getName());
 406  2
                 pomEntries.add(entry.getName());
 407  
             }
 408  1846
         }
 409  6
         return pomEntries;
 410  
     }
 411  
 
 412  
     /**
 413  
      * Retrieves the specified POM from a jar.
 414  
      *
 415  
      * @param path the path to the pom.xml file within the jar file
 416  
      * @param jar the jar file to extract the pom from
 417  
      * @return returns the POM file
 418  
      * @throws AnalysisException is thrown if there is an exception extracting
 419  
      * the file
 420  
      */
 421  
     private File extractPom(String path, JarFile jar) throws AnalysisException {
 422  2
         InputStream input = null;
 423  2
         FileOutputStream fos = null;
 424  2
         final File tmpDir = getNextTempDirectory();
 425  2
         final File file = new File(tmpDir, "pom.xml");
 426  
         try {
 427  2
             final ZipEntry entry = jar.getEntry(path);
 428  2
             if (entry == null) {
 429  0
                 throw new AnalysisException(String.format("Pom (%s)does not exist in %s", path, jar.getName()));
 430  
             }
 431  2
             input = jar.getInputStream(entry);
 432  2
             fos = new FileOutputStream(file);
 433  2
             IOUtils.copy(input, fos);
 434  0
         } catch (IOException ex) {
 435  0
             LOGGER.warn("An error occurred reading '{}' from '{}'.", path, jar.getName());
 436  0
             LOGGER.error("", ex);
 437  
         } finally {
 438  2
             FileUtils.close(fos);
 439  2
             FileUtils.close(input);
 440  2
         }
 441  2
         return file;
 442  
     }
 443  
 
 444  
     /**
 445  
      * Sets evidence from the pom on the supplied dependency.
 446  
      *
 447  
      * @param dependency the dependency to set data on
 448  
      * @param pom the information from the pom
 449  
      * @param classes a collection of ClassNameInformation - containing data
 450  
      * about the fully qualified class names within the JAR file being analyzed
 451  
      * @return true if there was evidence within the pom that we could use;
 452  
      * otherwise false
 453  
      */
 454  
     public static boolean setPomEvidence(Dependency dependency, Model pom, List<ClassNameInformation> classes) {
 455  2
         boolean foundSomething = false;
 456  2
         boolean addAsIdentifier = true;
 457  2
         if (pom == null) {
 458  0
             return foundSomething;
 459  
         }
 460  2
         String groupid = pom.getGroupId();
 461  2
         String parentGroupId = pom.getParentGroupId();
 462  2
         String artifactid = pom.getArtifactId();
 463  2
         String parentArtifactId = pom.getParentArtifactId();
 464  2
         String version = pom.getVersion();
 465  2
         String parentVersion = pom.getParentVersion();
 466  
 
 467  2
         if ("org.sonatype.oss".equals(parentGroupId) && "oss-parent".equals(parentArtifactId)) {
 468  0
             parentGroupId = null;
 469  0
             parentArtifactId = null;
 470  0
             parentVersion = null;
 471  
         }
 472  
 
 473  2
         if ((groupid == null || groupid.isEmpty()) && parentGroupId != null && !parentGroupId.isEmpty()) {
 474  0
             groupid = parentGroupId;
 475  
         }
 476  
 
 477  2
         final String originalGroupID = groupid;
 478  2
         if (groupid != null && (groupid.startsWith("org.") || groupid.startsWith("com."))) {
 479  1
             groupid = groupid.substring(4);
 480  
         }
 481  
 
 482  2
         if ((artifactid == null || artifactid.isEmpty()) && parentArtifactId != null && !parentArtifactId.isEmpty()) {
 483  0
             artifactid = parentArtifactId;
 484  
         }
 485  
 
 486  2
         final String originalArtifactID = artifactid;
 487  2
         if (artifactid != null && (artifactid.startsWith("org.") || artifactid.startsWith("com."))) {
 488  0
             artifactid = artifactid.substring(4);
 489  
         }
 490  
 
 491  2
         if ((version == null || version.isEmpty()) && parentVersion != null && !parentVersion.isEmpty()) {
 492  1
             version = parentVersion;
 493  
         }
 494  
 
 495  2
         if (groupid != null && !groupid.isEmpty()) {
 496  2
             foundSomething = true;
 497  2
             dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGHEST);
 498  2
             dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Confidence.LOW);
 499  2
             addMatchingValues(classes, groupid, dependency.getVendorEvidence());
 500  2
             addMatchingValues(classes, groupid, dependency.getProductEvidence());
 501  2
             if (parentGroupId != null && !parentGroupId.isEmpty() && !parentGroupId.equals(groupid)) {
 502  1
                 dependency.getVendorEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.MEDIUM);
 503  1
                 dependency.getProductEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.LOW);
 504  1
                 addMatchingValues(classes, parentGroupId, dependency.getVendorEvidence());
 505  1
                 addMatchingValues(classes, parentGroupId, dependency.getProductEvidence());
 506  
             }
 507  
         } else {
 508  0
             addAsIdentifier = false;
 509  
         }
 510  
 
 511  2
         if (artifactid != null && !artifactid.isEmpty()) {
 512  2
             foundSomething = true;
 513  2
             dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.HIGHEST);
 514  2
             dependency.getVendorEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.LOW);
 515  2
             addMatchingValues(classes, artifactid, dependency.getVendorEvidence());
 516  2
             addMatchingValues(classes, artifactid, dependency.getProductEvidence());
 517  2
             if (parentArtifactId != null && !parentArtifactId.isEmpty() && !parentArtifactId.equals(artifactid)) {
 518  1
                 dependency.getProductEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.MEDIUM);
 519  1
                 dependency.getVendorEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.LOW);
 520  1
                 addMatchingValues(classes, parentArtifactId, dependency.getVendorEvidence());
 521  1
                 addMatchingValues(classes, parentArtifactId, dependency.getProductEvidence());
 522  
             }
 523  
         } else {
 524  0
             addAsIdentifier = false;
 525  
         }
 526  
 
 527  2
         if (version != null && !version.isEmpty()) {
 528  2
             foundSomething = true;
 529  2
             dependency.getVersionEvidence().addEvidence("pom", "version", version, Confidence.HIGHEST);
 530  2
             if (parentVersion != null && !parentVersion.isEmpty() && !parentVersion.equals(version)) {
 531  0
                 dependency.getVersionEvidence().addEvidence("pom", "parent-version", version, Confidence.LOW);
 532  
             }
 533  
         } else {
 534  0
             addAsIdentifier = false;
 535  
         }
 536  
 
 537  2
         if (addAsIdentifier) {
 538  2
             dependency.addIdentifier("maven", String.format("%s:%s:%s", originalGroupID, originalArtifactID, version), null, Confidence.HIGH);
 539  
         }
 540  
 
 541  
         // org name
 542  2
         final String org = pom.getOrganization();
 543  2
         if (org != null && !org.isEmpty()) {
 544  0
             dependency.getVendorEvidence().addEvidence("pom", "organization name", org, Confidence.HIGH);
 545  0
             dependency.getProductEvidence().addEvidence("pom", "organization name", org, Confidence.LOW);
 546  0
             addMatchingValues(classes, org, dependency.getVendorEvidence());
 547  0
             addMatchingValues(classes, org, dependency.getProductEvidence());
 548  
         }
 549  
         //pom name
 550  2
         final String pomName = pom.getName();
 551  2
         if (pomName
 552  2
                 != null && !pomName.isEmpty()) {
 553  2
             foundSomething = true;
 554  2
             dependency.getProductEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH);
 555  2
             dependency.getVendorEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH);
 556  2
             addMatchingValues(classes, pomName, dependency.getVendorEvidence());
 557  2
             addMatchingValues(classes, pomName, dependency.getProductEvidence());
 558  
         }
 559  
 
 560  
         //Description
 561  2
         final String description = pom.getDescription();
 562  2
         if (description != null && !description.isEmpty() && !description.startsWith("POM was created by")) {
 563  1
             foundSomething = true;
 564  1
             final String trimmedDescription = addDescription(dependency, description, "pom", "description");
 565  1
             addMatchingValues(classes, trimmedDescription, dependency.getVendorEvidence());
 566  1
             addMatchingValues(classes, trimmedDescription, dependency.getProductEvidence());
 567  
         }
 568  
 
 569  2
         final String projectURL = pom.getProjectURL();
 570  2
         if (projectURL != null && !projectURL.trim().isEmpty()) {
 571  1
             dependency.getVendorEvidence().addEvidence("pom", "url", projectURL, Confidence.HIGHEST);
 572  
         }
 573  
 
 574  2
         extractLicense(pom, dependency);
 575  2
         return foundSomething;
 576  
     }
 577  
 
 578  
     /**
 579  
      * Analyzes the path information of the classes contained within the
 580  
      * JarAnalyzer to try and determine possible vendor or product names. If any
 581  
      * are found they are stored in the packageVendor and packageProduct
 582  
      * hashSets.
 583  
      *
 584  
      * @param classNames a list of class names
 585  
      * @param dependency a dependency to analyze
 586  
      * @param addPackagesAsEvidence a flag indicating whether or not package
 587  
      * names should be added as evidence.
 588  
      */
 589  
     protected void analyzePackageNames(List<ClassNameInformation> classNames,
 590  
             Dependency dependency, boolean addPackagesAsEvidence) {
 591  6
         final Map<String, Integer> vendorIdentifiers = new HashMap<String, Integer>();
 592  6
         final Map<String, Integer> productIdentifiers = new HashMap<String, Integer>();
 593  6
         analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers);
 594  
 
 595  6
         final int classCount = classNames.size();
 596  6
         final EvidenceCollection vendor = dependency.getVendorEvidence();
 597  6
         final EvidenceCollection product = dependency.getProductEvidence();
 598  
 
 599  6
         for (Map.Entry<String, Integer> entry : vendorIdentifiers.entrySet()) {
 600  48
             final float ratio = entry.getValue() / (float) classCount;
 601  48
             if (ratio > 0.5) {
 602  
                 //TODO remove weighting
 603  10
                 vendor.addWeighting(entry.getKey());
 604  10
                 if (addPackagesAsEvidence && entry.getKey().length() > 1) {
 605  8
                     vendor.addEvidence("jar", "package name", entry.getKey(), Confidence.LOW);
 606  
                 }
 607  
             }
 608  48
         }
 609  6
         for (Map.Entry<String, Integer> entry : productIdentifiers.entrySet()) {
 610  985
             final float ratio = entry.getValue() / (float) classCount;
 611  985
             if (ratio > 0.5) {
 612  5
                 product.addWeighting(entry.getKey());
 613  5
                 if (addPackagesAsEvidence && entry.getKey().length() > 1) {
 614  4
                     product.addEvidence("jar", "package name", entry.getKey(), Confidence.LOW);
 615  
                 }
 616  
             }
 617  985
         }
 618  6
     }
 619  
 
 620  
     /**
 621  
      * <p>
 622  
      * Reads the manifest from the JAR file and collects the entries. Some
 623  
      * vendorKey entries are:</p>
 624  
      * <ul><li>Implementation Title</li>
 625  
      * <li>Implementation Version</li> <li>Implementation Vendor</li>
 626  
      * <li>Implementation VendorId</li> <li>Bundle Name</li> <li>Bundle
 627  
      * Version</li> <li>Bundle Vendor</li> <li>Bundle Description</li> <li>Main
 628  
      * Class</li> </ul>
 629  
      * However, all but a handful of specific entries are read in.
 630  
      *
 631  
      * @param dependency A reference to the dependency
 632  
      * @param classInformation a collection of class information
 633  
      * @return whether evidence was identified parsing the manifest
 634  
      * @throws IOException if there is an issue reading the JAR file
 635  
      */
 636  
     protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation)
 637  
             throws IOException {
 638  7
         boolean foundSomething = false;
 639  7
         JarFile jar = null;
 640  
         try {
 641  7
             jar = new JarFile(dependency.getActualFilePath());
 642  7
             final Manifest manifest = jar.getManifest();
 643  7
             if (manifest == null) {
 644  0
                 if (!dependency.getFileName().toLowerCase().endsWith("-sources.jar")
 645  0
                         && !dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
 646  0
                         && !dependency.getFileName().toLowerCase().endsWith("-src.jar")
 647  0
                         && !dependency.getFileName().toLowerCase().endsWith("-doc.jar")) {
 648  0
                     LOGGER.debug("Jar file '{}' does not contain a manifest.",
 649  0
                             dependency.getFileName());
 650  
                 }
 651  0
                 return false;
 652  
             }
 653  7
             final EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
 654  7
             final EvidenceCollection productEvidence = dependency.getProductEvidence();
 655  7
             final EvidenceCollection versionEvidence = dependency.getVersionEvidence();
 656  7
             String source = "Manifest";
 657  7
             String specificationVersion = null;
 658  7
             boolean hasImplementationVersion = false;
 659  7
             Attributes atts = manifest.getMainAttributes();
 660  7
             for (Entry<Object, Object> entry : atts.entrySet()) {
 661  72
                 String key = entry.getKey().toString();
 662  72
                 String value = atts.getValue(key);
 663  72
                 if (HTML_DETECTION_PATTERN.matcher(value).find()) {
 664  0
                     value = Jsoup.parse(value).text();
 665  
                 }
 666  72
                 if (IGNORE_VALUES.contains(value)) {
 667  0
                     continue;
 668  72
                 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
 669  1
                     foundSomething = true;
 670  1
                     productEvidence.addEvidence(source, key, value, Confidence.HIGH);
 671  1
                     addMatchingValues(classInformation, value, productEvidence);
 672  71
                 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
 673  2
                     hasImplementationVersion = true;
 674  2
                     foundSomething = true;
 675  2
                     versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
 676  69
                 } else if ("specification-version".equalsIgnoreCase(key)) {
 677  1
                     specificationVersion = value;
 678  68
                 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
 679  1
                     foundSomething = true;
 680  1
                     vendorEvidence.addEvidence(source, key, value, Confidence.HIGH);
 681  1
                     addMatchingValues(classInformation, value, vendorEvidence);
 682  67
                 } else if (key.equalsIgnoreCase(IMPLEMENTATION_VENDOR_ID)) {
 683  0
                     foundSomething = true;
 684  0
                     vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 685  0
                     addMatchingValues(classInformation, value, vendorEvidence);
 686  67
                 } else if (key.equalsIgnoreCase(BUNDLE_DESCRIPTION)) {
 687  2
                     foundSomething = true;
 688  2
                     addDescription(dependency, value, "manifest", key);
 689  2
                     addMatchingValues(classInformation, value, productEvidence);
 690  65
                 } else if (key.equalsIgnoreCase(BUNDLE_NAME)) {
 691  3
                     foundSomething = true;
 692  3
                     productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 693  3
                     addMatchingValues(classInformation, value, productEvidence);
 694  
 //                //the following caused false positives.
 695  
 //                } else if (key.equalsIgnoreCase(BUNDLE_VENDOR)) {
 696  62
                 } else if (key.equalsIgnoreCase(BUNDLE_VERSION)) {
 697  3
                     foundSomething = true;
 698  3
                     versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
 699  59
                 } else if (key.equalsIgnoreCase(Attributes.Name.MAIN_CLASS.toString())) {
 700  3
                     continue;
 701  
                     //skipping main class as if this has important information to add it will be added during class name analysis...
 702  
                 } else {
 703  56
                     key = key.toLowerCase();
 704  56
                     if (!IGNORE_KEYS.contains(key)
 705  15
                             && !key.endsWith("jdk")
 706  15
                             && !key.contains("lastmodified")
 707  14
                             && !key.endsWith("package")
 708  14
                             && !key.endsWith("classpath")
 709  14
                             && !key.endsWith("class-path")
 710  14
                             && !key.endsWith("-scm") //todo change this to a regex?
 711  14
                             && !key.startsWith("scm-")
 712  14
                             && !value.trim().startsWith("scm:")
 713  14
                             && !isImportPackage(key, value)
 714  14
                             && !isPackage(key, value)) {
 715  13
                         foundSomething = true;
 716  13
                         if (key.contains("version")) {
 717  0
                             if (!key.contains("specification")) {
 718  0
                                 versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 719  
                             }
 720  13
                         } else if ("build-id".equals(key)) {
 721  0
                             int pos = value.indexOf('(');
 722  0
                             if (pos > 0) {
 723  0
                                 value = value.substring(0, pos - 1);
 724  
                             }
 725  0
                             pos = value.indexOf('[');
 726  0
                             if (pos > 0) {
 727  0
                                 value = value.substring(0, pos - 1);
 728  
                             }
 729  0
                             versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 730  0
                         } else if (key.contains("title")) {
 731  1
                             productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 732  1
                             addMatchingValues(classInformation, value, productEvidence);
 733  12
                         } else if (key.contains("vendor")) {
 734  0
                             if (key.contains("specification")) {
 735  0
                                 vendorEvidence.addEvidence(source, key, value, Confidence.LOW);
 736  
                             } else {
 737  0
                                 vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 738  0
                                 addMatchingValues(classInformation, value, vendorEvidence);
 739  
                             }
 740  12
                         } else if (key.contains("name")) {
 741  3
                             productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 742  3
                             vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 743  3
                             addMatchingValues(classInformation, value, vendorEvidence);
 744  3
                             addMatchingValues(classInformation, value, productEvidence);
 745  9
                         } else if (key.contains("license")) {
 746  2
                             addLicense(dependency, value);
 747  7
                         } else if (key.contains("description")) {
 748  0
                             addDescription(dependency, value, "manifest", key);
 749  
                         } else {
 750  7
                             productEvidence.addEvidence(source, key, value, Confidence.LOW);
 751  7
                             vendorEvidence.addEvidence(source, key, value, Confidence.LOW);
 752  7
                             addMatchingValues(classInformation, value, vendorEvidence);
 753  7
                             addMatchingValues(classInformation, value, productEvidence);
 754  7
                             if (value.matches(".*\\d.*")) {
 755  3
                                 final StringTokenizer tokenizer = new StringTokenizer(value, " ");
 756  15
                                 while (tokenizer.hasMoreElements()) {
 757  12
                                     final String s = tokenizer.nextToken();
 758  12
                                     if (s.matches("^[0-9.]+$")) {
 759  1
                                         versionEvidence.addEvidence(source, key, s, Confidence.LOW);
 760  
                                     }
 761  12
                                 }
 762  
                             }
 763  
                         }
 764  
                     }
 765  
                 }
 766  69
             }
 767  7
             for (Map.Entry<String, Attributes> item : manifest.getEntries().entrySet()) {
 768  8
                 final String name = item.getKey();
 769  8
                 source = "manifest: " + name;
 770  8
                 atts = item.getValue();
 771  8
                 for (Entry<Object, Object> entry : atts.entrySet()) {
 772  38
                     final String key = entry.getKey().toString();
 773  38
                     final String value = atts.getValue(key);
 774  38
                     if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
 775  8
                         foundSomething = true;
 776  8
                         productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 777  8
                         addMatchingValues(classInformation, value, productEvidence);
 778  30
                     } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
 779  5
                         foundSomething = true;
 780  5
                         versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 781  25
                     } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
 782  5
                         foundSomething = true;
 783  5
                         vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 784  5
                         addMatchingValues(classInformation, value, vendorEvidence);
 785  20
                     } else if (key.equalsIgnoreCase(Attributes.Name.SPECIFICATION_TITLE.toString())) {
 786  4
                         foundSomething = true;
 787  4
                         productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 788  4
                         addMatchingValues(classInformation, value, productEvidence);
 789  
                     }
 790  38
                 }
 791  8
             }
 792  7
             if (specificationVersion != null && !hasImplementationVersion) {
 793  0
                 foundSomething = true;
 794  0
                 versionEvidence.addEvidence(source, "specification-version", specificationVersion, Confidence.HIGH);
 795  
             }
 796  
         } finally {
 797  7
             if (jar != null) {
 798  7
                 jar.close();
 799  
             }
 800  
         }
 801  7
         return foundSomething;
 802  
     }
 803  
 
 804  
     /**
 805  
      * Adds a description to the given dependency. If the description contains
 806  
      * one of the following strings beyond 100 characters, then the description
 807  
      * used will be trimmed to that position:
 808  
      * <ul><li>"such as"</li><li>"like "</li><li>"will use "</li><li>"* uses
 809  
      * "</li></ul>
 810  
      *
 811  
      * @param dependency a dependency
 812  
      * @param description the description
 813  
      * @param source the source of the evidence
 814  
      * @param key the "name" of the evidence
 815  
      * @return if the description is trimmed, the trimmed version is returned;
 816  
      * otherwise the original description is returned
 817  
      */
 818  
     public static String addDescription(Dependency dependency, String description, String source, String key) {
 819  10
         if (dependency.getDescription() == null) {
 820  10
             dependency.setDescription(description);
 821  
         }
 822  
         String desc;
 823  10
         if (HTML_DETECTION_PATTERN.matcher(description).find()) {
 824  0
             desc = Jsoup.parse(description).text();
 825  
         } else {
 826  10
             desc = description;
 827  
         }
 828  10
         dependency.setDescription(desc);
 829  10
         if (desc.length() > 100) {
 830  0
             desc = desc.replaceAll("\\s\\s+", " ");
 831  0
             final int posSuchAs = desc.toLowerCase().indexOf("such as ", 100);
 832  0
             final int posLike = desc.toLowerCase().indexOf("like ", 100);
 833  0
             final int posWillUse = desc.toLowerCase().indexOf("will use ", 100);
 834  0
             final int posUses = desc.toLowerCase().indexOf(" uses ", 100);
 835  0
             int pos = -1;
 836  0
             pos = Math.max(pos, posSuchAs);
 837  0
             if (pos >= 0 && posLike >= 0) {
 838  0
                 pos = Math.min(pos, posLike);
 839  
             } else {
 840  0
                 pos = Math.max(pos, posLike);
 841  
             }
 842  0
             if (pos >= 0 && posWillUse >= 0) {
 843  0
                 pos = Math.min(pos, posWillUse);
 844  
             } else {
 845  0
                 pos = Math.max(pos, posWillUse);
 846  
             }
 847  0
             if (pos >= 0 && posUses >= 0) {
 848  0
                 pos = Math.min(pos, posUses);
 849  
             } else {
 850  0
                 pos = Math.max(pos, posUses);
 851  
             }
 852  
 
 853  0
             if (pos > 0) {
 854  0
                 desc = desc.substring(0, pos) + "...";
 855  
             }
 856  0
             dependency.getProductEvidence().addEvidence(source, key, desc, Confidence.LOW);
 857  0
             dependency.getVendorEvidence().addEvidence(source, key, desc, Confidence.LOW);
 858  0
         } else {
 859  10
             dependency.getProductEvidence().addEvidence(source, key, desc, Confidence.MEDIUM);
 860  10
             dependency.getVendorEvidence().addEvidence(source, key, desc, Confidence.MEDIUM);
 861  
         }
 862  10
         return desc;
 863  
     }
 864  
 
 865  
     /**
 866  
      * Adds a license to the given dependency.
 867  
      *
 868  
      * @param d a dependency
 869  
      * @param license the license
 870  
      */
 871  
     private void addLicense(Dependency d, String license) {
 872  2
         if (d.getLicense() == null) {
 873  2
             d.setLicense(license);
 874  0
         } else if (!d.getLicense().contains(license)) {
 875  0
             d.setLicense(d.getLicense() + NEWLINE + license);
 876  
         }
 877  2
     }
 878  
 
 879  
     /**
 880  
      * The parent directory for the individual directories per archive.
 881  
      */
 882  13
     private File tempFileLocation = null;
 883  
 
 884  
     /**
 885  
      * Initializes the JarAnalyzer.
 886  
      *
 887  
      * @throws InitializationException is thrown if there is an exception
 888  
      * creating a temporary directory
 889  
      */
 890  
     @Override
 891  
     public void initializeFileTypeAnalyzer() throws InitializationException {
 892  
         try {
 893  2
             final File baseDir = Settings.getTempDirectory();
 894  2
             tempFileLocation = File.createTempFile("check", "tmp", baseDir);
 895  2
             if (!tempFileLocation.delete()) {
 896  0
                 final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
 897  0
                 setEnabled(false);
 898  0
                 throw new InitializationException(msg);
 899  
             }
 900  2
             if (!tempFileLocation.mkdirs()) {
 901  0
                 final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
 902  0
                 setEnabled(false);
 903  0
                 throw new InitializationException(msg);
 904  
             }
 905  0
         } catch (IOException ex) {
 906  0
             setEnabled(false);
 907  0
             throw new InitializationException("Unable to create a temporary file", ex);
 908  2
         }
 909  2
     }
 910  
 
 911  
     /**
 912  
      * Deletes any files extracted from the JAR during analysis.
 913  
      */
 914  
     @Override
 915  
     public void closeAnalyzer() {
 916  1
         if (tempFileLocation != null && tempFileLocation.exists()) {
 917  1
             LOGGER.debug("Attempting to delete temporary files");
 918  1
             final boolean success = FileUtils.delete(tempFileLocation);
 919  1
             if (!success && tempFileLocation.exists()) {
 920  0
                 final String[] l = tempFileLocation.list();
 921  0
                 if (l != null && l.length > 0) {
 922  0
                     LOGGER.warn("Failed to delete some temporary files, see the log for more details");
 923  
                 }
 924  
             }
 925  
         }
 926  1
     }
 927  
 
 928  
     /**
 929  
      * Determines if the key value pair from the manifest is for an "import"
 930  
      * type entry for package names.
 931  
      *
 932  
      * @param key the key from the manifest
 933  
      * @param value the value from the manifest
 934  
      * @return true or false depending on if it is believed the entry is an
 935  
      * "import" entry
 936  
      */
 937  
     private boolean isImportPackage(String key, String value) {
 938  14
         final Pattern packageRx = Pattern.compile("^([a-zA-Z0-9_#\\$\\*\\.]+\\s*[,;]\\s*)+([a-zA-Z0-9_#\\$\\*\\.]+\\s*)?$");
 939  14
         final boolean matches = packageRx.matcher(value).matches();
 940  14
         return matches && (key.contains("import") || key.contains("include") || value.length() > 10);
 941  
     }
 942  
 
 943  
     /**
 944  
      * Cycles through an enumeration of JarEntries, contained within the
 945  
      * dependency, and returns a list of the class names. This does not include
 946  
      * core Java package names (i.e. java.* or javax.*).
 947  
      *
 948  
      * @param dependency the dependency being analyzed
 949  
      * @return an list of fully qualified class names
 950  
      */
 951  
     private List<ClassNameInformation> collectClassNames(Dependency dependency) {
 952  6
         final List<ClassNameInformation> classNames = new ArrayList<ClassNameInformation>();
 953  6
         JarFile jar = null;
 954  
         try {
 955  6
             jar = new JarFile(dependency.getActualFilePath());
 956  6
             final Enumeration<JarEntry> entries = jar.entries();
 957  1795
             while (entries.hasMoreElements()) {
 958  1769
                 final JarEntry entry = entries.nextElement();
 959  1750
                 final String name = entry.getName().toLowerCase();
 960  
                 //no longer stripping "|com\\.sun" - there are some com.sun jar files with CVEs.
 961  1823
                 if (name.endsWith(".class") && !name.matches("^javax?\\..*$")) {
 962  1429
                     final ClassNameInformation className = new ClassNameInformation(name.substring(0, name.length() - 6));
 963  1453
                     classNames.add(className);
 964  
                 }
 965  1800
             }
 966  0
         } catch (IOException ex) {
 967  0
             LOGGER.warn("Unable to open jar file '{}'.", dependency.getFileName());
 968  0
             LOGGER.debug("", ex);
 969  
         } finally {
 970  6
             if (jar != null) {
 971  
                 try {
 972  6
                     jar.close();
 973  0
                 } catch (IOException ex) {
 974  0
                     LOGGER.trace("", ex);
 975  6
                 }
 976  
             }
 977  
         }
 978  6
         return classNames;
 979  
     }
 980  
 
 981  
     /**
 982  
      * Cycles through the list of class names and places the package levels 0-3
 983  
      * into the provided maps for vendor and product. This is helpful when
 984  
      * analyzing vendor/product as many times this is included in the package
 985  
      * name.
 986  
      *
 987  
      * @param classNames a list of class names
 988  
      * @param vendor HashMap of possible vendor names from package names (e.g.
 989  
      * owasp)
 990  
      * @param product HashMap of possible product names from package names (e.g.
 991  
      * dependencycheck)
 992  
      */
 993  
     private void analyzeFullyQualifiedClassNames(List<ClassNameInformation> classNames,
 994  
             Map<String, Integer> vendor, Map<String, Integer> product) {
 995  6
         for (ClassNameInformation entry : classNames) {
 996  1535
             final List<String> list = entry.getPackageStructure();
 997  1535
             addEntry(vendor, list.get(0));
 998  
 
 999  1535
             if (list.size() == 2) {
 1000  0
                 addEntry(product, list.get(1));
 1001  1535
             } else if (list.size() == 3) {
 1002  345
                 addEntry(vendor, list.get(1));
 1003  345
                 addEntry(product, list.get(1));
 1004  345
                 addEntry(product, list.get(2));
 1005  1190
             } else if (list.size() >= 4) {
 1006  1190
                 addEntry(vendor, list.get(1));
 1007  1190
                 addEntry(vendor, list.get(2));
 1008  1190
                 addEntry(product, list.get(1));
 1009  1190
                 addEntry(product, list.get(2));
 1010  1190
                 addEntry(product, list.get(3));
 1011  
             }
 1012  1535
         }
 1013  6
     }
 1014  
 
 1015  
     /**
 1016  
      * Adds an entry to the specified collection and sets the Integer (e.g. the
 1017  
      * count) to 1. If the entry already exists in the collection then the
 1018  
      * Integer is incremented by 1.
 1019  
      *
 1020  
      * @param collection a collection of strings and their occurrence count
 1021  
      * @param key the key to add to the collection
 1022  
      */
 1023  
     private void addEntry(Map<String, Integer> collection, String key) {
 1024  8520
         if (collection.containsKey(key)) {
 1025  7487
             collection.put(key, collection.get(key) + 1);
 1026  
         } else {
 1027  1033
             collection.put(key, 1);
 1028  
         }
 1029  8520
     }
 1030  
 
 1031  
     /**
 1032  
      * Cycles through the collection of class name information to see if parts
 1033  
      * of the package names are contained in the provided value. If found, it
 1034  
      * will be added as the HIGHEST confidence evidence because we have more
 1035  
      * then one source corroborating the value.
 1036  
      *
 1037  
      * @param classes a collection of class name information
 1038  
      * @param value the value to check to see if it contains a package name
 1039  
      * @param evidence the evidence collection to add new entries too
 1040  
      */
 1041  
     private static void addMatchingValues(List<ClassNameInformation> classes, String value, EvidenceCollection evidence) {
 1042  63
         if (value == null || value.isEmpty() || classes == null || classes.isEmpty()) {
 1043  21
             return;
 1044  
         }
 1045  42
         final String text = value.toLowerCase();
 1046  42
         for (ClassNameInformation cni : classes) {
 1047  16314
             for (String key : cni.getPackageStructure()) {
 1048  62344
                 final Pattern p = Pattern.compile("\b" + key + "\b");
 1049  62348
                 if (p.matcher(text).find()) {
 1050  
                     //if (text.contains(key)) { //note, package structure elements are already lowercase.
 1051  0
                     evidence.addEvidence("jar", "package name", key, Confidence.HIGHEST);
 1052  
                 }
 1053  62360
             }
 1054  16317
         }
 1055  42
     }
 1056  
 
 1057  
     /**
 1058  
      * Simple check to see if the attribute from a manifest is just a package
 1059  
      * name.
 1060  
      *
 1061  
      * @param key the key of the value to check
 1062  
      * @param value the value to check
 1063  
      * @return true if the value looks like a java package name, otherwise false
 1064  
      */
 1065  
     private boolean isPackage(String key, String value) {
 1066  
 
 1067  28
         return !key.matches(".*(version|title|vendor|name|license|description).*")
 1068  8
                 && value.matches("^([a-zA-Z_][a-zA-Z0-9_\\$]*(\\.[a-zA-Z_][a-zA-Z0-9_\\$]*)*)?$");
 1069  
 
 1070  
     }
 1071  
 
 1072  
     /**
 1073  
      * Extracts the license information from the pom and adds it to the
 1074  
      * dependency.
 1075  
      *
 1076  
      * @param pom the pom object
 1077  
      * @param dependency the dependency to add license information too
 1078  
      */
 1079  
     public static void extractLicense(Model pom, Dependency dependency) {
 1080  
         //license
 1081  2
         if (pom.getLicenses() != null) {
 1082  2
             String license = null;
 1083  2
             for (License lic : pom.getLicenses()) {
 1084  0
                 String tmp = null;
 1085  0
                 if (lic.getName() != null) {
 1086  0
                     tmp = lic.getName();
 1087  
                 }
 1088  0
                 if (lic.getUrl() != null) {
 1089  0
                     if (tmp == null) {
 1090  0
                         tmp = lic.getUrl();
 1091  
                     } else {
 1092  0
                         tmp += ": " + lic.getUrl();
 1093  
                     }
 1094  
                 }
 1095  0
                 if (tmp == null) {
 1096  0
                     continue;
 1097  
                 }
 1098  0
                 if (HTML_DETECTION_PATTERN.matcher(tmp).find()) {
 1099  0
                     tmp = Jsoup.parse(tmp).text();
 1100  
                 }
 1101  0
                 if (license == null) {
 1102  0
                     license = tmp;
 1103  
                 } else {
 1104  0
                     license += "\n" + tmp;
 1105  
                 }
 1106  0
             }
 1107  2
             if (license != null) {
 1108  0
                 dependency.setLicense(license);
 1109  
 
 1110  
             }
 1111  
         }
 1112  2
     }
 1113  
 
 1114  
     /**
 1115  
      * Stores information about a class name.
 1116  
      */
 1117  
     protected static class ClassNameInformation {
 1118  
 
 1119  
         /**
 1120  
          * <p>
 1121  
          * Stores information about a given class name. This class will keep the
 1122  
          * fully qualified class name and a list of the important parts of the
 1123  
          * package structure. Up to the first four levels of the package
 1124  
          * structure are stored, excluding a leading "org" or "com".
 1125  
          * Example:</p>
 1126  
          * <code>ClassNameInformation obj = new ClassNameInformation("org.owasp.dependencycheck.analyzer.JarAnalyzer");
 1127  
          * System.out.println(obj.getName());
 1128  
          * for (String p : obj.getPackageStructure())
 1129  
          *     System.out.println(p);
 1130  
          * </code>
 1131  
          * <p>
 1132  
          * Would result in:</p>
 1133  
          * <code>org.owasp.dependencycheck.analyzer.JarAnalyzer
 1134  
          * owasp
 1135  
          * dependencycheck
 1136  
          * analyzer
 1137  
          * jaranalyzer</code>
 1138  
          *
 1139  
          * @param className a fully qualified class name
 1140  
          */
 1141  1430
         ClassNameInformation(String className) {
 1142  1463
             name = className;
 1143  1458
             if (name.contains("/")) {
 1144  1469
                 final String[] tmp = className.toLowerCase().split("/");
 1145  1492
                 int start = 0;
 1146  1489
                 int end = 3;
 1147  1489
                 if ("com".equals(tmp[0]) || "org".equals(tmp[0])) {
 1148  1532
                     start = 1;
 1149  1531
                     end = 4;
 1150  
                 }
 1151  1530
                 if (tmp.length <= end) {
 1152  343
                     end = tmp.length - 1;
 1153  
                 }
 1154  7171
                 for (int i = start; i <= end; i++) {
 1155  5668
                     packageStructure.add(tmp[i]);
 1156  
                 }
 1157  1507
             } else {
 1158  0
                 packageStructure.add(name);
 1159  
             }
 1160  1493
         }
 1161  
         /**
 1162  
          * The fully qualified class name.
 1163  
          */
 1164  
         private String name;
 1165  
 
 1166  
         /**
 1167  
          * Get the value of name
 1168  
          *
 1169  
          * @return the value of name
 1170  
          */
 1171  
         public String getName() {
 1172  0
             return name;
 1173  
         }
 1174  
 
 1175  
         /**
 1176  
          * Set the value of name
 1177  
          *
 1178  
          * @param name new value of name
 1179  
          */
 1180  
         public void setName(String name) {
 1181  0
             this.name = name;
 1182  0
         }
 1183  
         /**
 1184  
          * Up to the first four levels of the package structure, excluding a
 1185  
          * leading "org" or "com".
 1186  
          */
 1187  1435
         private final ArrayList<String> packageStructure = new ArrayList<String>();
 1188  
 
 1189  
         /**
 1190  
          * Get the value of packageStructure
 1191  
          *
 1192  
          * @return the value of packageStructure
 1193  
          */
 1194  
         public ArrayList<String> getPackageStructure() {
 1195  17855
             return packageStructure;
 1196  
         }
 1197  
     }
 1198  
 
 1199  
     /**
 1200  
      * Retrieves the next temporary directory to extract an archive too.
 1201  
      *
 1202  
      * @return a directory
 1203  
      * @throws AnalysisException thrown if unable to create temporary directory
 1204  
      */
 1205  
     private File getNextTempDirectory() throws AnalysisException {
 1206  2
         final int dirCount = DIR_COUNT.incrementAndGet();
 1207  2
         final File directory = new File(tempFileLocation, String.valueOf(dirCount));
 1208  
         //getting an exception for some directories not being able to be created; might be because the directory already exists?
 1209  2
         if (directory.exists()) {
 1210  0
             return getNextTempDirectory();
 1211  
         }
 1212  2
         if (!directory.mkdirs()) {
 1213  0
             final String msg = String.format("Unable to create temp directory '%s'.", directory.getAbsolutePath());
 1214  0
             throw new AnalysisException(msg);
 1215  
         }
 1216  2
         return directory;
 1217  
     }
 1218  
 }