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