diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java index ed383080d..158abe20d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java @@ -318,16 +318,17 @@ public class Engine { return null; } final String fileName = file.getName(); - final String extension = FileUtils.getFileExtension(fileName); + String extension = FileUtils.getFileExtension(fileName); + if (null == extension) { + extension = fileName; + } Dependency dependency = null; - if (extension != null) { - if (supportsExtension(extension)) { - dependency = new Dependency(file); - dependencies.add(dependency); + if (supportsExtension(extension)) { + dependency = new Dependency(file); + if (extension == fileName){ + dependency.setFileExtension(extension); } - } else { - final String msg = String.format("No file extension found on file '%s'. The file was not analyzed.", file.toString()); - LOGGER.log(Level.FINE, msg); + dependencies.add(dependency); } return dependency; } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java index 503f0feb4..3dfe67542 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java @@ -18,13 +18,10 @@ package org.owasp.dependencycheck.analyzer; import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FilenameFilter; -import java.io.IOException; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -34,30 +31,33 @@ import java.util.regex.Pattern; import javax.mail.MessagingException; import javax.mail.internet.InternetHeaders; -import org.apache.commons.compress.archivers.ArchiveEntry; -import org.apache.commons.compress.archivers.ArchiveInputStream; -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.commons.io.input.AutoCloseInputStream; import org.apache.commons.lang.StringUtils; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; -import org.owasp.dependencycheck.analyzer.exception.ArchiveExtractionException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.EvidenceCollection; +import org.owasp.dependencycheck.utils.ExtractionException; +import org.owasp.dependencycheck.utils.ExtractionUtil; import org.owasp.dependencycheck.utils.FileUtils; import org.owasp.dependencycheck.utils.Settings; /** - * Used to load a Wheel distriution file and collect information that can be - * used to determine the associated CPE. + * Used to analyze a Wheel distriution file or *.dist-info folder, and collect + * information that can be used to determine the associated CPE. * * @author Dale Visser */ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { + private static final String PKG_INFO = "PKG-INFO"; + + /** + * Name of wheel metadata files to analyze. + */ private static final String METADATA = "METADATA"; /** @@ -66,26 +66,12 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { private static final Logger LOGGER = Logger .getLogger(PythonDistributionAnalyzer.class.getName()); - /** - * The buffer size to use when extracting files from the archive. - */ - private static final int BUFFER_SIZE = 4096; - /** * The count of directories created during analysis. This is used for * creating temporary directories. */ private static int dirCount = 0; - /** - * Constructs a new PythonDistributionAnalyzer. - */ - public PythonDistributionAnalyzer() { - super(); - } - - // /** * The name of the analyzer. */ @@ -98,7 +84,39 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { /** * The set of file extensions supported by this analyzer. */ - private static final Set EXTENSIONS = newHashSet("whl"); + private static final Set EXTENSIONS = newHashSet("whl", METADATA, + PKG_INFO); + + /** + * Pattern that captures the vendor from a home page URL. + */ + private static final Pattern HOMEPAGE_VENDOR = Pattern + .compile("^[a-zA-Z]+://.*\\.(.+)\\.[a-zA-Z]+/?.*$"); + + /** + * The parent directory for the individual directories per archive. + */ + private File tempFileLocation; + + /** + * Filter that detects *.dist-info files (but doesn't verify they are + * directories. + */ + private static final FilenameFilter DIST_INFO_FILTER = new SuffixFileFilter( + ".dist-info"); + + /** + * Filter that detects files named "METADATA". + */ + private static final FilenameFilter METADATA_FILTER = new NameFileFilter( + METADATA); + + /** + * Constructs a new PythonDistributionAnalyzer. + */ + public PythonDistributionAnalyzer() { + super(); + } /** * Returns a list of file EXTENSIONS supported by this analyzer. @@ -142,42 +160,48 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { return Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_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 analyzeFileType(Dependency dependency, Engine engine) + protected void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException { - final File tmpWheelFolder = getNextTempDirectory(); - LOGGER.fine(String.format("%s exists? %b", tmpWheelFolder, - tmpWheelFolder.exists())); - extractFiles(new File(dependency.getActualFilePath()), tmpWheelFolder, - METADATA_FILTER); - collectWheelMetadata(dependency, tmpWheelFolder); + if ("whl".equals(dependency.getFileExtension())) { + final File tmpWheelFolder = getNextTempDirectory(); + LOGGER.fine(String.format("%s exists? %b", tmpWheelFolder, + tmpWheelFolder.exists())); + try { + ExtractionUtil.extractFilesUsingFilter( + new File(dependency.getActualFilePath()), + tmpWheelFolder, METADATA_FILTER); + } catch (ExtractionException ex) { + throw new AnalysisException(ex); + } + + collectWheelMetadata( + dependency, + getMatchingFile( + getMatchingFile(tmpWheelFolder, DIST_INFO_FILTER), + METADATA_FILTER)); + } else { + final File actualFile = dependency.getActualFile(); + final String name = actualFile.getName(); + final boolean metadata = METADATA.equals(name); + if (metadata || PKG_INFO.equals(name)) { + final File parent = actualFile.getParentFile(); + final String parentName = parent.getName(); + dependency.setDisplayFileName(parentName + "/" + name); + if (parent.isDirectory() + && ((metadata && parentName.endsWith(".dist-info")) || parentName + .endsWith(".egg-info"))) { + collectWheelMetadata(dependency, actualFile); + } + } + } } /** - * The parent directory for the individual directories per archive. - */ - private File tempFileLocation = null; - - /** - * Initializes the JarAnalyzer. - * - * @throws Exception - * is thrown if there is an exception creating a temporary - * directory + * Makes sure a usable temporary directory is available. */ @Override - public void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws Exception { final File baseDir = Settings.getTempDirectory(); tempFileLocation = File.createTempFile("check", "tmp", baseDir); if (!tempFileLocation.delete()) { @@ -209,33 +233,30 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { } } - private static final Pattern HOMEPAGE_VENDOR = Pattern - .compile("^[a-zA-Z]+://.*\\.(.+)\\.[a-zA-Z]+/?.*$"); - /** * Gathers evidence from the METADATA file. * * @param dependency * the dependency being analyzed */ - private static void collectWheelMetadata(Dependency dependency, - File wheelFolder) { - InternetHeaders headers = getManifestProperties(wheelFolder); + private static void collectWheelMetadata(Dependency dependency, File file) { + final InternetHeaders headers = getManifestProperties(file); addPropertyToEvidence(headers, dependency.getVersionEvidence(), "Version", Confidence.HIGHEST); addPropertyToEvidence(headers, dependency.getProductEvidence(), "Name", Confidence.HIGHEST); - String url = headers.getHeader("Home-page", null); - EvidenceCollection vendorEvidence = dependency.getVendorEvidence(); + final String url = headers.getHeader("Home-page", null); + final EvidenceCollection vendorEvidence = dependency + .getVendorEvidence(); if (StringUtils.isNotBlank(url)) { - Matcher m = HOMEPAGE_VENDOR.matcher(url); - if (m.matches()) { - vendorEvidence.addEvidence(METADATA, "vendor", m.group(1), - Confidence.MEDIUM); + final Matcher matcher = HOMEPAGE_VENDOR.matcher(url); + if (matcher.matches()) { + vendorEvidence.addEvidence(METADATA, "vendor", + matcher.group(1), Confidence.MEDIUM); } } addPropertyToEvidence(headers, vendorEvidence, "Author", Confidence.LOW); - String summary = headers.getHeader("Summary", null); + final String summary = headers.getHeader("Summary", null); if (StringUtils.isNotBlank(summary)) { JarAnalyzer .addDescription(dependency, summary, METADATA, "summary"); @@ -244,52 +265,34 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { private static void addPropertyToEvidence(InternetHeaders headers, EvidenceCollection evidence, String property, Confidence confidence) { - String value = headers.getHeader(property, null); + final String value = headers.getHeader(property, null); LOGGER.fine(String.format("Property: %s, Value: %s\n", property, value)); if (StringUtils.isNotBlank(value)) { evidence.addEvidence(METADATA, property, value, confidence); } } - private static final FilenameFilter DIST_INFO_FILTER = new SuffixFileFilter( - ".dist-info"); - - private static final FilenameFilter METADATA_FILTER = new NameFileFilter( - METADATA); - private static final File getMatchingFile(File folder, FilenameFilter filter) { File result = null; - File[] matches = folder.listFiles(filter); + final File[] matches = folder.listFiles(filter); if (null != matches && 1 == matches.length) { result = matches[0]; } return result; } - private static InternetHeaders getManifestProperties(File wheelFolder) { - InternetHeaders result = new InternetHeaders(); - LOGGER.fine(String.format("%s has %d entries.", wheelFolder, - wheelFolder.list().length)); - File dist_info = getMatchingFile(wheelFolder, DIST_INFO_FILTER); - if (null != dist_info && dist_info.isDirectory()) { - LOGGER.fine(String.format("%s has %d entries.", dist_info, - dist_info.list().length)); - File manifest = getMatchingFile(dist_info, METADATA_FILTER); - LOGGER.fine(String.format("METADATA file found? %b", - null != manifest)); - if (null != manifest) { - try { - result.load(new AutoCloseInputStream( - new BufferedInputStream(new FileInputStream( - manifest)))); - } catch (MessagingException e) { - LOGGER.log(Level.WARNING, e.getMessage(), e); - } catch (FileNotFoundException e) { - LOGGER.log(Level.WARNING, e.getMessage(), e); - } - } else { - LOGGER.fine(String.format("%s contents: %s", dist_info, - StringUtils.join(dist_info.list(), ";"))); + private static InternetHeaders getManifestProperties(File manifest) { + final InternetHeaders result = new InternetHeaders(); + if (null == manifest) { + LOGGER.fine("Manifest file not found."); + } else { + try { + result.load(new AutoCloseInputStream(new BufferedInputStream( + new FileInputStream(manifest)))); + } catch (MessagingException e) { + LOGGER.log(Level.WARNING, e.getMessage(), e); + } catch (FileNotFoundException e) { + LOGGER.log(Level.WARNING, e.getMessage(), e); } } return result; @@ -319,146 +322,4 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { } return directory; } - - /** - * Extracts the contents of an archive into the specified directory. - * - * @param archive - * an archive file such as a WAR or EAR - * @param destination - * a directory to extract the contents to - * @param filter - * determines which files get extracted - * @throws AnalysisException - * thrown if the archive is not found - */ - private static void extractFiles(File archive, File destination, - FilenameFilter filter) throws AnalysisException { - if (archive == null || destination == null) { - return; - } - - FileInputStream fis = null; - try { - fis = new FileInputStream(archive); - } catch (FileNotFoundException ex) { - LOGGER.log(Level.FINE, null, ex); - throw new AnalysisException("Archive file was not found.", ex); - } - try { - extractArchive(new ZipArchiveInputStream(new BufferedInputStream( - fis)), destination, filter); - } catch (ArchiveExtractionException ex) { - final String msg = String.format( - "Exception extracting archive '%s'.", archive.getName()); - LOGGER.log(Level.WARNING, msg); - LOGGER.log(Level.FINE, null, ex); - } finally { - try { - fis.close(); - } catch (IOException ex) { - LOGGER.log(Level.FINE, null, ex); - } - } - } - - /** - * Extracts files from an archive. - * - * @param input - * the archive to extract files from - * @param destination - * the location to write the files too - * @param filter - * determines which files get extracted - * @throws ArchiveExtractionException - * thrown if there is an exception extracting files from the - * archive - */ - private static void extractArchive(ArchiveInputStream input, File destination, - FilenameFilter filter) throws ArchiveExtractionException { - ArchiveEntry entry; - try { - while ((entry = input.getNextEntry()) != null) { - if (entry.isDirectory()) { - final File d = new File(destination, entry.getName()); - if (!d.exists()) { - if (!d.mkdirs()) { - final String msg = String.format( - "Unable to create directory '%s'.", - d.getAbsolutePath()); - throw new AnalysisException(msg); - } - } - } else { - final File file = new File(destination, entry.getName()); - if (filter.accept(file.getParentFile(), file.getName())) { - final String extracting = String.format( - "Extracting '%s'", file.getPath()); - LOGGER.fine(extracting); - BufferedOutputStream bos = null; - FileOutputStream fos = null; - try { - final File parent = file.getParentFile(); - if (!parent.isDirectory()) { - if (!parent.mkdirs()) { - final String msg = String.format( - "Unable to build directory '%s'.", - parent.getAbsolutePath()); - throw new AnalysisException(msg); - } - } - fos = new FileOutputStream(file); - bos = new BufferedOutputStream(fos, BUFFER_SIZE); - int count; - final byte[] data = new byte[BUFFER_SIZE]; - while ((count = input.read(data, 0, BUFFER_SIZE)) != -1) { - bos.write(data, 0, count); - } - bos.flush(); - } catch (FileNotFoundException ex) { - LOGGER.log(Level.FINE, null, ex); - final String msg = String - .format("Unable to find file '%s'.", - file.getName()); - throw new AnalysisException(msg, ex); - } catch (IOException ex) { - LOGGER.log(Level.FINE, null, ex); - final String msg = String.format( - "IO Exception while parsing file '%s'.", - file.getName()); - throw new AnalysisException(msg, ex); - } finally { - if (bos != null) { - try { - bos.close(); - } catch (IOException ex) { - LOGGER.log(Level.FINEST, null, ex); - } - } - if (fos != null) { - try { - fos.close(); - } catch (IOException ex) { - LOGGER.log(Level.FINEST, null, ex); - } - } - } - } - } - } - } catch (IOException ex) { - throw new ArchiveExtractionException(ex); - } catch (Throwable ex) { - throw new ArchiveExtractionException(ex); - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException ex) { - LOGGER.log(Level.FINEST, null, ex); - } - } - } - } -} +} \ No newline at end of file diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java index d3f88f257..3174680e3 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java @@ -222,7 +222,7 @@ public class Dependency implements Serializable, Comparable { } /** - * Sets the file name of the dependency. + * Sets the file extension of the dependency. * * @param fileExtension the file name of the dependency */ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java index 20346df96..5827d7d93 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/ExtractionUtil.java @@ -17,19 +17,29 @@ */ package org.owasp.dependencycheck.utils; +import static org.owasp.dependencycheck.utils.FileUtils.getFileExtension; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilenameFilter; import java.io.IOException; +import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; + +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.owasp.dependencycheck.Engine; -import static org.owasp.dependencycheck.utils.FileUtils.getFileExtension; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.analyzer.exception.ArchiveExtractionException; /** * @@ -106,12 +116,7 @@ public final class ExtractionUtil { try { fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos, BUFFER_SIZE); - int count; - final byte[] data = new byte[BUFFER_SIZE]; - while ((count = zis.read(data, 0, BUFFER_SIZE)) != -1) { - bos.write(data, 0, count); - } - bos.flush(); + transferUsingBuffer(zis, bos); } catch (FileNotFoundException ex) { LOGGER.log(Level.FINE, null, ex); final String msg = String.format("Unable to find file '%s'.", file.getName()); @@ -121,13 +126,7 @@ public final class ExtractionUtil { final String msg = String.format("IO Exception while parsing file '%s'.", file.getName()); throw new ExtractionException(msg, ex); } finally { - if (bos != null) { - try { - bos.close(); - } catch (IOException ex) { - LOGGER.log(Level.FINEST, null, ex); - } - } + closeStream(bos); } } } @@ -137,11 +136,157 @@ public final class ExtractionUtil { LOGGER.log(Level.FINE, msg, ex); throw new ExtractionException(msg, ex); } finally { - try { - zis.close(); - } catch (IOException ex) { - LOGGER.log(Level.FINEST, null, ex); - } + closeStream(zis); } } + + /** + * Extracts the contents of an archive into the specified directory. + * + * @param archive + * an archive file such as a WAR or EAR + * @param destination + * a directory to extract the contents to + * @param filter + * determines which files get extracted + * @throws ExtractionException + * thrown if the archive is not found + */ + public static void extractFilesUsingFilter(File archive, File destination, + FilenameFilter filter) throws ExtractionException { + if (archive == null || destination == null) { + return; + } + + FileInputStream fis = null; + try { + fis = new FileInputStream(archive); + } catch (FileNotFoundException ex) { + LOGGER.log(Level.FINE, null, ex); + throw new ExtractionException("Archive file was not found.", ex); + } + try { + extractArchive(new ZipArchiveInputStream(new BufferedInputStream( + fis)), destination, filter); + } catch (ArchiveExtractionException ex) { + final String msg = String.format( + "Exception extracting archive '%s'.", archive.getName()); + LOGGER.log(Level.WARNING, msg); + LOGGER.log(Level.FINE, null, ex); + } finally { + try { + fis.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINE, null, ex); + } + } + } + + /** + * Extracts files from an archive. + * + * @param input + * the archive to extract files from + * @param destination + * the location to write the files too + * @param filter + * determines which files get extracted + * @throws ArchiveExtractionException + * thrown if there is an exception extracting files from the + * archive + */ + private static void extractArchive(ArchiveInputStream input, + File destination, FilenameFilter filter) + throws ArchiveExtractionException { + ArchiveEntry entry; + try { + while ((entry = input.getNextEntry()) != null) { + if (entry.isDirectory()) { + final File dir = new File(destination, entry.getName()); + if (!dir.exists()) { + if (!dir.mkdirs()) { + final String msg = String.format( + "Unable to create directory '%s'.", + dir.getAbsolutePath()); + throw new AnalysisException(msg); + } + } + } else { + extractFile(input, destination, filter, entry); + } + } + } catch (IOException ex) { + throw new ArchiveExtractionException(ex); + } catch (Throwable ex) { + throw new ArchiveExtractionException(ex); + } finally { + closeStream(input); + } + } + + private static void extractFile(ArchiveInputStream input, File destination, + FilenameFilter filter, ArchiveEntry entry) throws ExtractionException { + final File file = new File(destination, entry.getName()); + if (filter.accept(file.getParentFile(), file.getName())) { + final String extracting = String.format("Extracting '%s'", + file.getPath()); + LOGGER.fine(extracting); + BufferedOutputStream bos = null; + FileOutputStream fos = null; + try { + createParentFile(file); + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos, BUFFER_SIZE); + transferUsingBuffer(input, bos); + } catch (FileNotFoundException ex) { + LOGGER.log(Level.FINE, null, ex); + final String msg = String.format("Unable to find file '%s'.", + file.getName()); + throw new ExtractionException(msg, ex); + } catch (IOException ex) { + LOGGER.log(Level.FINE, null, ex); + final String msg = String + .format("IO Exception while parsing file '%s'.", + file.getName()); + throw new ExtractionException(msg, ex); + } finally { + closeStream(bos); + closeStream(fos); + } + } + } + + private static void transferUsingBuffer(InputStream input, + BufferedOutputStream bos) throws IOException { + int count; + final byte[] data = new byte[BUFFER_SIZE]; + while ((count = input.read(data, 0, BUFFER_SIZE)) != -1) { + bos.write(data, 0, count); + } + bos.flush(); + } + + private static void closeStream(Closeable stream) { + if (stream != null) { + try { + stream.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, null, ex); + } + } + } + + private static void createParentFile(final File file) + throws ExtractionException { + final File parent = file.getParentFile(); + if (!parent.isDirectory()) { + if (!parent.mkdirs()) { + final String msg = String.format( + "Unable to build directory '%s'.", + parent.getAbsolutePath()); + throw new ExtractionException(msg); + } + } + } + } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzerTest.java index 80e6c1e40..895a8ff1d 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzerTest.java @@ -20,11 +20,12 @@ package org.owasp.dependencycheck.analyzer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.util.Collections; -import java.util.Set; +import java.util.Arrays; +import java.util.HashSet; import org.junit.Test; import org.owasp.dependencycheck.BaseTest; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Evidence; @@ -41,9 +42,28 @@ public class PythonDistributionAnalyzerTest extends BaseTest { * is thrown when an exception occurs. */ @Test - public void testAnalyze() throws Exception { + public void testAnalyzeWheel() throws AnalysisException { + djangoAssertions(new Dependency(BaseTest.getResourceAsFile(this, + "Django-1.7.2-py2.py3-none-any.whl"))); + } + + /** + * Test of inspect method, of class JarAnalyzer. + * + * @throws Exception + * is thrown when an exception occurs. + */ + @Test + public void testAnalyzeSitePackage() throws AnalysisException { final Dependency result = new Dependency(BaseTest.getResourceAsFile( - this, "Django-1.7.2-py2.py3-none-any.whl")); + this, "site-packages/Django-1.7.2.dist-info/METADATA")); + djangoAssertions(result); + assertEquals("Django-1.7.2.dist-info/METADATA", + result.getDisplayFileName()); + } + + private void djangoAssertions(final Dependency result) + throws AnalysisException { new PythonDistributionAnalyzer().analyze(result, null); assertTrue("Expected vendor evidence to contain \"djangoproject\".", result.getVendorEvidence().toString().contains("djangoproject")); @@ -54,9 +74,24 @@ public class PythonDistributionAnalyzerTest extends BaseTest { break; } } - assertTrue( - "implementation-version of 1.7.2 not found in Django wheel.", - found); + assertTrue("Version 1.7.2 not found in Django dependency.", found); + } + + @Test + public void testAnalyzeEggInfo() throws AnalysisException { + final Dependency result = new Dependency(BaseTest.getResourceAsFile( + this, "site-packages/eggutils-0.0.2-py2.7.egg-info/PKG-INFO")); + new PythonDistributionAnalyzer().analyze(result, null); + assertTrue("Expected vendor evidence to contain \"python\".", result + .getVendorEvidence().toString().contains("python")); + boolean found = false; + for (final Evidence e : result.getVersionEvidence()) { + if ("Version".equals(e.getName()) && "0.0.2".equals(e.getValue())) { + found = true; + break; + } + } + assertTrue("Version 0.0.2 not found in eggutils dependency.", found); } /** @@ -64,8 +99,10 @@ public class PythonDistributionAnalyzerTest extends BaseTest { */ @Test public void testGetSupportedExtensions() { - assertEquals("Supported extensions should just be \"whl\".", - (Set) Collections.singleton("whl"), + assertEquals( + "Supported extensions should just be \"whl\", \"METADATA\" and \"PKG-INFO\".", + new HashSet(Arrays + .asList("whl", "METADATA", "PKG-INFO")), new PythonDistributionAnalyzer().getSupportedExtensions()); } @@ -83,7 +120,12 @@ public class PythonDistributionAnalyzerTest extends BaseTest { */ @Test public void testSupportsExtension() { + final PythonDistributionAnalyzer analyzer = new PythonDistributionAnalyzer(); assertTrue("Should support \"whl\" extension.", - new PythonDistributionAnalyzer().supportsExtension("whl")); + analyzer.supportsExtension("whl")); + assertTrue("Should support \"METADATA\" extension.", + analyzer.supportsExtension("METADATA")); + assertTrue("Should support \"METADATA\" extension.", + analyzer.supportsExtension("PKG-INFO")); } } \ No newline at end of file diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.data/scripts/django-admin.py b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.data/scripts/django-admin.py new file mode 100755 index 000000000..8648efa78 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.data/scripts/django-admin.py @@ -0,0 +1,5 @@ +#!python +from django.core import management + +if __name__ == "__main__": + management.execute_from_command_line() diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/DESCRIPTION.rst b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/DESCRIPTION.rst new file mode 100644 index 000000000..e1187231a --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/DESCRIPTION.rst @@ -0,0 +1,3 @@ +UNKNOWN + + diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/LICENSE.txt b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/LICENSE.txt new file mode 100644 index 000000000..5f4f225dd --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/METADATA b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/METADATA new file mode 100644 index 000000000..839abfe94 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/METADATA @@ -0,0 +1,31 @@ +Metadata-Version: 2.0 +Name: Django +Version: 1.7.2 +Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. +Home-page: http://www.djangoproject.com/ +Author: Django Software Foundation +Author-email: foundation@djangoproject.com +License: BSD +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules + +UNKNOWN + + diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/WHEEL b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/WHEEL new file mode 100644 index 000000000..9dff69d86 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.24.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/entry_points.txt b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/entry_points.txt new file mode 100644 index 000000000..22df67eba --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +django-admin = django.core.management:execute_from_command_line + diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/metadata.json b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/metadata.json new file mode 100644 index 000000000..7e0009efb --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/metadata.json @@ -0,0 +1 @@ +{"license": "BSD", "name": "Django", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "A high-level Python Web framework that encourages rapid development and clean, pragmatic design.", "version": "1.7.2", "extensions": {"python.details": {"project_urls": {"Home": "http://www.djangoproject.com/"}, "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "contacts": [{"role": "author", "email": "foundation@djangoproject.com", "name": "Django Software Foundation"}]}, "python.commands": {"wrap_console": {"django-admin": "django.core.management:execute_from_command_line"}}, "python.exports": {"console_scripts": {"django-admin": "django.core.management:execute_from_command_line"}}}, "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"]} \ No newline at end of file diff --git a/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/top_level.txt b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/top_level.txt new file mode 100644 index 000000000..d3e4ba564 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/Django-1.7.2.dist-info/top_level.txt @@ -0,0 +1 @@ +django diff --git a/dependency-check-core/src/test/resources/site-packages/django/__init__.py b/dependency-check-core/src/test/resources/site-packages/django/__init__.py new file mode 100644 index 000000000..5df6a56ca --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/django/__init__.py @@ -0,0 +1,21 @@ +VERSION = (1, 7, 2, 'final', 0) + + +def get_version(*args, **kwargs): + # Don't litter django/__init__.py with all the get_version stuff. + # Only import if it's actually called. + from django.utils.version import get_version + return get_version(*args, **kwargs) + + +def setup(): + """ + Configure the settings (this happens as a side effect of accessing the + first setting), configure logging and populate the app registry. + """ + from django.apps import apps + from django.conf import settings + from django.utils.log import configure_logging + + configure_logging(settings.LOGGING_CONFIG, settings.LOGGING) + apps.populate(settings.INSTALLED_APPS) diff --git a/dependency-check-core/src/test/resources/site-packages/django/shortcuts.py b/dependency-check-core/src/test/resources/site-packages/django/shortcuts.py new file mode 100644 index 000000000..62560472c --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/django/shortcuts.py @@ -0,0 +1,168 @@ +""" +This module collects helper functions and classes that "span" multiple levels +of MVC. In other words, these functions/classes introduce controlled coupling +for convenience's sake. +""" +from django.template import loader, RequestContext +from django.http import HttpResponse, Http404 +from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect +from django.db.models.base import ModelBase +from django.db.models.manager import Manager +from django.db.models.query import QuerySet +from django.core import urlresolvers +from django.utils import six + + +def render_to_response(*args, **kwargs): + """ + Returns a HttpResponse whose content is filled with the result of calling + django.template.loader.render_to_string() with the passed arguments. + """ + httpresponse_kwargs = {'content_type': kwargs.pop('content_type', None)} + + return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) + + +def render(request, *args, **kwargs): + """ + Returns a HttpResponse whose content is filled with the result of calling + django.template.loader.render_to_string() with the passed arguments. + Uses a RequestContext by default. + """ + httpresponse_kwargs = { + 'content_type': kwargs.pop('content_type', None), + 'status': kwargs.pop('status', None), + } + + if 'context_instance' in kwargs: + context_instance = kwargs.pop('context_instance') + if kwargs.get('current_app', None): + raise ValueError('If you provide a context_instance you must ' + 'set its current_app before calling render()') + else: + current_app = kwargs.pop('current_app', None) + context_instance = RequestContext(request, current_app=current_app) + + kwargs['context_instance'] = context_instance + + return HttpResponse(loader.render_to_string(*args, **kwargs), + **httpresponse_kwargs) + + +def redirect(to, *args, **kwargs): + """ + Returns an HttpResponseRedirect to the appropriate URL for the arguments + passed. + + The arguments could be: + + * A model: the model's `get_absolute_url()` function will be called. + + * A view name, possibly with arguments: `urlresolvers.reverse()` will + be used to reverse-resolve the name. + + * A URL, which will be used as-is for the redirect location. + + By default issues a temporary redirect; pass permanent=True to issue a + permanent redirect + """ + if kwargs.pop('permanent', False): + redirect_class = HttpResponsePermanentRedirect + else: + redirect_class = HttpResponseRedirect + + return redirect_class(resolve_url(to, *args, **kwargs)) + + +def _get_queryset(klass): + """ + Returns a QuerySet from a Model, Manager, or QuerySet. Created to make + get_object_or_404 and get_list_or_404 more DRY. + + Raises a ValueError if klass is not a Model, Manager, or QuerySet. + """ + if isinstance(klass, QuerySet): + return klass + elif isinstance(klass, Manager): + manager = klass + elif isinstance(klass, ModelBase): + manager = klass._default_manager + else: + if isinstance(klass, type): + klass__name = klass.__name__ + else: + klass__name = klass.__class__.__name__ + raise ValueError("Object is of type '%s', but must be a Django Model, " + "Manager, or QuerySet" % klass__name) + return manager.all() + + +def get_object_or_404(klass, *args, **kwargs): + """ + Uses get() to return an object, or raises a Http404 exception if the object + does not exist. + + klass may be a Model, Manager, or QuerySet object. All other passed + arguments and keyword arguments are used in the get() query. + + Note: Like with get(), an MultipleObjectsReturned will be raised if more than one + object is found. + """ + queryset = _get_queryset(klass) + try: + return queryset.get(*args, **kwargs) + except queryset.model.DoesNotExist: + raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) + + +def get_list_or_404(klass, *args, **kwargs): + """ + Uses filter() to return a list of objects, or raise a Http404 exception if + the list is empty. + + klass may be a Model, Manager, or QuerySet object. All other passed + arguments and keyword arguments are used in the filter() query. + """ + queryset = _get_queryset(klass) + obj_list = list(queryset.filter(*args, **kwargs)) + if not obj_list: + raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) + return obj_list + + +def resolve_url(to, *args, **kwargs): + """ + Return a URL appropriate for the arguments passed. + + The arguments could be: + + * A model: the model's `get_absolute_url()` function will be called. + + * A view name, possibly with arguments: `urlresolvers.reverse()` will + be used to reverse-resolve the name. + + * A URL, which will be returned as-is. + + """ + # If it's a model, use get_absolute_url() + if hasattr(to, 'get_absolute_url'): + return to.get_absolute_url() + + if isinstance(to, six.string_types): + # Handle relative URLs + if any(to.startswith(path) for path in ('./', '../')): + return to + + # Next try a reverse URL resolution. + try: + return urlresolvers.reverse(to, args=args, kwargs=kwargs) + except urlresolvers.NoReverseMatch: + # If this is a callable, re-raise. + if callable(to): + raise + # If this doesn't "feel" like a URL, re-raise. + if '/' not in to and '.' not in to: + raise + + # Finally, fall back and assume it's a URL + return to diff --git a/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/PKG-INFO b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/PKG-INFO new file mode 100644 index 000000000..44f3e9f66 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/PKG-INFO @@ -0,0 +1,28 @@ +Metadata-Version: 1.0 +Name: eggutils +Version: 0.0.2 +Summary: A set of utilities to create/manipulate eggs +Home-page: http://pypi.python.org/pypi/eggutils +Author: David Cournapeau +Author-email: cournape@gmail.com +License: BSD +Description: Set of utilities to manipulate and create eggs without setuptools, and outside + distutils context (i.e. without running setup). + + Making an egg containing DLL + ============================ + + Given Windows model for DLLs, when several python packages depends on the same + dll to be shared between extensions, it may be useful to have a "DLL egg" which + put the dlls within the python installation such as the dll are automatically + found by any extension to the corresponding python interpreter. + + Usage: + + :: + make-dll-egg -m PKG-INFO foo.dll bar.dll + + This will create a DLL with metadata taken from the PKG-INFO file, containing + both foo and bar dlls. + +Platform: any diff --git a/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/SOURCES.txt b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/SOURCES.txt new file mode 100644 index 000000000..6fccdf214 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/SOURCES.txt @@ -0,0 +1,10 @@ +README +setup.cfg +setup.py +eggutils/__init__.py +eggutils/eggutils.py +eggutils.egg-info/PKG-INFO +eggutils.egg-info/SOURCES.txt +eggutils.egg-info/dependency_links.txt +eggutils.egg-info/entry_points.txt +eggutils.egg-info/top_level.txt \ No newline at end of file diff --git a/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/dependency_links.txt b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/dependency_links.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/entry_points.txt b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/entry_points.txt new file mode 100644 index 000000000..ddbb36653 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +make-dll-egg = eggutils.eggutils:wrap_main + diff --git a/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/installed-files.txt b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/installed-files.txt new file mode 100644 index 000000000..825a8c348 --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/installed-files.txt @@ -0,0 +1,11 @@ +../eggutils/__init__.py +../eggutils/eggutils.py +../eggutils/__init__.pyc +../eggutils/eggutils.pyc +./ +SOURCES.txt +dependency_links.txt +top_level.txt +entry_points.txt +PKG-INFO +../../../../bin/make-dll-egg diff --git a/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/top_level.txt b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/top_level.txt new file mode 100644 index 000000000..c870be18e --- /dev/null +++ b/dependency-check-core/src/test/resources/site-packages/eggutils-0.0.2-py2.7.egg-info/top_level.txt @@ -0,0 +1 @@ +eggutils