Coverage Report - org.owasp.dependencycheck.analyzer.JarAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
JarAnalyzer
80%
320/399
65%
163/248
6.103
JarAnalyzer$ClassNameInformation
80%
17/21
90%
9/10
6.103
 
 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  3
     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  3
     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  3
     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  3
     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  36
     public JarAnalyzer() {
 142  
         try {
 143  36
             final JAXBContext jaxbContext = JAXBContext.newInstance("org.owasp.dependencycheck.jaxb.pom.generated");
 144  36
             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  36
         }
 149  36
     }
 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  3
     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
 159  
     /**
 160  
      * The set of file extensions supported by this analyzer.
 161  
      */
 162  3
     private static final Set<String> EXTENSIONS = newHashSet("jar");
 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  17472
         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  3
         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  17463
         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  3
         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  36
             final ArrayList<ClassNameInformation> classNames = collectClassNames(dependency);
 216  36
             final String fileName = dependency.getFileName().toLowerCase();
 217  36
             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  36
             final boolean hasManifest = parseManifest(dependency, classNames);
 225  36
             final boolean hasPOM = analyzePOM(dependency, classNames);
 226  36
             final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
 227  36
             analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
 228  0
         } catch (IOException ex) {
 229  0
             throw new AnalysisException("Exception occurred reading the JAR file.", ex);
 230  36
         }
 231  36
     }
 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  36
         boolean foundSomething = false;
 246  
         final JarFile jar;
 247  
         try {
 248  36
             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  36
         }
 257  
         List<String> pomEntries;
 258  
         try {
 259  36
             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  36
         }
 268  36
         if (pomEntries.isEmpty()) {
 269  24
             return false;
 270  
         }
 271  12
         if (pomEntries.size() > 1) { //need to sort out which pom we will use
 272  0
             pomEntries = filterPomEntries(pomEntries, classes);
 273  
         }
 274  12
         for (String path : pomEntries) {
 275  12
             Properties pomProperties = null;
 276  
             try {
 277  12
                 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  12
             }
 281  12
             Model pom = null;
 282  
             try {
 283  12
                 pom = retrievePom(path, jar);
 284  0
             } catch (JAXBException ex) {
 285  0
                 final String msg = String.format("Unable to parse POM '%s' in '%s'",
 286  
                         path, dependency.getFilePath());
 287  0
                 final AnalysisException ax = new AnalysisException(msg, ex);
 288  0
                 dependency.getAnalysisExceptions().add(ax);
 289  0
                 Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINE, msg, ax);
 290  0
             } catch (IOException ex) {
 291  0
                 final String msg = String.format("Unable to retrieve POM '%s' in '%s'",
 292  
                         path, dependency.getFilePath());
 293  0
                 Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINE, msg, ex);
 294  12
             }
 295  12
             foundSomething = setPomEvidence(dependency, pom, pomProperties, classes) || foundSomething;
 296  12
         }
 297  12
         return foundSomething;
 298  
     }
 299  
 
 300  
     /**
 301  
      * Given a path to a pom.xml within a JarFile, this method attempts to load
 302  
      * a sibling pom.properties if one exists.
 303  
      *
 304  
      * @param path the path to the pom.xml within the JarFile
 305  
      * @param jar the JarFile to load the pom.properties from
 306  
      * @return a Properties object or null if no pom.properties was found
 307  
      * @throws IOException thrown if there is an exception reading the
 308  
      * pom.properties
 309  
      */
 310  
     @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "OS_OPEN_STREAM",
 311  
             justification = "The reader is closed by closing the zipEntry")
 312  
     private Properties retrievePomProperties(String path, final JarFile jar) throws IOException {
 313  12
         Properties pomProperties = null;
 314  12
         final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
 315  12
         final ZipEntry propEntry = jar.getEntry(propPath);
 316  12
         if (propEntry != null) {
 317  0
             final Reader reader = new InputStreamReader(jar.getInputStream(propEntry), "UTF-8");
 318  0
             pomProperties = new Properties();
 319  0
             pomProperties.load(reader);
 320  
         }
 321  12
         return pomProperties;
 322  
     }
 323  
 
 324  
     /**
 325  
      * Searches a JarFile for pom.xml entries and returns a listing of these
 326  
      * entries.
 327  
      *
 328  
      * @param jar the JarFile to search
 329  
      * @return a list of pom.xml entries
 330  
      * @throws IOException thrown if there is an exception reading a JarEntryf
 331  
      */
 332  
     private List<String> retrievePomListing(final JarFile jar) throws IOException {
 333  36
         final List<String> pomEntries = new ArrayList<String>();
 334  36
         final Enumeration<JarEntry> entries = jar.entries();
 335  12552
         while (entries.hasMoreElements()) {
 336  12516
             final JarEntry entry = entries.nextElement();
 337  12516
             final String entryName = (new File(entry.getName())).getName().toLowerCase();
 338  12516
             if (!entry.isDirectory() && "pom.xml".equals(entryName)) {
 339  12
                 pomEntries.add(entry.getName());
 340  
             }
 341  12516
         }
 342  36
         return pomEntries;
 343  
     }
 344  
 
 345  
     /**
 346  
      * Retrieves the specified POM from a jar file and converts it to a Model.
 347  
      *
 348  
      * @param path the path to the pom.xml file within the jar file
 349  
      * @param jar the jar file to extract the pom from
 350  
      * @return returns a
 351  
      * {@link org.owasp.dependencycheck.analyzer.pom.generated.Model} object
 352  
      * @throws JAXBException is thrown if there is an exception parsing the pom
 353  
      * @throws IOException is thrown if there is an exception reading the jar
 354  
      */
 355  
     private Model retrievePom(String path, JarFile jar) throws JAXBException, IOException {
 356  12
         final ZipEntry entry = jar.getEntry(path);
 357  12
         if (entry != null) { //should never be null
 358  12
             Model m = null;
 359  
             try {
 360  12
                 final XMLFilter filter = new MavenNamespaceFilter();
 361  12
                 final SAXParserFactory spf = SAXParserFactory.newInstance();
 362  12
                 final SAXParser sp = spf.newSAXParser();
 363  12
                 final XMLReader xr = sp.getXMLReader();
 364  12
                 filter.setParent(xr);
 365  12
                 final NonClosingStream stream = new NonClosingStream(jar.getInputStream(entry));
 366  12
                 final InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
 367  12
                 final InputSource xml = new InputSource(reader);
 368  12
                 final SAXSource source = new SAXSource(filter, xml);
 369  12
                 final JAXBElement<Model> el = pomUnmarshaller.unmarshal(source, Model.class);
 370  12
                 m = el.getValue();
 371  0
             } catch (ParserConfigurationException ex) {
 372  0
                 final String msg = String.format("Unable to parse pom '%s' in jar '%s'", path, jar.getName());
 373  0
                 Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINE, msg, ex);
 374  0
             } catch (SAXException ex) {
 375  0
                 final String msg = String.format("Unable to parse pom '%s' in jar '%s'", path, jar.getName());
 376  0
                 Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINE, msg, ex);
 377  12
             }
 378  12
             return m;
 379  
         }
 380  0
         return null;
 381  
     }
 382  
 
 383  
     /**
 384  
      * Sets evidence from the pom on the supplied dependency.
 385  
      *
 386  
      * @param dependency the dependency to set data on
 387  
      * @param pom the information from the pom
 388  
      * @param pomProperties the pom properties file (null if none exists)
 389  
      * @param classes a collection of ClassNameInformation - containing data
 390  
      * about the fully qualified class names within the JAR file being analyzed
 391  
      * @return true if there was evidence within the pom that we could use;
 392  
      * otherwise false
 393  
      */
 394  
     private boolean setPomEvidence(Dependency dependency, Model pom, Properties pomProperties, ArrayList<ClassNameInformation> classes) {
 395  12
         boolean foundSomething = false;
 396  12
         if (pom == null) {
 397  0
             return foundSomething;
 398  
         }
 399  12
         String groupid = interpolateString(pom.getGroupId(), pomProperties);
 400  12
         if (groupid != null && !groupid.isEmpty()) {
 401  9
             if (groupid.startsWith("org.") || groupid.startsWith("com.")) {
 402  6
                 groupid = groupid.substring(4);
 403  
             }
 404  9
             foundSomething = true;
 405  9
             dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Evidence.Confidence.HIGH);
 406  9
             dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Evidence.Confidence.LOW);
 407  9
             addMatchingValues(classes, groupid, dependency.getVendorEvidence());
 408  9
             addMatchingValues(classes, groupid, dependency.getProductEvidence());
 409  
         }
 410  12
         String artifactid = interpolateString(pom.getArtifactId(), pomProperties);
 411  12
         if (artifactid != null && !artifactid.isEmpty()) {
 412  12
             if (artifactid.startsWith("org.") || artifactid.startsWith("com.")) {
 413  0
                 artifactid = artifactid.substring(4);
 414  
             }
 415  12
             foundSomething = true;
 416  12
             dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Evidence.Confidence.HIGH);
 417  12
             dependency.getVendorEvidence().addEvidence("pom", "artifactid", artifactid, Evidence.Confidence.LOW);
 418  12
             addMatchingValues(classes, artifactid, dependency.getVendorEvidence());
 419  12
             addMatchingValues(classes, artifactid, dependency.getProductEvidence());
 420  
         }
 421  
         //version
 422  12
         final String version = interpolateString(pom.getVersion(), pomProperties);
 423  12
         if (version != null && !version.isEmpty()) {
 424  6
             foundSomething = true;
 425  6
             dependency.getVersionEvidence().addEvidence("pom", "version", version, Evidence.Confidence.HIGHEST);
 426  
         }
 427  
         // org name
 428  12
         final Organization org = pom.getOrganization();
 429  12
         if (org != null && org.getName() != null) {
 430  0
             foundSomething = true;
 431  0
             final String orgName = interpolateString(org.getName(), pomProperties);
 432  0
             if (orgName != null && !orgName.isEmpty()) {
 433  0
                 dependency.getVendorEvidence().addEvidence("pom", "organization name", orgName, Evidence.Confidence.HIGH);
 434  0
                 addMatchingValues(classes, orgName, dependency.getVendorEvidence());
 435  
             }
 436  
         }
 437  
         //pom name
 438  12
         final String pomName = interpolateString(pom.getName(), pomProperties);
 439  12
         if (pomName != null && !pomName.isEmpty()) {
 440  12
             foundSomething = true;
 441  12
             dependency.getProductEvidence().addEvidence("pom", "name", pomName, Evidence.Confidence.HIGH);
 442  12
             dependency.getVendorEvidence().addEvidence("pom", "name", pomName, Evidence.Confidence.HIGH);
 443  12
             addMatchingValues(classes, pomName, dependency.getVendorEvidence());
 444  12
             addMatchingValues(classes, pomName, dependency.getProductEvidence());
 445  
         }
 446  
 
 447  
         //Description
 448  12
         if (pom.getDescription() != null) {
 449  6
             foundSomething = true;
 450  6
             final String description = interpolateString(pom.getDescription(), pomProperties);
 451  6
             if (description != null && !description.isEmpty()) {
 452  6
                 addDescription(dependency, description, "pom", "description");
 453  6
                 addMatchingValues(classes, description, dependency.getVendorEvidence());
 454  6
                 addMatchingValues(classes, description, dependency.getProductEvidence());
 455  
             }
 456  
         }
 457  
 
 458  
         //license
 459  12
         if (pom.getLicenses() != null) {
 460  3
             String license = null;
 461  3
             for (License lic : pom.getLicenses().getLicense()) {
 462  3
                 String tmp = null;
 463  3
                 if (lic.getName() != null) {
 464  3
                     tmp = interpolateString(lic.getName(), pomProperties);
 465  
                 }
 466  3
                 if (lic.getUrl() != null) {
 467  3
                     if (tmp == null) {
 468  0
                         tmp = interpolateString(lic.getUrl(), pomProperties);
 469  
                     } else {
 470  3
                         tmp += ": " + interpolateString(lic.getUrl(), pomProperties);
 471  
                     }
 472  
                 }
 473  3
                 if (tmp == null) {
 474  0
                     continue;
 475  
                 }
 476  3
                 if (HTML_DETECTION_PATTERN.matcher(tmp).find()) {
 477  0
                     tmp = Jsoup.parse(tmp).text();
 478  
                 }
 479  3
                 if (license == null) {
 480  3
                     license = tmp;
 481  
                 } else {
 482  0
                     license += "\n" + tmp;
 483  
                 }
 484  3
             }
 485  3
             if (license != null) {
 486  3
                 dependency.setLicense(license);
 487  
             }
 488  
         }
 489  12
         return foundSomething;
 490  
     }
 491  
 
 492  
     /**
 493  
      * Analyzes the path information of the classes contained within the
 494  
      * JarAnalyzer to try and determine possible vendor or product names. If any
 495  
      * are found they are stored in the packageVendor and packageProduct
 496  
      * hashSets.
 497  
      *
 498  
      * @param classNames a list of class names
 499  
      * @param dependency a dependency to analyze
 500  
      * @param addPackagesAsEvidence a flag indicating whether or not package
 501  
      * names should be added as evidence.
 502  
      */
 503  
     protected void analyzePackageNames(ArrayList<ClassNameInformation> classNames,
 504  
             Dependency dependency, boolean addPackagesAsEvidence) {
 505  36
         final HashMap<String, Integer> vendorIdentifiers = new HashMap<String, Integer>();
 506  36
         final HashMap<String, Integer> productIdentifiers = new HashMap<String, Integer>();
 507  36
         analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers);
 508  
 
 509  36
         final int classCount = classNames.size();
 510  36
         final EvidenceCollection vendor = dependency.getVendorEvidence();
 511  36
         final EvidenceCollection product = dependency.getProductEvidence();
 512  
 
 513  36
         for (Map.Entry<String, Integer> entry : vendorIdentifiers.entrySet()) {
 514  333
             final float ratio = entry.getValue() / (float) classCount;
 515  333
             if (ratio > 0.5) {
 516  
                 //TODO remove weighting
 517  72
                 vendor.addWeighting(entry.getKey());
 518  72
                 if (addPackagesAsEvidence) {
 519  51
                     vendor.addEvidence("jar", "package", entry.getKey(), Evidence.Confidence.LOW);
 520  
                 }
 521  
             }
 522  333
         }
 523  36
         for (Map.Entry<String, Integer> entry : productIdentifiers.entrySet()) {
 524  5133
             final float ratio = entry.getValue() / (float) classCount;
 525  5133
             if (ratio > 0.5) {
 526  42
                 product.addWeighting(entry.getKey());
 527  42
                 if (addPackagesAsEvidence) {
 528  27
                     product.addEvidence("jar", "package", entry.getKey(), Evidence.Confidence.LOW);
 529  
                 }
 530  
             }
 531  5133
         }
 532  36
     }
 533  
 
 534  
     /**
 535  
      * <p>Reads the manifest from the JAR file and collects the entries. Some
 536  
      * vendorKey entries are:</p> <ul><li>Implementation Title</li>
 537  
      * <li>Implementation Version</li> <li>Implementation Vendor</li>
 538  
      * <li>Implementation VendorId</li> <li>Bundle Name</li> <li>Bundle
 539  
      * Version</li> <li>Bundle Vendor</li> <li>Bundle Description</li> <li>Main
 540  
      * Class</li> </ul>
 541  
      * However, all but a handful of specific entries are read in.
 542  
      *
 543  
      * @param dependency A reference to the dependency
 544  
      * @param classInformation a collection of class information
 545  
      * @return whether evidence was identified parsing the manifest
 546  
      * @throws IOException if there is an issue reading the JAR file
 547  
      */
 548  
     protected boolean parseManifest(Dependency dependency, ArrayList<ClassNameInformation> classInformation) throws IOException {
 549  36
         boolean foundSomething = false;
 550  36
         JarFile jar = null;
 551  
         try {
 552  36
             jar = new JarFile(dependency.getActualFilePath());
 553  
 
 554  36
             final Manifest manifest = jar.getManifest();
 555  36
             if (manifest == null) {
 556  
                 //don't log this for javadoc or sources jar files
 557  0
                 if (!dependency.getFileName().toLowerCase().endsWith("-sources.jar")
 558  
                         && !dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
 559  
                         && !dependency.getFileName().toLowerCase().endsWith("-src.jar")
 560  
                         && !dependency.getFileName().toLowerCase().endsWith("-doc.jar")) {
 561  0
                     Logger.getLogger(JarAnalyzer.class.getName()).log(Level.INFO,
 562  
                             String.format("Jar file '%s' does not contain a manifest.",
 563  
                             dependency.getFileName()));
 564  
                 }
 565  0
                 return false;
 566  
             }
 567  36
             final Attributes atts = manifest.getMainAttributes();
 568  
 
 569  36
             final EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
 570  36
             final EvidenceCollection productEvidence = dependency.getProductEvidence();
 571  36
             final EvidenceCollection versionEvidence = dependency.getVersionEvidence();
 572  
 
 573  36
             final String source = "Manifest";
 574  
 
 575  36
             for (Entry<Object, Object> entry : atts.entrySet()) {
 576  504
                 String key = entry.getKey().toString();
 577  504
                 String value = atts.getValue(key);
 578  504
                 if (HTML_DETECTION_PATTERN.matcher(value).find()) {
 579  0
                     value = Jsoup.parse(value).text();
 580  
                 }
 581  504
                 if (IGNORE_VALUES.contains(value)) {
 582  0
                     continue;
 583  504
                 } else if (key.equals(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
 584  18
                     foundSomething = true;
 585  18
                     productEvidence.addEvidence(source, key, value, Evidence.Confidence.HIGH);
 586  18
                     addMatchingValues(classInformation, value, productEvidence);
 587  486
                 } else if (key.equals(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
 588  24
                     foundSomething = true;
 589  24
                     versionEvidence.addEvidence(source, key, value, Evidence.Confidence.HIGH);
 590  462
                 } else if (key.equals(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
 591  12
                     foundSomething = true;
 592  12
                     vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.HIGH);
 593  12
                     addMatchingValues(classInformation, value, vendorEvidence);
 594  450
                 } else if (key.equals(Attributes.Name.IMPLEMENTATION_VENDOR_ID.toString())) {
 595  6
                     foundSomething = true;
 596  6
                     vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 597  6
                     addMatchingValues(classInformation, value, vendorEvidence);
 598  444
                 } else if (key.equals(BUNDLE_DESCRIPTION)) {
 599  15
                     foundSomething = true;
 600  15
                     addDescription(dependency, value, "manifest", key);
 601  
                     //productEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 602  15
                     addMatchingValues(classInformation, value, productEvidence);
 603  429
                 } else if (key.equals(BUNDLE_NAME)) {
 604  21
                     foundSomething = true;
 605  21
                     productEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 606  21
                     addMatchingValues(classInformation, value, productEvidence);
 607  408
                 } else if (key.equals(BUNDLE_VENDOR)) {
 608  15
                     foundSomething = true;
 609  15
                     vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.HIGH);
 610  15
                     addMatchingValues(classInformation, value, vendorEvidence);
 611  393
                 } else if (key.equals(BUNDLE_VERSION)) {
 612  21
                     foundSomething = true;
 613  21
                     versionEvidence.addEvidence(source, key, value, Evidence.Confidence.HIGH);
 614  372
                 } else if (key.equals(Attributes.Name.MAIN_CLASS.toString())) {
 615  9
                     continue;
 616  
                     //skipping main class as if this has important information to add
 617  
                     // it will be added during class name analysis...  if other fields
 618  
                     // have the information from the class name then they will get added...
 619  
 //                    foundSomething = true;
 620  
 //                    productEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 621  
 //                    vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 622  
 //                    addMatchingValues(classInformation, value, vendorEvidence);
 623  
 //                    addMatchingValues(classInformation, value, productEvidence);
 624  
                 } else {
 625  363
                     key = key.toLowerCase();
 626  
 
 627  363
                     if (!IGNORE_KEYS.contains(key)
 628  
                             && !key.endsWith("jdk")
 629  
                             && !key.contains("lastmodified")
 630  
                             && !key.endsWith("package")
 631  
                             && !key.endsWith("classpath")
 632  
                             && !key.endsWith("class-path")
 633  
                             && !key.endsWith("-scm") //todo change this to a regex?
 634  
                             && !key.startsWith("scm-")
 635  
                             && !isImportPackage(key, value)
 636  
                             && !isPackage(key, value)) {
 637  
 
 638  108
                         foundSomething = true;
 639  108
                         if (key.contains("version")) {
 640  18
                             versionEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 641  90
                         } else if (key.contains("title")) {
 642  12
                             productEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 643  12
                             addMatchingValues(classInformation, value, productEvidence);
 644  78
                         } else if (key.contains("vendor")) {
 645  6
                             vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 646  6
                             addMatchingValues(classInformation, value, vendorEvidence);
 647  72
                         } else if (key.contains("name")) {
 648  24
                             productEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 649  24
                             vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
 650  24
                             addMatchingValues(classInformation, value, vendorEvidence);
 651  24
                             addMatchingValues(classInformation, value, productEvidence);
 652  48
                         } else if (key.contains("license")) {
 653  9
                             addLicense(dependency, value);
 654  
                         } else {
 655  39
                             if (key.contains("description")) {
 656  0
                                 addDescription(dependency, value, "manifest", key);
 657  
                             } else {
 658  39
                                 productEvidence.addEvidence(source, key, value, Evidence.Confidence.LOW);
 659  39
                                 vendorEvidence.addEvidence(source, key, value, Evidence.Confidence.LOW);
 660  39
                                 addMatchingValues(classInformation, value, vendorEvidence);
 661  39
                                 addMatchingValues(classInformation, value, productEvidence);
 662  39
                                 if (value.matches(".*\\d.*")) {
 663  18
                                     final StringTokenizer tokenizer = new StringTokenizer(value, " ");
 664  90
                                     while (tokenizer.hasMoreElements()) {
 665  72
                                         final String s = tokenizer.nextToken();
 666  72
                                         if (s.matches("^[0-9.]+$")) {
 667  0
                                             versionEvidence.addEvidence(source, key, s, Evidence.Confidence.LOW);
 668  
                                         }
 669  72
                                     }
 670  
                                 }
 671  
                             }
 672  
                         }
 673  
                     }
 674  
                 }
 675  495
             }
 676  
         } finally {
 677  36
             if (jar != null) {
 678  36
                 jar.close();
 679  
             }
 680  
         }
 681  36
         return foundSomething;
 682  
     }
 683  
 
 684  
     /**
 685  
      * Adds a description to the given dependency.
 686  
      *
 687  
      * @param dependency a dependency
 688  
      * @param description the description
 689  
      * @param source the source of the evidence
 690  
      * @param key the "name" of the evidence
 691  
      */
 692  
     private void addDescription(Dependency dependency, String description, String source, String key) {
 693  21
         if (dependency.getDescription() == null) {
 694  18
             dependency.setDescription(description);
 695  
         }
 696  
         String desc;
 697  21
         if (HTML_DETECTION_PATTERN.matcher(description).find()) {
 698  0
             desc = Jsoup.parse(description).text();
 699  
         } else {
 700  21
             desc = description;
 701  
         }
 702  21
         dependency.setDescription(desc);
 703  21
         if (desc.length() > 100) {
 704  6
             final int posSuchAs = desc.toLowerCase().indexOf("such as ", 100);
 705  6
             final int posLike = desc.toLowerCase().indexOf("like ", 100);
 706  6
             int pos = -1;
 707  6
             if (posLike > 0 && posSuchAs > 0) {
 708  0
                 pos = posLike > posSuchAs ? posLike : posSuchAs;
 709  6
             } else if (posLike > 0) {
 710  6
                 pos = posLike;
 711  0
             } else if (posSuchAs > 0) {
 712  0
                 pos = posSuchAs;
 713  
             }
 714  6
             String descToUse = desc;
 715  6
             if (pos > 0) {
 716  6
                 final StringBuilder sb = new StringBuilder(pos + 3);
 717  6
                 sb.append(desc.substring(0, pos));
 718  6
                 sb.append("...");
 719  6
                 descToUse = sb.toString();
 720  
             }
 721  6
             dependency.getProductEvidence().addEvidence(source, key, descToUse, Evidence.Confidence.LOW);
 722  6
             dependency.getVendorEvidence().addEvidence(source, key, descToUse, Evidence.Confidence.LOW);
 723  6
         } else {
 724  15
             dependency.getProductEvidence().addEvidence(source, key, desc, Evidence.Confidence.MEDIUM);
 725  15
             dependency.getVendorEvidence().addEvidence(source, key, desc, Evidence.Confidence.MEDIUM);
 726  
         }
 727  21
     }
 728  
 
 729  
     /**
 730  
      * Adds a license to the given dependency.
 731  
      *
 732  
      * @param d a dependency
 733  
      * @param license the license
 734  
      */
 735  
     private void addLicense(Dependency d, String license) {
 736  9
         if (d.getLicense() == null) {
 737  9
             d.setLicense(license);
 738  0
         } else if (!d.getLicense().contains(license)) {
 739  0
             d.setLicense(d.getLicense() + NEWLINE + license);
 740  
         }
 741  9
     }
 742  
 
 743  
     /**
 744  
      * The initialize method does nothing for this Analyzer.
 745  
      */
 746  
     public void initialize() {
 747  
         //do nothing
 748  0
     }
 749  
 
 750  
     /**
 751  
      * The close method does nothing for this Analyzer.
 752  
      */
 753  
     public void close() {
 754  
         //do nothing
 755  0
     }
 756  
 
 757  
     /**
 758  
      * <p>A utility function that will interpolate strings based on values given
 759  
      * in the properties file. It will also interpolate the strings contained
 760  
      * within the properties file so that properties can reference other
 761  
      * properties.</p>
 762  
      * <p><b>Note:</b> if there is no property found the reference will be
 763  
      * removed. In other words, if the interpolated string will be replaced with
 764  
      * an empty string.
 765  
      * </p>
 766  
      * <p>Example:</p>
 767  
      * <code>
 768  
      * Properties p = new Properties();
 769  
      * p.setProperty("key", "value");
 770  
      * String s = interpolateString("'${key}' and '${nothing}'", p);
 771  
      * System.out.println(s);
 772  
      * </code>
 773  
      * <p>Will result in:</p>
 774  
      * <code>
 775  
      * 'value' and ''
 776  
      * </code>
 777  
      *
 778  
      * @param text the string that contains references to properties.
 779  
      * @param properties a collection of properties that may be referenced
 780  
      * within the text.
 781  
      * @return the interpolated text.
 782  
      */
 783  
     protected String interpolateString(String text, Properties properties) {
 784  81
         Properties props = properties;
 785  81
         if (text == null) {
 786  9
             return text;
 787  
         }
 788  72
         if (props == null) {
 789  51
             props = new Properties();
 790  
         }
 791  
 
 792  72
         final int pos = text.indexOf("${");
 793  72
         if (pos < 0) {
 794  63
             return text;
 795  
         }
 796  9
         final int end = text.indexOf("}");
 797  9
         if (end < pos) {
 798  0
             return text;
 799  
         }
 800  
 
 801  9
         final String propName = text.substring(pos + 2, end);
 802  9
         String propValue = interpolateString(props.getProperty(propName), props);
 803  9
         if (propValue == null) {
 804  0
             propValue = "";
 805  
         }
 806  9
         final StringBuilder sb = new StringBuilder(propValue.length() + text.length());
 807  9
         sb.append(text.subSequence(0, pos));
 808  9
         sb.append(propValue);
 809  9
         sb.append(text.substring(end + 1));
 810  9
         return interpolateString(sb.toString(), props); //yes yes, this should be a loop...
 811  
     }
 812  
 
 813  
     /**
 814  
      * Determines if the key value pair from the manifest is for an "import"
 815  
      * type entry for package names.
 816  
      *
 817  
      * @param key the key from the manifest
 818  
      * @param value the value from the manifest
 819  
      * @return true or false depending on if it is believed the entry is an
 820  
      * "import" entry
 821  
      */
 822  
     private boolean isImportPackage(String key, String value) {
 823  114
         final Pattern packageRx = Pattern.compile("^((([a-zA-Z_#\\$0-9]\\.)+)\\s*\\;\\s*)+$");
 824  114
         if (packageRx.matcher(value).matches()) {
 825  0
             return (key.contains("import") || key.contains("include"));
 826  
         }
 827  114
         return false;
 828  
     }
 829  
 
 830  
     /**
 831  
      * Cycles through an enumeration of JarEntries, contained within the
 832  
      * dependency, and returns a list of the class names. This does not include
 833  
      * core Java package names (i.e. java.* or javax.*).
 834  
      *
 835  
      * @param dependency the dependency being analyzed
 836  
      * @return an list of fully qualified class names
 837  
      */
 838  
     private ArrayList<ClassNameInformation> collectClassNames(Dependency dependency) {
 839  36
         final ArrayList<ClassNameInformation> classNames = new ArrayList<ClassNameInformation>();
 840  36
         JarFile jar = null;
 841  
         try {
 842  36
             jar = new JarFile(dependency.getActualFilePath());
 843  36
             final Enumeration entries = jar.entries();
 844  12552
             while (entries.hasMoreElements()) {
 845  12516
                 final JarEntry entry = (JarEntry) entries.nextElement();
 846  12516
                 final String name = entry.getName().toLowerCase();
 847  
                 //no longer stripping "|com\\.sun" - there are some com.sun jar files with CVEs.
 848  12516
                 if (name.endsWith(".class") && !name.matches("^javax?\\..*$")) {
 849  10350
                     final ClassNameInformation className = new ClassNameInformation(name.substring(0, name.length() - 6));
 850  10350
                     classNames.add(className);
 851  
                 }
 852  12516
             }
 853  0
         } catch (IOException ex) {
 854  0
             final String msg = String.format("Unable to open jar file '%s'.", dependency.getFileName());
 855  0
             Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg);
 856  0
             Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINE, null, ex);
 857  
         } finally {
 858  36
             if (jar != null) {
 859  
                 try {
 860  36
                     jar.close();
 861  0
                 } catch (IOException ex) {
 862  0
                     Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINEST, null, ex);
 863  36
                 }
 864  
             }
 865  
         }
 866  36
         return classNames;
 867  
     }
 868  
 
 869  
     /**
 870  
      * Cycles through the list of class names and places the package levels 0-3
 871  
      * into the provided maps for vendor and product. This is helpful when
 872  
      * analyzing vendor/product as many times this is included in the package
 873  
      * name.
 874  
      *
 875  
      * @param classNames a list of class names
 876  
      * @param vendor HashMap of possible vendor names from package names (e.g.
 877  
      * owasp)
 878  
      * @param product HashMap of possible product names from package names (e.g.
 879  
      * dependencycheck)
 880  
      */
 881  
     private void analyzeFullyQualifiedClassNames(ArrayList<ClassNameInformation> classNames,
 882  
             HashMap<String, Integer> vendor, HashMap<String, Integer> product) {
 883  36
         for (ClassNameInformation entry : classNames) {
 884  10350
             final ArrayList<String> list = entry.getPackageStructure();
 885  10350
             addEntry(vendor, list.get(0));
 886  
 
 887  10350
             if (list.size() == 2) {
 888  0
                 addEntry(product, list.get(1));
 889  
             }
 890  10350
             if (list.size() == 3) {
 891  2226
                 addEntry(vendor, list.get(1));
 892  2226
                 addEntry(product, list.get(1));
 893  2226
                 addEntry(product, list.get(2));
 894  
             }
 895  10350
             if (list.size() >= 4) {
 896  8124
                 addEntry(vendor, list.get(1));
 897  8124
                 addEntry(vendor, list.get(2));
 898  8124
                 addEntry(product, list.get(1));
 899  8124
                 addEntry(product, list.get(2));
 900  8124
                 addEntry(product, list.get(3));
 901  
             }
 902  10350
         }
 903  36
     }
 904  
 
 905  
     /**
 906  
      * Adds an entry to the specified collection and sets the Integer (e.g. the
 907  
      * count) to 1. If the entry already exists in the collection then the
 908  
      * Integer is incremented by 1.
 909  
      *
 910  
      * @param collection a collection of strings and their occurrence count
 911  
      * @param key the key to add to the collection
 912  
      */
 913  
     private void addEntry(HashMap<String, Integer> collection, String key) {
 914  57648
         if (collection.containsKey(key)) {
 915  52182
             collection.put(key, collection.get(key) + 1);
 916  
         } else {
 917  5466
             collection.put(key, 1);
 918  
         }
 919  57648
     }
 920  
 
 921  
     /**
 922  
      * Cycles through the collection of class name information to see if parts
 923  
      * of the package names are contained in the provided value. If found, it
 924  
      * will be added as the HIGHEST confidence evidence because we have more
 925  
      * then one source corroborating the value.
 926  
      *
 927  
      * @param classes a collection of class name information
 928  
      * @param value the value to check to see if it contains a package name
 929  
      * @param evidence the evidence collection to add new entries too
 930  
      */
 931  
     private void addMatchingValues(ArrayList<ClassNameInformation> classes, String value, EvidenceCollection evidence) {
 932  309
         if (value == null || value.isEmpty()) {
 933  0
             return;
 934  
         }
 935  309
         final String text = value.toLowerCase();
 936  309
         for (ClassNameInformation cni : classes) {
 937  91014
             for (String key : cni.getPackageStructure()) {
 938  350844
                 if (text.contains(key)) { //note, package structure elements are already lowercase.
 939  109401
                     evidence.addEvidence("jar", "package name", key, Evidence.Confidence.HIGHEST);
 940  
                 }
 941  
             }
 942  
         }
 943  309
     }
 944  
 
 945  
     /**
 946  
      * <p><b>This is currently a failed implementation.</b> Part of the issue is
 947  
      * I was trying to solve the wrong problem. Instead of multiple POMs being
 948  
      * in the JAR to just add information about dependencies - I didn't realize
 949  
      * until later that I was looking at an uber-jar (aka fat-jar) that included
 950  
      * all of its dependencies.</p>
 951  
      * <p>I'm leaving this method in the source tree, entirely commented out
 952  
      * until a solution https://github.com/jeremylong/DependencyCheck/issues/11
 953  
      * has been implemented.</p>
 954  
      * <p>Takes a list of pom entries from a JAR file and attempts to filter it
 955  
      * down to the pom related to the jar (rather then the pom entry for a
 956  
      * dependency).</p>
 957  
      *
 958  
      * @param pomEntries a list of pom entries
 959  
      * @param classes a list of fully qualified classes from the JAR file
 960  
      * @return the list of pom entries that are associated with the jar being
 961  
      * analyzed rather then the dependent poms
 962  
      */
 963  
     private List<String> filterPomEntries(List<String> pomEntries, ArrayList<ClassNameInformation> classes) {
 964  0
         return pomEntries;
 965  
 //        final HashMap<String, Integer> usePoms = new HashMap<String, Integer>();
 966  
 //        final ArrayList<String> possiblePoms = new ArrayList<String>();
 967  
 //        for (String entry : pomEntries) {
 968  
 //            //todo validate that the starts with is correct... or does it start with a ./ or /?
 969  
 //            // is it different on different platforms?
 970  
 //            if (entry.startsWith("META-INF/maven/")) {
 971  
 //                //trim the meta-inf/maven and pom.xml...
 972  
 //                final String pomPath = entry.substring(15, entry.length() - 8).toLowerCase();
 973  
 //                final String[] parts = pomPath.split("/");
 974  
 //                if (parts == null || parts.length != 2) { //misplaced pom?
 975  
 //                    //TODO add logging to FINE
 976  
 //                    possiblePoms.add(entry);
 977  
 //                }
 978  
 //                parts[0] = parts[0].replace('.', '/');
 979  
 //                parts[1] = parts[1].replace('.', '/');
 980  
 //                for (ClassNameInformation cni : classes) {
 981  
 //                    final String name = cni.getName();
 982  
 //                    if (StringUtils.containsIgnoreCase(name, parts[0])) {
 983  
 //                        addEntry(usePoms, entry);
 984  
 //                    }
 985  
 //                    if (StringUtils.containsIgnoreCase(name, parts[1])) {
 986  
 //                        addEntry(usePoms, entry);
 987  
 //                    }
 988  
 //                }
 989  
 //            } else { // we have a JAR file with an incorrect POM layout...
 990  
 //                //TODO add logging to FINE
 991  
 //                possiblePoms.add(entry);
 992  
 //            }
 993  
 //        }
 994  
 //        List<String> retValue;
 995  
 //        if (usePoms.isEmpty()) {
 996  
 //            if (possiblePoms.isEmpty()) {
 997  
 //                retValue = pomEntries;
 998  
 //            } else {
 999  
 //                retValue = possiblePoms;
 1000  
 //            }
 1001  
 //        } else {
 1002  
 //            retValue = new ArrayList<String>();
 1003  
 //            int maxCount = 0;
 1004  
 //            for (Map.Entry<String, Integer> entry : usePoms.entrySet()) {
 1005  
 //                final int current = entry.getValue().intValue();
 1006  
 //                if (current > maxCount) {
 1007  
 //                    maxCount = current;
 1008  
 //                    retValue.clear();
 1009  
 //                    retValue.add(entry.getKey());
 1010  
 //                } else if (current == maxCount) {
 1011  
 //                    retValue.add(entry.getKey());
 1012  
 //                }
 1013  
 //            }
 1014  
 //        }
 1015  
 //        return retValue;
 1016  
     }
 1017  
 
 1018  
     /**
 1019  
      * Simple check to see if the attribute from a manifest is just a package
 1020  
      * name.
 1021  
      *
 1022  
      * @param key the key of the value to check
 1023  
      * @param value the value to check
 1024  
      * @return true if the value looks like a java package name, otherwise false
 1025  
      */
 1026  
     private boolean isPackage(String key, String value) {
 1027  
 
 1028  114
         return !key.matches(".*(version|title|vendor|name|license|description).*")
 1029  
                 && value.matches("^([a-zA-Z_][a-zA-Z0-9_\\$]*(\\.[a-zA-Z_][a-zA-Z0-9_\\$]*)*)?$");
 1030  
     }
 1031  
 
 1032  
     /**
 1033  
      * Stores information about a class name.
 1034  
      */
 1035  
     protected static class ClassNameInformation {
 1036  
 
 1037  
         /**
 1038  
          * Stores information about a given class name. This class will keep the
 1039  
          * fully qualified class name and a list of the important parts of the
 1040  
          * package structure. Up to the first four levels of the package
 1041  
          * structure are stored, excluding a leading "org" or "com". Example:
 1042  
          * <code>ClassNameInformation obj = new ClassNameInformation("org.owasp.dependencycheck.analyzer.JarAnalyzer");
 1043  
          * System.out.println(obj.getName());
 1044  
          * for (String p : obj.getPackageStructure())
 1045  
          *     System.out.println(p);
 1046  
          * </code> Would result in:
 1047  
          * <code>org.owasp.dependencycheck.analyzer.JarAnalyzer
 1048  
          * owasp
 1049  
          * dependencycheck
 1050  
          * analyzer
 1051  
          * jaranalyzer</code>
 1052  
          *
 1053  
          * @param className a fully qualified class name
 1054  
          */
 1055  10350
         ClassNameInformation(String className) {
 1056  10350
             name = className;
 1057  10350
             if (name.contains("/")) {
 1058  10350
                 final String[] tmp = className.toLowerCase().split("/");
 1059  10350
                 int start = 0;
 1060  10350
                 int end = 3;
 1061  10350
                 if ("com".equals(tmp[0]) || "org".equals(tmp[0])) {
 1062  9069
                     start = 1;
 1063  9069
                     end = 4;
 1064  
                 }
 1065  10350
                 if (tmp.length <= end) {
 1066  2226
                     end = tmp.length - 1;
 1067  
                 }
 1068  49524
                 for (int i = start; i <= end; i++) {
 1069  39174
                     packageStructure.add(tmp[i]);
 1070  
                 }
 1071  10350
             } else {
 1072  0
                 packageStructure.add(name);
 1073  
             }
 1074  10350
         }
 1075  
         /**
 1076  
          * The fully qualified class name.
 1077  
          */
 1078  
         private String name;
 1079  
 
 1080  
         /**
 1081  
          * Get the value of name
 1082  
          *
 1083  
          * @return the value of name
 1084  
          */
 1085  
         public String getName() {
 1086  0
             return name;
 1087  
         }
 1088  
 
 1089  
         /**
 1090  
          * Set the value of name
 1091  
          *
 1092  
          * @param name new value of name
 1093  
          */
 1094  
         public void setName(String name) {
 1095  0
             this.name = name;
 1096  0
         }
 1097  
         /**
 1098  
          * Up to the first four levels of the package structure, excluding a
 1099  
          * leading "org" or "com".
 1100  
          */
 1101  10350
         private ArrayList<String> packageStructure = new ArrayList<String>();
 1102  
 
 1103  
         /**
 1104  
          * Get the value of packageStructure
 1105  
          *
 1106  
          * @return the value of packageStructure
 1107  
          */
 1108  
         public ArrayList<String> getPackageStructure() {
 1109  101364
             return packageStructure;
 1110  
         }
 1111  
     }
 1112  
 }