Coverage Report - org.owasp.dependencycheck.analyzer.JarAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
JarAnalyzer
66%
351/531
55%
198/354
7.581
JarAnalyzer$ClassNameInformation
80%
17/21
80%
8/10
7.581
 
 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.OutputStream;
 27  
 import java.io.Reader;
 28  
 import java.util.ArrayList;
 29  
 import java.util.Collections;
 30  
 import java.util.Enumeration;
 31  
 import java.util.HashMap;
 32  
 import java.util.List;
 33  
 import java.util.Map;
 34  
 import java.util.Map.Entry;
 35  
 import java.util.Properties;
 36  
 import java.util.Set;
 37  
 import java.util.StringTokenizer;
 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 int dirCount = 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  10
     public JarAnalyzer() {
 157  10
     }
 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  861
         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  17
         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  4
         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  10
         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 analyzeFileType(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.", 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
         boolean foundSomething = false;
 264  
         final JarFile jar;
 265  
         try {
 266  6
             jar = new JarFile(dependency.getActualFilePath());
 267  0
         } catch (IOException ex) {
 268  0
             LOGGER.warn("Unable to read JarFile '{}'.", dependency.getActualFilePath());
 269  0
             LOGGER.trace("", ex);
 270  0
             return false;
 271  6
         }
 272  
         List<String> pomEntries;
 273  
         try {
 274  6
             pomEntries = retrievePomListing(jar);
 275  0
         } catch (IOException ex) {
 276  0
             LOGGER.warn("Unable to read Jar file entries in '{}'.", dependency.getActualFilePath());
 277  0
             LOGGER.trace("", ex);
 278  0
             return false;
 279  6
         }
 280  6
         File externalPom = null;
 281  6
         if (pomEntries.isEmpty()) {
 282  4
             final String pomPath = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
 283  4
             externalPom = new File(pomPath);
 284  4
             if (externalPom.isFile()) {
 285  0
                 pomEntries.add(pomPath);
 286  
             } else {
 287  4
                 return false;
 288  
             }
 289  
         }
 290  2
         for (String path : pomEntries) {
 291  2
             LOGGER.debug("Reading pom entry: {}", path);
 292  2
             Properties pomProperties = null;
 293  
             try {
 294  2
                 if (externalPom == null) {
 295  2
                     pomProperties = retrievePomProperties(path, jar);
 296  
                 }
 297  0
             } catch (IOException ex) {
 298  0
                 LOGGER.trace("ignore this, failed reading a non-existent pom.properties", ex);
 299  2
             }
 300  2
             Model pom = null;
 301  
             try {
 302  2
                 if (pomEntries.size() > 1) {
 303  
                     //extract POM to its own directory and add it as its own dependency
 304  0
                     final Dependency newDependency = new Dependency();
 305  0
                     pom = extractPom(path, jar, newDependency);
 306  
 
 307  0
                     final String displayPath = String.format("%s%s%s",
 308  0
                             dependency.getFilePath(),
 309  
                             File.separator,
 310  
                             path);
 311  0
                     final String displayName = String.format("%s%s%s",
 312  0
                             dependency.getFileName(),
 313  
                             File.separator,
 314  
                             path);
 315  
 
 316  0
                     newDependency.setFileName(displayName);
 317  0
                     newDependency.setFilePath(displayPath);
 318  0
                     pom.processProperties(pomProperties);
 319  0
                     setPomEvidence(newDependency, pom, null);
 320  0
                     engine.getDependencies().add(newDependency);
 321  0
                     Collections.sort(engine.getDependencies());
 322  0
                 } else {
 323  2
                     if (externalPom == null) {
 324  2
                         pom = PomUtils.readPom(path, jar);
 325  
                     } else {
 326  0
                         pom = PomUtils.readPom(externalPom);
 327  
                     }
 328  2
                     if (pom != null) {
 329  2
                         pom.processProperties(pomProperties);
 330  2
                         foundSomething |= setPomEvidence(dependency, pom, classes);
 331  
                     }
 332  
                 }
 333  0
             } catch (AnalysisException ex) {
 334  0
                 LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath());
 335  0
                 LOGGER.trace("", ex);
 336  2
             }
 337  2
         }
 338  2
         return foundSomething;
 339  
     }
 340  
 
 341  
     /**
 342  
      * Given a path to a pom.xml within a JarFile, this method attempts to load
 343  
      * a sibling pom.properties if one exists.
 344  
      *
 345  
      * @param path the path to the pom.xml within the JarFile
 346  
      * @param jar the JarFile to load the pom.properties from
 347  
      * @return a Properties object or null if no pom.properties was found
 348  
      * @throws IOException thrown if there is an exception reading the
 349  
      * pom.properties
 350  
      */
 351  
     private Properties retrievePomProperties(String path, final JarFile jar) throws IOException {
 352  2
         Properties pomProperties = null;
 353  2
         final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
 354  2
         final ZipEntry propEntry = jar.getEntry(propPath);
 355  2
         if (propEntry != null) {
 356  0
             Reader reader = null;
 357  
             try {
 358  0
                 reader = new InputStreamReader(jar.getInputStream(propEntry), "UTF-8");
 359  0
                 pomProperties = new Properties();
 360  0
                 pomProperties.load(reader);
 361  0
                 LOGGER.debug("Read pom.properties: {}", propPath);
 362  
             } finally {
 363  0
                 if (reader != null) {
 364  
                     try {
 365  0
                         reader.close();
 366  0
                     } catch (IOException ex) {
 367  0
                         LOGGER.trace("close error", ex);
 368  0
                     }
 369  
                 }
 370  
             }
 371  
         }
 372  2
         return pomProperties;
 373  
     }
 374  
 
 375  
     /**
 376  
      * Searches a JarFile for pom.xml entries and returns a listing of these
 377  
      * entries.
 378  
      *
 379  
      * @param jar the JarFile to search
 380  
      * @return a list of pom.xml entries
 381  
      * @throws IOException thrown if there is an exception reading a JarEntry
 382  
      */
 383  
     private List<String> retrievePomListing(final JarFile jar) throws IOException {
 384  6
         final List<String> pomEntries = new ArrayList<String>();
 385  6
         final Enumeration<JarEntry> entries = jar.entries();
 386  1852
         while (entries.hasMoreElements()) {
 387  1846
             final JarEntry entry = entries.nextElement();
 388  1846
             final String entryName = (new File(entry.getName())).getName().toLowerCase();
 389  1846
             if (!entry.isDirectory() && "pom.xml".equals(entryName)) {
 390  2
                 LOGGER.trace("POM Entry found: {}", entry.getName());
 391  2
                 pomEntries.add(entry.getName());
 392  
             }
 393  1846
         }
 394  6
         return pomEntries;
 395  
     }
 396  
 
 397  
     /**
 398  
      * Retrieves the specified POM from a jar file and converts it to a Model.
 399  
      *
 400  
      * @param path the path to the pom.xml file within the jar file
 401  
      * @param jar the jar file to extract the pom from
 402  
      * @param dependency the dependency being analyzed
 403  
      * @return returns the POM object
 404  
      * @throws AnalysisException is thrown if there is an exception extracting
 405  
      * or parsing the POM {@link org.owasp.dependencycheck.xml.pom.Model} object
 406  
      */
 407  
     private Model extractPom(String path, JarFile jar, Dependency dependency) throws AnalysisException {
 408  0
         InputStream input = null;
 409  0
         FileOutputStream fos = null;
 410  0
         final File tmpDir = getNextTempDirectory();
 411  0
         final File file = new File(tmpDir, "pom.xml");
 412  
         try {
 413  0
             final ZipEntry entry = jar.getEntry(path);
 414  0
             if (entry == null) {
 415  0
                 throw new AnalysisException(String.format("Pom (%s)does not exist in %s", path, jar.getName()));
 416  
             }
 417  0
             input = jar.getInputStream(entry);
 418  0
             fos = new FileOutputStream(file);
 419  0
             IOUtils.copy(input, fos);
 420  0
             dependency.setActualFilePath(file.getAbsolutePath());
 421  0
         } catch (IOException ex) {
 422  0
             LOGGER.warn("An error occurred reading '{}' from '{}'.", path, dependency.getFilePath());
 423  0
             LOGGER.error("", ex);
 424  
         } finally {
 425  0
             closeStream(fos);
 426  0
             closeStream(input);
 427  0
         }
 428  0
         return PomUtils.readPom(file);
 429  
     }
 430  
 
 431  
     /**
 432  
      * Silently closes an input stream ignoring errors.
 433  
      *
 434  
      * @param stream an input stream to close
 435  
      */
 436  
     private void closeStream(InputStream stream) {
 437  0
         if (stream != null) {
 438  
             try {
 439  0
                 stream.close();
 440  0
             } catch (IOException ex) {
 441  0
                 LOGGER.trace("", ex);
 442  0
             }
 443  
         }
 444  0
     }
 445  
 
 446  
     /**
 447  
      * Silently closes an output stream ignoring errors.
 448  
      *
 449  
      * @param stream an output stream to close
 450  
      */
 451  
     private void closeStream(OutputStream stream) {
 452  0
         if (stream != null) {
 453  
             try {
 454  0
                 stream.close();
 455  0
             } catch (IOException ex) {
 456  0
                 LOGGER.trace("", ex);
 457  0
             }
 458  
         }
 459  0
     }
 460  
 
 461  
     /**
 462  
      * Sets evidence from the pom on the supplied dependency.
 463  
      *
 464  
      * @param dependency the dependency to set data on
 465  
      * @param pom the information from the pom
 466  
      * @param classes a collection of ClassNameInformation - containing data
 467  
      * about the fully qualified class names within the JAR file being analyzed
 468  
      * @return true if there was evidence within the pom that we could use;
 469  
      * otherwise false
 470  
      */
 471  
     public static boolean setPomEvidence(Dependency dependency, Model pom, List<ClassNameInformation> classes) {
 472  2
         boolean foundSomething = false;
 473  2
         boolean addAsIdentifier = true;
 474  2
         if (pom == null) {
 475  0
             return foundSomething;
 476  
         }
 477  2
         String groupid = pom.getGroupId();
 478  2
         String parentGroupId = pom.getParentGroupId();
 479  2
         String artifactid = pom.getArtifactId();
 480  2
         String parentArtifactId = pom.getParentArtifactId();
 481  2
         String version = pom.getVersion();
 482  2
         String parentVersion = pom.getParentVersion();
 483  
 
 484  2
         if ("org.sonatype.oss".equals(parentGroupId) && "oss-parent".equals(parentArtifactId)) {
 485  0
             parentGroupId = null;
 486  0
             parentArtifactId = null;
 487  0
             parentVersion = null;
 488  
         }
 489  
 
 490  2
         if ((groupid == null || groupid.isEmpty()) && parentGroupId != null && !parentGroupId.isEmpty()) {
 491  0
             groupid = parentGroupId;
 492  
         }
 493  
 
 494  2
         final String originalGroupID = groupid;
 495  2
         if (groupid != null && (groupid.startsWith("org.") || groupid.startsWith("com."))) {
 496  1
             groupid = groupid.substring(4);
 497  
         }
 498  
 
 499  2
         if ((artifactid == null || artifactid.isEmpty()) && parentArtifactId != null && !parentArtifactId.isEmpty()) {
 500  0
             artifactid = parentArtifactId;
 501  
         }
 502  
 
 503  2
         final String originalArtifactID = artifactid;
 504  2
         if (artifactid != null && (artifactid.startsWith("org.") || artifactid.startsWith("com."))) {
 505  0
             artifactid = artifactid.substring(4);
 506  
         }
 507  
 
 508  2
         if ((version == null || version.isEmpty()) && parentVersion != null && !parentVersion.isEmpty()) {
 509  1
             version = parentVersion;
 510  
         }
 511  
 
 512  2
         if (groupid != null && !groupid.isEmpty()) {
 513  2
             foundSomething = true;
 514  2
             dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGHEST);
 515  2
             dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Confidence.LOW);
 516  2
             addMatchingValues(classes, groupid, dependency.getVendorEvidence());
 517  2
             addMatchingValues(classes, groupid, dependency.getProductEvidence());
 518  2
             if (parentGroupId != null && !parentGroupId.isEmpty() && !parentGroupId.equals(groupid)) {
 519  1
                 dependency.getVendorEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.MEDIUM);
 520  1
                 dependency.getProductEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.LOW);
 521  1
                 addMatchingValues(classes, parentGroupId, dependency.getVendorEvidence());
 522  1
                 addMatchingValues(classes, parentGroupId, dependency.getProductEvidence());
 523  
             }
 524  
         } else {
 525  0
             addAsIdentifier = false;
 526  
         }
 527  
 
 528  2
         if (artifactid != null && !artifactid.isEmpty()) {
 529  2
             foundSomething = true;
 530  2
             dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.HIGHEST);
 531  2
             dependency.getVendorEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.LOW);
 532  2
             addMatchingValues(classes, artifactid, dependency.getVendorEvidence());
 533  2
             addMatchingValues(classes, artifactid, dependency.getProductEvidence());
 534  2
             if (parentArtifactId != null && !parentArtifactId.isEmpty() && !parentArtifactId.equals(artifactid)) {
 535  1
                 dependency.getProductEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.MEDIUM);
 536  1
                 dependency.getVendorEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.LOW);
 537  1
                 addMatchingValues(classes, parentArtifactId, dependency.getVendorEvidence());
 538  1
                 addMatchingValues(classes, parentArtifactId, dependency.getProductEvidence());
 539  
             }
 540  
         } else {
 541  0
             addAsIdentifier = false;
 542  
         }
 543  
 
 544  2
         if (version != null && !version.isEmpty()) {
 545  2
             foundSomething = true;
 546  2
             dependency.getVersionEvidence().addEvidence("pom", "version", version, Confidence.HIGHEST);
 547  2
             if (parentVersion != null && !parentVersion.isEmpty() && !parentVersion.equals(version)) {
 548  0
                 dependency.getVersionEvidence().addEvidence("pom", "parent-version", version, Confidence.LOW);
 549  
             }
 550  
         } else {
 551  0
             addAsIdentifier = false;
 552  
         }
 553  
 
 554  2
         if (addAsIdentifier) {
 555  2
             dependency.addIdentifier("maven", String.format("%s:%s:%s", originalGroupID, originalArtifactID, version), null, Confidence.HIGH);
 556  
         }
 557  
 
 558  
         // org name
 559  2
         final String org = pom.getOrganization();
 560  2
         if (org != null && !org.isEmpty()) {
 561  0
             dependency.getVendorEvidence().addEvidence("pom", "organization name", org, Confidence.HIGH);
 562  0
             dependency.getProductEvidence().addEvidence("pom", "organization name", org, Confidence.LOW);
 563  0
             addMatchingValues(classes, org, dependency.getVendorEvidence());
 564  0
             addMatchingValues(classes, org, dependency.getProductEvidence());
 565  
         }
 566  
         //pom name
 567  2
         final String pomName = pom.getName();
 568  2
         if (pomName
 569  2
                 != null && !pomName.isEmpty()) {
 570  2
             foundSomething = true;
 571  2
             dependency.getProductEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH);
 572  2
             dependency.getVendorEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH);
 573  2
             addMatchingValues(classes, pomName, dependency.getVendorEvidence());
 574  2
             addMatchingValues(classes, pomName, dependency.getProductEvidence());
 575  
         }
 576  
 
 577  
         //Description
 578  2
         final String description = pom.getDescription();
 579  2
         if (description != null && !description.isEmpty() && !description.startsWith("POM was created by")) {
 580  1
             foundSomething = true;
 581  1
             final String trimmedDescription = addDescription(dependency, description, "pom", "description");
 582  1
             addMatchingValues(classes, trimmedDescription, dependency.getVendorEvidence());
 583  1
             addMatchingValues(classes, trimmedDescription, dependency.getProductEvidence());
 584  
         }
 585  
 
 586  2
         final String projectURL = pom.getProjectURL();
 587  2
         if (projectURL != null && !projectURL.trim().isEmpty()) {
 588  1
             dependency.getVendorEvidence().addEvidence("pom", "url", projectURL, Confidence.HIGHEST);
 589  
         }
 590  
 
 591  2
         extractLicense(pom, dependency);
 592  2
         return foundSomething;
 593  
     }
 594  
 
 595  
     /**
 596  
      * Analyzes the path information of the classes contained within the
 597  
      * JarAnalyzer to try and determine possible vendor or product names. If any
 598  
      * are found they are stored in the packageVendor and packageProduct
 599  
      * hashSets.
 600  
      *
 601  
      * @param classNames a list of class names
 602  
      * @param dependency a dependency to analyze
 603  
      * @param addPackagesAsEvidence a flag indicating whether or not package
 604  
      * names should be added as evidence.
 605  
      */
 606  
     protected void analyzePackageNames(List<ClassNameInformation> classNames,
 607  
             Dependency dependency, boolean addPackagesAsEvidence) {
 608  6
         final Map<String, Integer> vendorIdentifiers = new HashMap<String, Integer>();
 609  6
         final Map<String, Integer> productIdentifiers = new HashMap<String, Integer>();
 610  6
         analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers);
 611  
 
 612  6
         final int classCount = classNames.size();
 613  6
         final EvidenceCollection vendor = dependency.getVendorEvidence();
 614  6
         final EvidenceCollection product = dependency.getProductEvidence();
 615  
 
 616  6
         for (Map.Entry<String, Integer> entry : vendorIdentifiers.entrySet()) {
 617  48
             final float ratio = entry.getValue() / (float) classCount;
 618  48
             if (ratio > 0.5) {
 619  
                 //TODO remove weighting
 620  10
                 vendor.addWeighting(entry.getKey());
 621  10
                 if (addPackagesAsEvidence && entry.getKey().length() > 1) {
 622  8
                     vendor.addEvidence("jar", "package name", entry.getKey(), Confidence.LOW);
 623  
                 }
 624  
             }
 625  48
         }
 626  6
         for (Map.Entry<String, Integer> entry : productIdentifiers.entrySet()) {
 627  985
             final float ratio = entry.getValue() / (float) classCount;
 628  985
             if (ratio > 0.5) {
 629  5
                 product.addWeighting(entry.getKey());
 630  5
                 if (addPackagesAsEvidence && entry.getKey().length() > 1) {
 631  4
                     product.addEvidence("jar", "package name", entry.getKey(), Confidence.LOW);
 632  
                 }
 633  
             }
 634  985
         }
 635  6
     }
 636  
 
 637  
     /**
 638  
      * <p>
 639  
      * Reads the manifest from the JAR file and collects the entries. Some
 640  
      * vendorKey entries are:</p>
 641  
      * <ul><li>Implementation Title</li>
 642  
      * <li>Implementation Version</li> <li>Implementation Vendor</li>
 643  
      * <li>Implementation VendorId</li> <li>Bundle Name</li> <li>Bundle
 644  
      * Version</li> <li>Bundle Vendor</li> <li>Bundle Description</li> <li>Main
 645  
      * Class</li> </ul>
 646  
      * However, all but a handful of specific entries are read in.
 647  
      *
 648  
      * @param dependency A reference to the dependency
 649  
      * @param classInformation a collection of class information
 650  
      * @return whether evidence was identified parsing the manifest
 651  
      * @throws IOException if there is an issue reading the JAR file
 652  
      */
 653  
     protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation) throws IOException {
 654  7
         boolean foundSomething = false;
 655  7
         JarFile jar = null;
 656  
         try {
 657  7
             jar = new JarFile(dependency.getActualFilePath());
 658  7
             final Manifest manifest = jar.getManifest();
 659  7
             if (manifest == null) {
 660  0
                 if (!dependency.getFileName().toLowerCase().endsWith("-sources.jar")
 661  0
                         && !dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
 662  0
                         && !dependency.getFileName().toLowerCase().endsWith("-src.jar")
 663  0
                         && !dependency.getFileName().toLowerCase().endsWith("-doc.jar")) {
 664  0
                     LOGGER.debug("Jar file '{}' does not contain a manifest.",
 665  0
                             dependency.getFileName());
 666  
                 }
 667  0
                 return false;
 668  
             }
 669  7
             final EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
 670  7
             final EvidenceCollection productEvidence = dependency.getProductEvidence();
 671  7
             final EvidenceCollection versionEvidence = dependency.getVersionEvidence();
 672  7
             String source = "Manifest";
 673  7
             String specificationVersion = null;
 674  7
             boolean hasImplementationVersion = false;
 675  7
             Attributes atts = manifest.getMainAttributes();
 676  7
             for (Entry<Object, Object> entry : atts.entrySet()) {
 677  72
                 String key = entry.getKey().toString();
 678  72
                 String value = atts.getValue(key);
 679  72
                 if (HTML_DETECTION_PATTERN.matcher(value).find()) {
 680  0
                     value = Jsoup.parse(value).text();
 681  
                 }
 682  72
                 if (IGNORE_VALUES.contains(value)) {
 683  0
                     continue;
 684  72
                 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
 685  1
                     foundSomething = true;
 686  1
                     productEvidence.addEvidence(source, key, value, Confidence.HIGH);
 687  1
                     addMatchingValues(classInformation, value, productEvidence);
 688  71
                 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
 689  2
                     hasImplementationVersion = true;
 690  2
                     foundSomething = true;
 691  2
                     versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
 692  69
                 } else if ("specification-version".equalsIgnoreCase(key)) {
 693  1
                     specificationVersion = value;
 694  68
                 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
 695  1
                     foundSomething = true;
 696  1
                     vendorEvidence.addEvidence(source, key, value, Confidence.HIGH);
 697  1
                     addMatchingValues(classInformation, value, vendorEvidence);
 698  67
                 } else if (key.equalsIgnoreCase(IMPLEMENTATION_VENDOR_ID)) {
 699  0
                     foundSomething = true;
 700  0
                     vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 701  0
                     addMatchingValues(classInformation, value, vendorEvidence);
 702  67
                 } else if (key.equalsIgnoreCase(BUNDLE_DESCRIPTION)) {
 703  2
                     foundSomething = true;
 704  2
                     addDescription(dependency, value, "manifest", key);
 705  2
                     addMatchingValues(classInformation, value, productEvidence);
 706  65
                 } else if (key.equalsIgnoreCase(BUNDLE_NAME)) {
 707  3
                     foundSomething = true;
 708  3
                     productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 709  3
                     addMatchingValues(classInformation, value, productEvidence);
 710  
 //                //the following caused false positives.
 711  
 //                } else if (key.equalsIgnoreCase(BUNDLE_VENDOR)) {
 712  62
                 } else if (key.equalsIgnoreCase(BUNDLE_VERSION)) {
 713  3
                     foundSomething = true;
 714  3
                     versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
 715  59
                 } else if (key.equalsIgnoreCase(Attributes.Name.MAIN_CLASS.toString())) {
 716  3
                     continue;
 717  
                     //skipping main class as if this has important information to add it will be added during class name analysis...
 718  
                 } else {
 719  56
                     key = key.toLowerCase();
 720  56
                     if (!IGNORE_KEYS.contains(key)
 721  15
                             && !key.endsWith("jdk")
 722  15
                             && !key.contains("lastmodified")
 723  14
                             && !key.endsWith("package")
 724  14
                             && !key.endsWith("classpath")
 725  14
                             && !key.endsWith("class-path")
 726  14
                             && !key.endsWith("-scm") //todo change this to a regex?
 727  14
                             && !key.startsWith("scm-")
 728  14
                             && !value.trim().startsWith("scm:")
 729  14
                             && !isImportPackage(key, value)
 730  14
                             && !isPackage(key, value)) {
 731  13
                         foundSomething = true;
 732  13
                         if (key.contains("version")) {
 733  0
                             if (!key.contains("specification")) {
 734  0
                                 versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 735  
                             }
 736  13
                         } else if ("build-id".equals(key)) {
 737  0
                             int pos = value.indexOf('(');
 738  0
                             if (pos >= 0) {
 739  0
                                 value = value.substring(0, pos - 1);
 740  
                             }
 741  0
                             pos = value.indexOf('[');
 742  0
                             if (pos >= 0) {
 743  0
                                 value = value.substring(0, pos - 1);
 744  
                             }
 745  0
                             versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 746  0
                         } else if (key.contains("title")) {
 747  1
                             productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 748  1
                             addMatchingValues(classInformation, value, productEvidence);
 749  12
                         } else if (key.contains("vendor")) {
 750  0
                             if (key.contains("specification")) {
 751  0
                                 vendorEvidence.addEvidence(source, key, value, Confidence.LOW);
 752  
                             } else {
 753  0
                                 vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 754  0
                                 addMatchingValues(classInformation, value, vendorEvidence);
 755  
                             }
 756  12
                         } else if (key.contains("name")) {
 757  3
                             productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 758  3
                             vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 759  3
                             addMatchingValues(classInformation, value, vendorEvidence);
 760  3
                             addMatchingValues(classInformation, value, productEvidence);
 761  9
                         } else if (key.contains("license")) {
 762  2
                             addLicense(dependency, value);
 763  7
                         } else if (key.contains("description")) {
 764  0
                             addDescription(dependency, value, "manifest", key);
 765  
                         } else {
 766  7
                             productEvidence.addEvidence(source, key, value, Confidence.LOW);
 767  7
                             vendorEvidence.addEvidence(source, key, value, Confidence.LOW);
 768  7
                             addMatchingValues(classInformation, value, vendorEvidence);
 769  7
                             addMatchingValues(classInformation, value, productEvidence);
 770  7
                             if (value.matches(".*\\d.*")) {
 771  3
                                 final StringTokenizer tokenizer = new StringTokenizer(value, " ");
 772  15
                                 while (tokenizer.hasMoreElements()) {
 773  12
                                     final String s = tokenizer.nextToken();
 774  12
                                     if (s.matches("^[0-9.]+$")) {
 775  1
                                         versionEvidence.addEvidence(source, key, s, Confidence.LOW);
 776  
                                     }
 777  12
                                 }
 778  
                             }
 779  
                         }
 780  
                     }
 781  
                 }
 782  69
             }
 783  7
             for (Map.Entry<String, Attributes> item : manifest.getEntries().entrySet()) {
 784  8
                 final String name = item.getKey();
 785  8
                 source = "manifest: " + name;
 786  8
                 atts = item.getValue();
 787  8
                 for (Entry<Object, Object> entry : atts.entrySet()) {
 788  38
                     final String key = entry.getKey().toString();
 789  38
                     final String value = atts.getValue(key);
 790  38
                     if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
 791  8
                         foundSomething = true;
 792  8
                         productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 793  8
                         addMatchingValues(classInformation, value, productEvidence);
 794  30
                     } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
 795  5
                         foundSomething = true;
 796  5
                         versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 797  25
                     } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
 798  5
                         foundSomething = true;
 799  5
                         vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 800  5
                         addMatchingValues(classInformation, value, vendorEvidence);
 801  20
                     } else if (key.equalsIgnoreCase(Attributes.Name.SPECIFICATION_TITLE.toString())) {
 802  4
                         foundSomething = true;
 803  4
                         productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
 804  4
                         addMatchingValues(classInformation, value, productEvidence);
 805  
                     }
 806  38
                 }
 807  8
             }
 808  7
             if (specificationVersion != null && !hasImplementationVersion) {
 809  0
                 foundSomething = true;
 810  0
                 versionEvidence.addEvidence(source, "specification-version", specificationVersion, Confidence.HIGH);
 811  
             }
 812  
         } finally {
 813  7
             if (jar != null) {
 814  7
                 jar.close();
 815  
             }
 816  
         }
 817  7
         return foundSomething;
 818  
     }
 819  
 
 820  
     /**
 821  
      * Adds a description to the given dependency. If the description contains
 822  
      * one of the following strings beyond 100 characters, then the description
 823  
      * used will be trimmed to that position:
 824  
      * <ul><li>"such as"</li><li>"like "</li><li>"will use "</li><li>"* uses
 825  
      * "</li></ul>
 826  
      *
 827  
      * @param dependency a dependency
 828  
      * @param description the description
 829  
      * @param source the source of the evidence
 830  
      * @param key the "name" of the evidence
 831  
      * @return if the description is trimmed, the trimmed version is returned;
 832  
      * otherwise the original description is returned
 833  
      */
 834  
     public static String addDescription(Dependency dependency, String description, String source, String key) {
 835  10
         if (dependency.getDescription() == null) {
 836  10
             dependency.setDescription(description);
 837  
         }
 838  
         String desc;
 839  10
         if (HTML_DETECTION_PATTERN.matcher(description).find()) {
 840  0
             desc = Jsoup.parse(description).text();
 841  
         } else {
 842  10
             desc = description;
 843  
         }
 844  10
         dependency.setDescription(desc);
 845  10
         if (desc.length() > 100) {
 846  0
             desc = desc.replaceAll("\\s\\s+", " ");
 847  0
             final int posSuchAs = desc.toLowerCase().indexOf("such as ", 100);
 848  0
             final int posLike = desc.toLowerCase().indexOf("like ", 100);
 849  0
             final int posWillUse = desc.toLowerCase().indexOf("will use ", 100);
 850  0
             final int posUses = desc.toLowerCase().indexOf(" uses ", 100);
 851  0
             int pos = -1;
 852  0
             pos = Math.max(pos, posSuchAs);
 853  0
             if (pos >= 0 && posLike >= 0) {
 854  0
                 pos = Math.min(pos, posLike);
 855  
             } else {
 856  0
                 pos = Math.max(pos, posLike);
 857  
             }
 858  0
             if (pos >= 0 && posWillUse >= 0) {
 859  0
                 pos = Math.min(pos, posWillUse);
 860  
             } else {
 861  0
                 pos = Math.max(pos, posWillUse);
 862  
             }
 863  0
             if (pos >= 0 && posUses >= 0) {
 864  0
                 pos = Math.min(pos, posUses);
 865  
             } else {
 866  0
                 pos = Math.max(pos, posUses);
 867  
             }
 868  
 
 869  0
             if (pos > 0) {
 870  0
                 desc = desc.substring(0, pos) + "...";
 871  
             }
 872  0
             dependency.getProductEvidence().addEvidence(source, key, desc, Confidence.LOW);
 873  0
             dependency.getVendorEvidence().addEvidence(source, key, desc, Confidence.LOW);
 874  0
         } else {
 875  10
             dependency.getProductEvidence().addEvidence(source, key, desc, Confidence.MEDIUM);
 876  10
             dependency.getVendorEvidence().addEvidence(source, key, desc, Confidence.MEDIUM);
 877  
         }
 878  10
         return desc;
 879  
     }
 880  
 
 881  
     /**
 882  
      * Adds a license to the given dependency.
 883  
      *
 884  
      * @param d a dependency
 885  
      * @param license the license
 886  
      */
 887  
     private void addLicense(Dependency d, String license) {
 888  2
         if (d.getLicense() == null) {
 889  2
             d.setLicense(license);
 890  0
         } else if (!d.getLicense().contains(license)) {
 891  0
             d.setLicense(d.getLicense() + NEWLINE + license);
 892  
         }
 893  2
     }
 894  
 
 895  
     /**
 896  
      * The parent directory for the individual directories per archive.
 897  
      */
 898  10
     private File tempFileLocation = null;
 899  
 
 900  
     /**
 901  
      * Initializes the JarAnalyzer.
 902  
      *
 903  
      * @throws InitializationException is thrown if there is an exception
 904  
      * creating a temporary directory
 905  
      */
 906  
     @Override
 907  
     public void initializeFileTypeAnalyzer() throws InitializationException {
 908  
         try {
 909  1
             final File baseDir = Settings.getTempDirectory();
 910  1
             tempFileLocation = File.createTempFile("check", "tmp", baseDir);
 911  1
             if (!tempFileLocation.delete()) {
 912  0
                 final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
 913  0
                 setEnabled(false);
 914  0
                 throw new InitializationException(msg);
 915  
             }
 916  1
             if (!tempFileLocation.mkdirs()) {
 917  0
                 final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
 918  0
                 setEnabled(false);
 919  0
                 throw new InitializationException(msg);
 920  
             }
 921  0
         } catch (IOException ex) {
 922  0
             setEnabled(false);
 923  0
             throw new InitializationException("Unable to create a temporary file", ex);
 924  1
         }
 925  1
     }
 926  
 
 927  
     /**
 928  
      * Deletes any files extracted from the JAR during analysis.
 929  
      */
 930  
     @Override
 931  
     public void close() {
 932  2
         if (tempFileLocation != null && tempFileLocation.exists()) {
 933  1
             LOGGER.debug("Attempting to delete temporary files");
 934  1
             final boolean success = FileUtils.delete(tempFileLocation);
 935  1
             if (!success) {
 936  0
                 LOGGER.warn("Failed to delete some temporary files, see the log for more details");
 937  
             }
 938  
         }
 939  2
     }
 940  
 
 941  
     /**
 942  
      * Determines if the key value pair from the manifest is for an "import"
 943  
      * type entry for package names.
 944  
      *
 945  
      * @param key the key from the manifest
 946  
      * @param value the value from the manifest
 947  
      * @return true or false depending on if it is believed the entry is an
 948  
      * "import" entry
 949  
      */
 950  
     private boolean isImportPackage(String key, String value) {
 951  14
         final Pattern packageRx = Pattern.compile("^([a-zA-Z0-9_#\\$\\*\\.]+\\s*[,;]\\s*)+([a-zA-Z0-9_#\\$\\*\\.]+\\s*)?$");
 952  14
         final boolean matches = packageRx.matcher(value).matches();
 953  14
         return matches && (key.contains("import") || key.contains("include") || value.length() > 10);
 954  
     }
 955  
 
 956  
     /**
 957  
      * Cycles through an enumeration of JarEntries, contained within the
 958  
      * dependency, and returns a list of the class names. This does not include
 959  
      * core Java package names (i.e. java.* or javax.*).
 960  
      *
 961  
      * @param dependency the dependency being analyzed
 962  
      * @return an list of fully qualified class names
 963  
      */
 964  
     private List<ClassNameInformation> collectClassNames(Dependency dependency) {
 965  6
         final List<ClassNameInformation> classNames = new ArrayList<ClassNameInformation>();
 966  6
         JarFile jar = null;
 967  
         try {
 968  6
             jar = new JarFile(dependency.getActualFilePath());
 969  6
             final Enumeration<JarEntry> entries = jar.entries();
 970  1852
             while (entries.hasMoreElements()) {
 971  1846
                 final JarEntry entry = entries.nextElement();
 972  1846
                 final String name = entry.getName().toLowerCase();
 973  
                 //no longer stripping "|com\\.sun" - there are some com.sun jar files with CVEs.
 974  1846
                 if (name.endsWith(".class") && !name.matches("^javax?\\..*$")) {
 975  1535
                     final ClassNameInformation className = new ClassNameInformation(name.substring(0, name.length() - 6));
 976  1535
                     classNames.add(className);
 977  
                 }
 978  1846
             }
 979  0
         } catch (IOException ex) {
 980  0
             LOGGER.warn("Unable to open jar file '{}'.", dependency.getFileName());
 981  0
             LOGGER.debug("", ex);
 982  
         } finally {
 983  6
             if (jar != null) {
 984  
                 try {
 985  6
                     jar.close();
 986  0
                 } catch (IOException ex) {
 987  0
                     LOGGER.trace("", ex);
 988  6
                 }
 989  
             }
 990  
         }
 991  6
         return classNames;
 992  
     }
 993  
 
 994  
     /**
 995  
      * Cycles through the list of class names and places the package levels 0-3
 996  
      * into the provided maps for vendor and product. This is helpful when
 997  
      * analyzing vendor/product as many times this is included in the package
 998  
      * name.
 999  
      *
 1000  
      * @param classNames a list of class names
 1001  
      * @param vendor HashMap of possible vendor names from package names (e.g.
 1002  
      * owasp)
 1003  
      * @param product HashMap of possible product names from package names (e.g.
 1004  
      * dependencycheck)
 1005  
      */
 1006  
     private void analyzeFullyQualifiedClassNames(List<ClassNameInformation> classNames,
 1007  
             Map<String, Integer> vendor, Map<String, Integer> product) {
 1008  6
         for (ClassNameInformation entry : classNames) {
 1009  1535
             final List<String> list = entry.getPackageStructure();
 1010  1535
             addEntry(vendor, list.get(0));
 1011  
 
 1012  1535
             if (list.size() == 2) {
 1013  0
                 addEntry(product, list.get(1));
 1014  
             }
 1015  1535
             if (list.size() == 3) {
 1016  345
                 addEntry(vendor, list.get(1));
 1017  345
                 addEntry(product, list.get(1));
 1018  345
                 addEntry(product, list.get(2));
 1019  
             }
 1020  1535
             if (list.size() >= 4) {
 1021  1190
                 addEntry(vendor, list.get(1));
 1022  1190
                 addEntry(vendor, list.get(2));
 1023  1190
                 addEntry(product, list.get(1));
 1024  1190
                 addEntry(product, list.get(2));
 1025  1190
                 addEntry(product, list.get(3));
 1026  
             }
 1027  1535
         }
 1028  6
     }
 1029  
 
 1030  
     /**
 1031  
      * Adds an entry to the specified collection and sets the Integer (e.g. the
 1032  
      * count) to 1. If the entry already exists in the collection then the
 1033  
      * Integer is incremented by 1.
 1034  
      *
 1035  
      * @param collection a collection of strings and their occurrence count
 1036  
      * @param key the key to add to the collection
 1037  
      */
 1038  
     private void addEntry(Map<String, Integer> collection, String key) {
 1039  8520
         if (collection.containsKey(key)) {
 1040  7487
             collection.put(key, collection.get(key) + 1);
 1041  
         } else {
 1042  1033
             collection.put(key, 1);
 1043  
         }
 1044  8520
     }
 1045  
 
 1046  
     /**
 1047  
      * Cycles through the collection of class name information to see if parts
 1048  
      * of the package names are contained in the provided value. If found, it
 1049  
      * will be added as the HIGHEST confidence evidence because we have more
 1050  
      * then one source corroborating the value.
 1051  
      *
 1052  
      * @param classes a collection of class name information
 1053  
      * @param value the value to check to see if it contains a package name
 1054  
      * @param evidence the evidence collection to add new entries too
 1055  
      */
 1056  
     private static void addMatchingValues(List<ClassNameInformation> classes, String value, EvidenceCollection evidence) {
 1057  63
         if (value == null || value.isEmpty() || classes == null || classes.isEmpty()) {
 1058  21
             return;
 1059  
         }
 1060  42
         final String text = value.toLowerCase();
 1061  42
         for (ClassNameInformation cni : classes) {
 1062  16322
             for (String key : cni.getPackageStructure()) {
 1063  62418
                 final Pattern p = Pattern.compile("\b" + key + "\b");
 1064  62418
                 if (p.matcher(text).find()) {
 1065  
                     //if (text.contains(key)) { //note, package structure elements are already lowercase.
 1066  0
                     evidence.addEvidence("jar", "package name", key, Confidence.HIGHEST);
 1067  
                 }
 1068  62418
             }
 1069  16322
         }
 1070  42
     }
 1071  
 
 1072  
     /**
 1073  
      * Simple check to see if the attribute from a manifest is just a package
 1074  
      * name.
 1075  
      *
 1076  
      * @param key the key of the value to check
 1077  
      * @param value the value to check
 1078  
      * @return true if the value looks like a java package name, otherwise false
 1079  
      */
 1080  
     private boolean isPackage(String key, String value) {
 1081  
 
 1082  28
         return !key.matches(".*(version|title|vendor|name|license|description).*")
 1083  8
                 && value.matches("^([a-zA-Z_][a-zA-Z0-9_\\$]*(\\.[a-zA-Z_][a-zA-Z0-9_\\$]*)*)?$");
 1084  
 
 1085  
     }
 1086  
 
 1087  
     /**
 1088  
      * Extracts the license information from the pom and adds it to the
 1089  
      * dependency.
 1090  
      *
 1091  
      * @param pom the pom object
 1092  
      * @param dependency the dependency to add license information too
 1093  
      */
 1094  
     public static void extractLicense(Model pom, Dependency dependency) {
 1095  
         //license
 1096  2
         if (pom.getLicenses() != null) {
 1097  2
             String license = null;
 1098  2
             for (License lic : pom.getLicenses()) {
 1099  0
                 String tmp = null;
 1100  0
                 if (lic.getName() != null) {
 1101  0
                     tmp = lic.getName();
 1102  
                 }
 1103  0
                 if (lic.getUrl() != null) {
 1104  0
                     if (tmp == null) {
 1105  0
                         tmp = lic.getUrl();
 1106  
                     } else {
 1107  0
                         tmp += ": " + lic.getUrl();
 1108  
                     }
 1109  
                 }
 1110  0
                 if (tmp == null) {
 1111  0
                     continue;
 1112  
                 }
 1113  0
                 if (HTML_DETECTION_PATTERN.matcher(tmp).find()) {
 1114  0
                     tmp = Jsoup.parse(tmp).text();
 1115  
                 }
 1116  0
                 if (license == null) {
 1117  0
                     license = tmp;
 1118  
                 } else {
 1119  0
                     license += "\n" + tmp;
 1120  
                 }
 1121  0
             }
 1122  2
             if (license != null) {
 1123  0
                 dependency.setLicense(license);
 1124  
 
 1125  
             }
 1126  
         }
 1127  2
     }
 1128  
 
 1129  
     /**
 1130  
      * Stores information about a class name.
 1131  
      */
 1132  
     protected static class ClassNameInformation {
 1133  
 
 1134  
         /**
 1135  
          * <p>
 1136  
          * Stores information about a given class name. This class will keep the
 1137  
          * fully qualified class name and a list of the important parts of the
 1138  
          * package structure. Up to the first four levels of the package
 1139  
          * structure are stored, excluding a leading "org" or "com".
 1140  
          * Example:</p>
 1141  
          * <code>ClassNameInformation obj = new ClassNameInformation("org.owasp.dependencycheck.analyzer.JarAnalyzer");
 1142  
          * System.out.println(obj.getName());
 1143  
          * for (String p : obj.getPackageStructure())
 1144  
          *     System.out.println(p);
 1145  
          * </code>
 1146  
          * <p>
 1147  
          * Would result in:</p>
 1148  
          * <code>org.owasp.dependencycheck.analyzer.JarAnalyzer
 1149  
          * owasp
 1150  
          * dependencycheck
 1151  
          * analyzer
 1152  
          * jaranalyzer</code>
 1153  
          *
 1154  
          * @param className a fully qualified class name
 1155  
          */
 1156  1535
         ClassNameInformation(String className) {
 1157  1535
             name = className;
 1158  1535
             if (name.contains("/")) {
 1159  1535
                 final String[] tmp = className.toLowerCase().split("/");
 1160  1535
                 int start = 0;
 1161  1535
                 int end = 3;
 1162  1535
                 if ("com".equals(tmp[0]) || "org".equals(tmp[0])) {
 1163  1535
                     start = 1;
 1164  1535
                     end = 4;
 1165  
                 }
 1166  1535
                 if (tmp.length <= end) {
 1167  345
                     end = tmp.length - 1;
 1168  
                 }
 1169  7330
                 for (int i = start; i <= end; i++) {
 1170  5795
                     packageStructure.add(tmp[i]);
 1171  
                 }
 1172  1535
             } else {
 1173  0
                 packageStructure.add(name);
 1174  
             }
 1175  1535
         }
 1176  
         /**
 1177  
          * The fully qualified class name.
 1178  
          */
 1179  
         private String name;
 1180  
 
 1181  
         /**
 1182  
          * Get the value of name
 1183  
          *
 1184  
          * @return the value of name
 1185  
          */
 1186  
         public String getName() {
 1187  0
             return name;
 1188  
         }
 1189  
 
 1190  
         /**
 1191  
          * Set the value of name
 1192  
          *
 1193  
          * @param name new value of name
 1194  
          */
 1195  
         public void setName(String name) {
 1196  0
             this.name = name;
 1197  0
         }
 1198  
         /**
 1199  
          * Up to the first four levels of the package structure, excluding a
 1200  
          * leading "org" or "com".
 1201  
          */
 1202  1535
         private final ArrayList<String> packageStructure = new ArrayList<String>();
 1203  
 
 1204  
         /**
 1205  
          * Get the value of packageStructure
 1206  
          *
 1207  
          * @return the value of packageStructure
 1208  
          */
 1209  
         public ArrayList<String> getPackageStructure() {
 1210  17857
             return packageStructure;
 1211  
         }
 1212  
     }
 1213  
 
 1214  
     /**
 1215  
      * Retrieves the next temporary directory to extract an archive too.
 1216  
      *
 1217  
      * @return a directory
 1218  
      * @throws AnalysisException thrown if unable to create temporary directory
 1219  
      */
 1220  
     private File getNextTempDirectory() throws AnalysisException {
 1221  0
         dirCount += 1;
 1222  0
         final File directory = new File(tempFileLocation, String.valueOf(dirCount));
 1223  
         //getting an exception for some directories not being able to be created; might be because the directory already exists?
 1224  0
         if (directory.exists()) {
 1225  0
             return getNextTempDirectory();
 1226  
         }
 1227  0
         if (!directory.mkdirs()) {
 1228  0
             final String msg = String.format("Unable to create temp directory '%s'.", directory.getAbsolutePath());
 1229  0
             throw new AnalysisException(msg);
 1230  
         }
 1231  0
         return directory;
 1232  
     }
 1233  
 }