org.owasp.dependencycheck.analyzer.JarAnalyzer Maven / Gradle / Ivy
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;
import com.github.packageurl.PackageURLBuilder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceType;
import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
import org.owasp.dependencycheck.dependency.naming.Identifier;
import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.FileUtils;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.xml.pom.License;
import org.owasp.dependencycheck.xml.pom.Model;
import org.owasp.dependencycheck.xml.pom.PomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to load a JAR file and collect information that can be used to determine
* the associated CPE.
*
* @author Jeremy Long
*/
public class JarAnalyzer extends AbstractFileTypeAnalyzer {
//
/**
* A descriptor for the type of dependencies processed or added by this
* analyzer.
*/
public static final String DEPENDENCY_ECOSYSTEM = "Java";
/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(JarAnalyzer.class);
/**
* The count of directories created during analysis. This is used for
* creating temporary directories.
*/
private static final AtomicInteger DIR_COUNT = new AtomicInteger(0);
/**
* The system independent newline character.
*/
private static final String NEWLINE = System.getProperty("line.separator");
/**
* A list of values in the manifest to ignore as they only result in false
* positives.
*/
private static final Set IGNORE_VALUES = newHashSet(
"Sun Java System Application Server");
/**
* A list of elements in the manifest to ignore.
*/
private static final Set IGNORE_KEYS = newHashSet(
"built-by",
"created-by",
"builtby",
"createdby",
"build-jdk",
"buildjdk",
"ant-version",
"antversion",
"dynamicimportpackage",
"dynamicimport-package",
"dynamic-importpackage",
"dynamic-import-package",
"import-package",
"ignore-package",
"export-package",
"importpackage",
"import-template",
"importtemplate",
"export-template",
"exporttemplate",
"ignorepackage",
"exportpackage",
"sealed",
"manifest-version",
"archiver-version",
"manifestversion",
"archiverversion",
"classpath",
"class-path",
"tool",
"bundle-manifestversion",
"bundlemanifestversion",
"bundle-vendor",
"include-resource",
"embed-dependency",
"ipojo-components",
"ipojo-extension",
"plugin-dependencies",
"eclipse-sourcereferences");
/**
* Deprecated Jar manifest attribute, that is, nonetheless, useful for
* analysis.
*/
@SuppressWarnings("deprecation")
private static final String IMPLEMENTATION_VENDOR_ID = Attributes.Name.IMPLEMENTATION_VENDOR_ID
.toString();
/**
* item in some manifest, should be considered medium confidence.
*/
private static final String BUNDLE_VERSION = "Bundle-Version"; //: 2.1.2
/**
* item in some manifest, should be considered medium confidence.
*/
private static final String BUNDLE_DESCRIPTION = "Bundle-Description"; //: Apache Struts 2
/**
* item in some manifest, should be considered medium confidence.
*/
private static final String BUNDLE_NAME = "Bundle-Name"; //: Struts 2 Core
/**
* A pattern to detect HTML within text.
*/
private static final Pattern HTML_DETECTION_PATTERN = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE);
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "Jar Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
/**
* The set of file extensions supported by this analyzer.
*/
private static final String[] EXTENSIONS = {"jar", "war", "aar"};
/**
* The file filter used to determine which files this analyzer supports.
*/
private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build();
/**
* The expected first bytes when reading a zip file.
*/
private static final byte[] ZIP_FIRST_BYTES = new byte[]{0x50, 0x4B, 0x03, 0x04};
/**
* The expected first bytes when reading an empty zip file.
*/
private static final byte[] ZIP_EMPTY_FIRST_BYTES = new byte[]{0x50, 0x4B, 0x05, 0x06};
/**
* The expected first bytes when reading a spanned zip file.
*/
private static final byte[] ZIP_SPANNED_FIRST_BYTES = new byte[]{0x50, 0x4B, 0x07, 0x08};
//
/**
* The parent directory for the individual directories per archive.
*/
private File tempFileLocation = null;
//
/**
* Returns the FileFilter.
*
* @return the FileFilter
*/
@Override
protected FileFilter getFileFilter() {
return FILTER;
}
/**
* Returns the name of the analyzer.
*
* @return the name of the analyzer.
*/
@Override
public String getName() {
return ANALYZER_NAME;
}
/**
* Returns the phase that the analyzer is intended to run in.
*
* @return the phase that the analyzer is intended to run in.
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
//
/**
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_JAR_ENABLED;
}
/**
* Loads a specified JAR file and collects information from the manifest and
* checksums to identify the correct CPE information.
*
* @param dependency the dependency to analyze.
* @param engine the engine that is scanning the dependencies
* @throws AnalysisException is thrown if there is an error reading the JAR
* file.
*/
@Override
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
final List classNames = collectClassNames(dependency);
final String fileName = dependency.getFileName().toLowerCase();
if ((classNames.isEmpty()
&& (fileName.endsWith("-sources.jar")
|| fileName.endsWith("-javadoc.jar")
|| fileName.endsWith("-src.jar")
|| fileName.endsWith("-doc.jar")
|| isMacOSMetaDataFile(dependency, engine)))
|| !isZipFile(dependency)) {
engine.removeDependency(dependency);
return;
}
Exception exception = null;
boolean hasManifest = false;
try {
hasManifest = parseManifest(dependency, classNames);
} catch (IOException ex) {
LOGGER.debug("Invalid Manifest", ex);
exception = ex;
}
boolean hasPOM = false;
try {
hasPOM = analyzePOM(dependency, classNames, engine);
} catch (AnalysisException ex) {
LOGGER.debug("Error parsing pom.xml", ex);
exception = ex;
}
final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
if (exception != null) {
throw new AnalysisException(String.format("A error occurred extracing evidence from "
+ "%s, analysis may be incomplete; please see the log for more details.",
dependency.getDisplayFileName()), exception);
}
}
/**
* Checks if the given dependency appears to be a macOS meta-data file,
* returning true if its filename starts with a ._ prefix and if there is
* another dependency with the same filename minus the ._ prefix, otherwise
* it returns false.
*
* @param dependency the dependency to check if it's a macOS meta-data file
* @param engine the engine that is scanning the dependencies
* @return whether or not the given dependency appears to be a macOS
* meta-data file
*/
@SuppressFBWarnings(justification = "If actual file path is not null the path will have elements and getFileName will not be called on a null",
value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"})
private boolean isMacOSMetaDataFile(final Dependency dependency, final Engine engine) {
if (dependency.getActualFilePath() != null) {
final String fileName = Paths.get(dependency.getActualFilePath()).getFileName().toString();
return fileName.startsWith("._") && hasDependencyWithFilename(engine.getDependencies(), fileName.substring(2));
}
return false;
}
/**
* Iterates through the given list of dependencies and returns true when it
* finds a dependency with a filename matching the given filename, otherwise
* returns false.
*
* @param dependencies the dependencies to search within
* @param fileName the filename to search for
* @return whether or not the given dependencies contain a dependency with
* the given filename
*/
@SuppressFBWarnings(justification = "If actual file path is not null the path will have elements and getFileName will not be called on a null",
value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"})
private boolean hasDependencyWithFilename(final Dependency[] dependencies, final String fileName) {
for (final Dependency dependency : dependencies) {
if (dependency.getActualFilePath() != null
&& Paths.get(dependency.getActualFilePath()).getFileName().toString().equalsIgnoreCase(fileName)) {
return true;
}
}
return false;
}
/**
* Attempts to read the first bytes of the given dependency (using its
* actual file path) and returns true if they match the expected first bytes
* of a zip file, which may be empty or spanned. If they don't match, or if
* the file could not be read, then it returns false.
*
* @param dependency the dependency to check if it's a zip file
* @return whether or not the given dependency appears to be a zip file from
* its first bytes
*/
@SuppressFBWarnings(justification = "try with resources will clean up the output stream", value = {"OBL_UNSATISFIED_OBLIGATION"})
private boolean isZipFile(final Dependency dependency) {
final byte[] buffer = new byte[4];
try (final FileInputStream fileInputStream = new FileInputStream(dependency.getActualFilePath())) {
if (fileInputStream.read(buffer) > 0
&& (Arrays.equals(buffer, ZIP_FIRST_BYTES)
|| Arrays.equals(buffer, ZIP_EMPTY_FIRST_BYTES)
|| Arrays.equals(buffer, ZIP_SPANNED_FIRST_BYTES))) {
return true;
}
} catch (Exception e) {
LOGGER.warn("Unable to check if '{}' is a zip file", dependency.getActualFilePath());
LOGGER.trace("", e);
}
return false;
}
/**
* Attempts to find a pom.xml within the JAR file. If found it extracts
* information and adds it to the evidence. This will attempt to interpolate
* the strings contained within the pom.properties if one exists.
*
* @param dependency the dependency being analyzed
* @param classes a collection of class name information
* @param engine the analysis engine, used to add additional dependencies
* @throws AnalysisException is thrown if there is an exception parsing the
* pom
* @return whether or not evidence was added to the dependency
*/
protected boolean analyzePOM(Dependency dependency, List classes, Engine engine) throws AnalysisException {
//TODO add breakpoint on groov-all to find out why commons-cli is not added as a new dependency?
boolean evidenceAdded = false;
try (JarFile jar = new JarFile(dependency.getActualFilePath())) {
//check if we are scanning in a repo directory - so the pom is adjacent to the jar
final String repoPomName = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
final File repoPom = new File(repoPomName);
if (repoPom.isFile()) {
final Model pom = PomUtils.readPom(repoPom);
evidenceAdded |= setPomEvidence(dependency, pom, classes, true);
}
final List pomEntries = retrievePomListing(jar);
for (String path : pomEntries) {
LOGGER.debug("Reading pom entry: {}", path);
try {
//extract POM to its own directory and add it as its own dependency
final Properties pomProperties = retrievePomProperties(path, jar);
final File pomFile = extractPom(path, jar);
final Model pom = PomUtils.readPom(pomFile);
pom.processProperties(pomProperties);
final String artifactId = new File(path).getParentFile().getName();
if (dependency.getActualFile().getName().startsWith(artifactId)) {
evidenceAdded |= setPomEvidence(dependency, pom, classes, true);
} else {
final String displayPath = String.format("%s%s%s",
dependency.getFilePath(),
File.separator,
path);
final String displayName = String.format("%s%s%s",
dependency.getFileName(),
File.separator,
path);
final Dependency newDependency = new Dependency();
newDependency.setActualFilePath(pomFile.getAbsolutePath());
newDependency.setFileName(displayName);
newDependency.setFilePath(displayPath);
newDependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
String groupId = pom.getGroupId();
String version = pom.getVersion();
if (groupId == null) {
groupId = pom.getParentGroupId();
}
if (version == null) {
version = pom.getParentVersion();
}
if (groupId == null) {
newDependency.setName(pom.getArtifactId());
newDependency.setPackagePath(String.format("%s:%s", pom.getArtifactId(), version));
} else {
newDependency.setName(String.format("%s:%s", groupId, pom.getArtifactId()));
newDependency.setPackagePath(String.format("%s:%s:%s", groupId, pom.getArtifactId(), version));
}
newDependency.setDisplayFileName(String.format("%s (shaded: %s)",
dependency.getDisplayFileName(), newDependency.getPackagePath()));
newDependency.setVersion(version);
setPomEvidence(newDependency, pom, null, true);
if (dependency.getProjectReferences().size() > 0) {
newDependency.addAllProjectReferences(dependency.getProjectReferences());
}
engine.addDependency(newDependency);
}
} catch (AnalysisException ex) {
LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath());
LOGGER.trace("", ex);
}
}
} catch (IOException ex) {
LOGGER.warn("Unable to read JarFile '{}'.", dependency.getActualFilePath());
LOGGER.trace("", ex);
}
return evidenceAdded;
}
/**
* Given a path to a pom.xml within a JarFile, this method attempts to load
* a sibling pom.properties if one exists.
*
* @param path the path to the pom.xml within the JarFile
* @param jar the JarFile to load the pom.properties from
* @return a Properties object or null if no pom.properties was found
*/
private Properties retrievePomProperties(String path, final JarFile jar) {
Properties pomProperties = null;
final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
final ZipEntry propEntry = jar.getEntry(propPath);
if (propEntry != null) {
try (Reader reader = new InputStreamReader(jar.getInputStream(propEntry), StandardCharsets.UTF_8)) {
pomProperties = new Properties();
pomProperties.load(reader);
LOGGER.debug("Read pom.properties: {}", propPath);
} catch (UnsupportedEncodingException ex) {
LOGGER.trace("UTF-8 is not supported", ex);
} catch (IOException ex) {
LOGGER.trace("Unable to read the POM properties", ex);
}
}
return pomProperties;
}
/**
* Searches a JarFile for pom.xml entries and returns a listing of these
* entries.
*
* @param jar the JarFile to search
* @return a list of pom.xml entries
* @throws IOException thrown if there is an exception reading a JarEntry
*/
private List retrievePomListing(final JarFile jar) throws IOException {
final List pomEntries = new ArrayList<>();
final Enumeration entries = jar.entries();
while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();
final String entryName = new File(entry.getName()).getName().toLowerCase();
if (!entry.isDirectory() && "pom.xml".equals(entryName)
&& entry.getName().toUpperCase().startsWith("META-INF")) {
pomEntries.add(entry.getName());
}
}
return pomEntries;
}
/**
* Retrieves the specified POM from a jar.
*
* @param path the path to the pom.xml file within the jar file
* @param jar the jar file to extract the pom from
* @return returns the POM file
* @throws AnalysisException is thrown if there is an exception extracting
* the file
*/
private File extractPom(String path, JarFile jar) throws AnalysisException {
final File tmpDir = getNextTempDirectory();
final File file = new File(tmpDir, "pom.xml");
final ZipEntry entry = jar.getEntry(path);
if (entry == null) {
throw new AnalysisException(String.format("Pom (%s) does not exist in %s", path, jar.getName()));
}
try (InputStream input = jar.getInputStream(entry);
FileOutputStream fos = new FileOutputStream(file)) {
IOUtils.copy(input, fos);
} catch (IOException ex) {
LOGGER.warn("An error occurred reading '{}' from '{}'.", path, jar.getName());
LOGGER.error("", ex);
}
return file;
}
/**
* Sets evidence from the pom on the supplied dependency.
*
* @param dependency the dependency to set data on
* @param pom the information from the pom
* @param classes a collection of ClassNameInformation - containing data
* about the fully qualified class names within the JAR file being analyzed
* @param isMainPom a flag indicating if this is the primary pom.
* @return true if there was evidence within the pom that we could use;
* otherwise false
*/
public static boolean setPomEvidence(Dependency dependency, Model pom, List classes, boolean isMainPom) {
if (pom == null) {
return false;
}
boolean foundSomething = false;
boolean addAsIdentifier = true;
String groupid = pom.getGroupId();
String parentGroupId = pom.getParentGroupId();
String artifactid = pom.getArtifactId();
String parentArtifactId = pom.getParentArtifactId();
String version = pom.getVersion();
String parentVersion = pom.getParentVersion();
if (("org.sonatype.oss".equals(parentGroupId) && "oss-parent".equals(parentArtifactId))
|| ("org.springframework.boot".equals(parentGroupId) && "spring-boot-starter-parent".equals(parentArtifactId))) {
parentGroupId = null;
parentArtifactId = null;
parentVersion = null;
}
if ((groupid == null || groupid.isEmpty()) && parentGroupId != null && !parentGroupId.isEmpty()) {
groupid = parentGroupId;
}
final String originalGroupID = groupid;
if (groupid != null && (groupid.startsWith("org.") || groupid.startsWith("com."))) {
groupid = groupid.substring(4);
}
if ((artifactid == null || artifactid.isEmpty()) && parentArtifactId != null && !parentArtifactId.isEmpty()) {
artifactid = parentArtifactId;
}
final String originalArtifactID = artifactid;
if (artifactid != null && (artifactid.startsWith("org.") || artifactid.startsWith("com."))) {
artifactid = artifactid.substring(4);
}
if ((version == null || version.isEmpty()) && parentVersion != null && !parentVersion.isEmpty()) {
version = parentVersion;
}
if (isMainPom && dependency.getName() == null && originalArtifactID != null && !originalArtifactID.isEmpty()) {
if (originalGroupID != null && !originalGroupID.isEmpty()) {
dependency.setName(String.format("%s:%s", originalGroupID, originalArtifactID));
} else {
dependency.setName(originalArtifactID);
}
}
if (isMainPom && dependency.getVersion() == null && version != null && !version.isEmpty()) {
dependency.setVersion(version);
}
if (groupid != null && !groupid.isEmpty()) {
foundSomething = true;
dependency.addEvidence(EvidenceType.VENDOR, "pom", "groupid", groupid, Confidence.HIGHEST);
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "groupid", groupid, Confidence.LOW);
addMatchingValues(classes, groupid, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, groupid, dependency, EvidenceType.PRODUCT);
if (parentGroupId != null && !parentGroupId.isEmpty() && !parentGroupId.equals(groupid)) {
dependency.addEvidence(EvidenceType.VENDOR, "pom", "parent-groupid", parentGroupId, Confidence.MEDIUM);
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "parent-groupid", parentGroupId, Confidence.LOW);
addMatchingValues(classes, parentGroupId, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, parentGroupId, dependency, EvidenceType.PRODUCT);
}
} else {
addAsIdentifier = false;
}
if (artifactid != null && !artifactid.isEmpty()) {
foundSomething = true;
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "artifactid", artifactid, Confidence.HIGHEST);
dependency.addEvidence(EvidenceType.VENDOR, "pom", "artifactid", artifactid, Confidence.LOW);
addMatchingValues(classes, artifactid, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, artifactid, dependency, EvidenceType.PRODUCT);
if (parentArtifactId != null && !parentArtifactId.isEmpty() && !parentArtifactId.equals(artifactid)) {
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "parent-artifactid", parentArtifactId, Confidence.MEDIUM);
dependency.addEvidence(EvidenceType.VENDOR, "pom", "parent-artifactid", parentArtifactId, Confidence.LOW);
addMatchingValues(classes, parentArtifactId, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, parentArtifactId, dependency, EvidenceType.PRODUCT);
}
} else {
addAsIdentifier = false;
}
if (version != null && !version.isEmpty()) {
foundSomething = true;
dependency.addEvidence(EvidenceType.VERSION, "pom", "version", version, Confidence.HIGHEST);
if (parentVersion != null && !parentVersion.isEmpty() && !parentVersion.equals(version)) {
dependency.addEvidence(EvidenceType.VERSION, "pom", "parent-version", version, Confidence.LOW);
}
} else {
addAsIdentifier = false;
}
if (addAsIdentifier && isMainPom) {
Identifier id;
try {
final PackageURL purl = PackageURLBuilder.aPackageURL().withType("maven").withNamespace(originalGroupID)
.withName(originalArtifactID).withVersion(version).build();
id = new PurlIdentifier(purl, Confidence.HIGH);
} catch (MalformedPackageURLException ex) {
final String gav = String.format("%s:%s:%s", originalGroupID, originalArtifactID, version);
LOGGER.debug("Error building package url for " + gav + "; using generic identifier instead.", ex);
id = new GenericIdentifier("maven:" + gav, Confidence.HIGH);
}
dependency.addSoftwareIdentifier(id);
}
// org name
final String org = pom.getOrganization();
if (org != null && !org.isEmpty()) {
dependency.addEvidence(EvidenceType.VENDOR, "pom", "organization name", org, Confidence.HIGH);
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "organization name", org, Confidence.LOW);
addMatchingValues(classes, org, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, org, dependency, EvidenceType.PRODUCT);
}
// org name
String orgUrl = pom.getOrganizationUrl();
if (orgUrl != null && !orgUrl.isEmpty()) {
if (orgUrl.startsWith("https://github.com/")) {
orgUrl = orgUrl.substring(19);
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "url", orgUrl, Confidence.HIGH);
} else {
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "organization url", orgUrl, Confidence.LOW);
}
dependency.addEvidence(EvidenceType.VENDOR, "pom", "organization url", orgUrl, Confidence.MEDIUM);
}
//pom name
final String pomName = pom.getName();
if (pomName
!= null && !pomName.isEmpty()) {
foundSomething = true;
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "name", pomName, Confidence.HIGH);
dependency.addEvidence(EvidenceType.VENDOR, "pom", "name", pomName, Confidence.HIGH);
addMatchingValues(classes, pomName, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, pomName, dependency, EvidenceType.PRODUCT);
}
//Description
final String description = pom.getDescription();
if (description != null && !description.isEmpty()
&& !description.startsWith("POM was created by")
&& !description.startsWith("Sonatype helps open source projects")
&& !description.endsWith("project for Spring Boot")) {
foundSomething = true;
final String trimmedDescription = addDescription(dependency, description, "pom", "description");
addMatchingValues(classes, trimmedDescription, dependency, EvidenceType.VENDOR);
addMatchingValues(classes, trimmedDescription, dependency, EvidenceType.PRODUCT);
}
String projectURL = pom.getProjectURL();
if (projectURL != null && !projectURL.trim().isEmpty()) {
if (projectURL.startsWith("https://github.com/")) {
projectURL = projectURL.substring(19);
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "url", projectURL, Confidence.HIGH);
} else {
dependency.addEvidence(EvidenceType.PRODUCT, "pom", "url", projectURL, Confidence.MEDIUM);
}
dependency.addEvidence(EvidenceType.VENDOR, "pom", "url", projectURL, Confidence.HIGHEST);
}
extractLicense(pom, dependency);
return foundSomething;
}
/**
* Analyzes the path information of the classes contained within the
* JarAnalyzer to try and determine possible vendor or product names. If any
* are found they are stored in the packageVendor and packageProduct
* hashSets.
*
* @param classNames a list of class names
* @param dependency a dependency to analyze
* @param addPackagesAsEvidence a flag indicating whether or not package
* names should be added as evidence.
*/
protected void analyzePackageNames(List classNames,
Dependency dependency, boolean addPackagesAsEvidence) {
final Map vendorIdentifiers = new HashMap<>();
final Map productIdentifiers = new HashMap<>();
analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers);
final int classCount = classNames.size();
vendorIdentifiers.entrySet().forEach((entry) -> {
final float ratio = entry.getValue() / (float) classCount;
if (ratio > 0.5) {
//TODO remove weighting?
dependency.addVendorWeighting(entry.getKey());
if (addPackagesAsEvidence && entry.getKey().length() > 1) {
dependency.addEvidence(EvidenceType.VENDOR, "jar", "package name", entry.getKey(), Confidence.LOW);
}
}
});
productIdentifiers.entrySet().forEach((entry) -> {
final float ratio = entry.getValue() / (float) classCount;
if (ratio > 0.5) {
//todo remove weighting
dependency.addProductWeighting(entry.getKey());
if (addPackagesAsEvidence && entry.getKey().length() > 1) {
dependency.addEvidence(EvidenceType.PRODUCT, "jar", "package name", entry.getKey(), Confidence.LOW);
}
}
});
}
/**
*
* Reads the manifest from the JAR file and collects the entries. Some
* vendorKey entries are:
* - Implementation Title
* - Implementation Version
- Implementation Vendor
* - Implementation VendorId
- Bundle Name
- Bundle
* Version
- Bundle Vendor
- Bundle Description
- Main
* Class
* However, all but a handful of specific entries are read in.
*
* @param dependency A reference to the dependency
* @param classInformation a collection of class information
* @return whether evidence was identified parsing the manifest
* @throws IOException if there is an issue reading the JAR file
*/
//CSOFF: MethodLength
protected boolean parseManifest(Dependency dependency, List classInformation)
throws IOException {
boolean foundSomething = false;
try (JarFile jar = new JarFile(dependency.getActualFilePath())) {
final Manifest manifest = jar.getManifest();
if (manifest == null) {
if (!dependency.getFileName().toLowerCase().endsWith("-sources.jar")
&& !dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
&& !dependency.getFileName().toLowerCase().endsWith("-src.jar")
&& !dependency.getFileName().toLowerCase().endsWith("-doc.jar")) {
LOGGER.debug("Jar file '{}' does not contain a manifest.", dependency.getFileName());
}
return false;
}
String source = "Manifest";
String specificationVersion = null;
boolean hasImplementationVersion = false;
Attributes atts = manifest.getMainAttributes();
for (Entry