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