change to skip and remove macOS metadata and non-zip files

This commit is contained in:
Richard Mealing
2017-08-21 13:51:45 +01:00
parent 6d7f7d8e42
commit 5d87dc2942
5 changed files with 127 additions and 26 deletions

View File

@@ -19,12 +19,14 @@ package org.owasp.dependencycheck.analyzer;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
@@ -42,6 +44,7 @@ import java.util.jar.JarFile;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -53,11 +56,11 @@ import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceCollection; import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder; import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.xml.pom.License;
import org.owasp.dependencycheck.xml.pom.PomUtils;
import org.owasp.dependencycheck.xml.pom.Model;
import org.owasp.dependencycheck.utils.FileUtils; import org.owasp.dependencycheck.utils.FileUtils;
import org.owasp.dependencycheck.utils.Settings; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -168,6 +171,21 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
*/ */
private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build(); 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};
//</editor-fold> //</editor-fold>
//<editor-fold defaultstate="collapsed" desc="All standard implmentation details of Analyzer"> //<editor-fold defaultstate="collapsed" desc="All standard implmentation details of Analyzer">
/** /**
@@ -223,19 +241,82 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
*/ */
@Override @Override
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException { public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
final List<ClassNameInformation> classNames = collectClassNames(dependency); try {
final String fileName = dependency.getFileName().toLowerCase(); final List<ClassNameInformation> classNames = collectClassNames(dependency);
if (classNames.isEmpty() final String fileName = dependency.getFileName().toLowerCase();
&& (fileName.endsWith("-sources.jar") if (classNames.isEmpty()
|| fileName.endsWith("-javadoc.jar") && (fileName.endsWith("-sources.jar")
|| fileName.endsWith("-src.jar") || fileName.endsWith("-javadoc.jar")
|| fileName.endsWith("-doc.jar"))) { || fileName.endsWith("-src.jar")
engine.getDependencies().remove(dependency); || fileName.endsWith("-doc.jar")
|| isMacOSMetaDataFile(dependency, engine))
|| !isZipFile(dependency)) {
engine.getDependencies().remove(dependency);
return;
}
final boolean hasManifest = parseManifest(dependency, classNames);
final boolean hasPOM = analyzePOM(dependency, classNames, engine);
final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
} catch (IOException ex) {
throw new AnalysisException("Exception occurred reading the JAR file (" + dependency.getFileName() + ").", ex);
} }
final boolean hasManifest = parseManifest(dependency, classNames); }
final boolean hasPOM = analyzePOM(dependency, classNames, engine);
final boolean addPackagesAsEvidence = !(hasManifest && hasPOM); /**
analyzePackageNames(classNames, dependency, addPackagesAsEvidence); * Checks if the given dependency appears to be a macOS metadata 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 metadata file
* @param engine the engine that is scanning the dependencies
* @return whether or not the given dependency appears to be a macOS metadata file
*/
private boolean isMacOSMetaDataFile(final Dependency dependency, final Engine engine) {
final String fileName = Paths.get(dependency.getActualFilePath()).getFileName().toString();
return fileName.startsWith("._") && hasDependencyWithFilename(engine.getDependencies(), fileName.substring(2));
}
/**
* 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
*/
private boolean hasDependencyWithFilename(final List<Dependency> dependencies, final String fileName) {
for (final Dependency dependency : dependencies) {
if (Paths.get(dependency.getActualFilePath()).getFileName().toString().toLowerCase()
.equals(fileName.toLowerCase())) {
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
*/
private boolean isZipFile(final Dependency dependency) {
final byte[] buffer = new byte[4];
try (final FileInputStream fileInputStream = new FileInputStream(dependency.getActualFilePath())) {
fileInputStream.read(buffer);
if (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;
} }
/** /**
@@ -583,8 +664,10 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* @param dependency A reference to the dependency * @param dependency A reference to the dependency
* @param classInformation a collection of class information * @param classInformation a collection of class information
* @return whether evidence was identified parsing the manifest * @return whether evidence was identified parsing the manifest
* @throws IOException if there is an issue reading the JAR file
*/ */
protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation) { protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation)
throws IOException {
boolean foundSomething = false; boolean foundSomething = false;
try (JarFile jar = new JarFile(dependency.getActualFilePath())) { try (JarFile jar = new JarFile(dependency.getActualFilePath())) {
final Manifest manifest = jar.getManifest(); final Manifest manifest = jar.getManifest();
@@ -741,9 +824,6 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
foundSomething = true; foundSomething = true;
versionEvidence.addEvidence(source, "specification-version", specificationVersion, Confidence.HIGH); versionEvidence.addEvidence(source, "specification-version", specificationVersion, Confidence.HIGH);
} }
} catch (IOException ex) {
LOGGER.warn("Unable to read dependency file '{}'", dependency.getActualFilePath());
LOGGER.trace("", ex);
} }
return foundSomething; return foundSomething;
} }

View File

@@ -20,17 +20,18 @@ package org.owasp.dependencycheck.analyzer;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.Test; import org.junit.Test;
import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.BaseTest;
import org.owasp.dependencycheck.analyzer.JarAnalyzer.ClassNameInformation; import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Evidence; import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.utils.Settings; import org.owasp.dependencycheck.utils.Settings;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@@ -176,10 +177,29 @@ public class JarAnalyzerTest extends BaseTest {
} }
@Test @Test
public void testParseManifest_CatchesIOException() { public void testAnalyzeDependency_SkipsMacOSMetaDataFile() throws Exception {
Dependency dependency = new Dependency(); JarAnalyzer instance = new JarAnalyzer();
dependency.setActualFilePath("doesNotExist"); Dependency macOSMetaDataFile = new Dependency();
assertFalse(new File(dependency.getActualFilePath()).exists()); macOSMetaDataFile
assertFalse(new JarAnalyzer().parseManifest(dependency, new ArrayList<ClassNameInformation>())); .setActualFilePath(FileUtils.getFile("src", "test", "resources", "._avro-ipc-1.5.0.jar").getAbsolutePath());
macOSMetaDataFile.setFileName("._avro-ipc-1.5.0.jar");
Dependency actualJarFile = new Dependency();
actualJarFile.setActualFilePath(BaseTest.getResourceAsFile(this, "avro-ipc-1.5.0.jar").getAbsolutePath());
actualJarFile.setFileName("avro-ipc-1.5.0.jar");
Engine engine = new Engine();
engine.setDependencies(Arrays.asList(macOSMetaDataFile, actualJarFile));
instance.analyzeDependency(macOSMetaDataFile, engine);
}
@Test
public void testAnalyseDependency_SkipsNonZipFile() throws Exception {
JarAnalyzer instance = new JarAnalyzer();
Dependency textFileWithJarExtension = new Dependency();
textFileWithJarExtension
.setActualFilePath(BaseTest.getResourceAsFile(this, "textFileWithJarExtension.jar").getAbsolutePath());
textFileWithJarExtension.setFileName("textFileWithJarExtension.jar");
Engine engine = new Engine();
engine.setDependencies(Collections.singletonList(textFileWithJarExtension));
instance.analyzeDependency(textFileWithJarExtension, engine);
} }
} }

View File

@@ -0,0 +1 @@
text file with jar extension