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