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