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