Added capability to scan extracted egg and wheel metadata in the local Python

environment.


Former-commit-id: b0259d38134bf18b1eb72db9951dbe2d04ba8fb9
This commit is contained in:
Dale Visser
2015-03-30 15:59:54 -04:00
parent a5dee0cb27
commit e328ec990c
21 changed files with 648 additions and 279 deletions

View File

@@ -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;
}

View File

@@ -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 <dvisser@ida.org>
*/
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();
}
// <editor-fold defaultstate="collapsed"
// desc="All standard implmentation details of Analyzer">
/**
* 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<String> EXTENSIONS = newHashSet("whl");
private static final Set<String> 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);
}
}
}
}
}
}

View File

@@ -222,7 +222,7 @@ public class Dependency implements Serializable, Comparable<Dependency> {
}
/**
* Sets the file name of the dependency.
* Sets the file extension of the dependency.
*
* @param fileExtension the file name of the dependency
*/

View File

@@ -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);
}
}
}
}

View File

@@ -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<String>) Collections.singleton("whl"),
assertEquals(
"Supported extensions should just be \"whl\", \"METADATA\" and \"PKG-INFO\".",
new HashSet<String>(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"));
}
}

View File

@@ -0,0 +1,5 @@
#!python
from django.core import management
if __name__ == "__main__":
management.execute_from_command_line()

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,3 @@
[console_scripts]
django-admin = django.core.management:execute_from_command_line

View File

@@ -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"]}

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,3 @@
[console_scripts]
make-dll-egg = eggutils.eggutils:wrap_main

View File

@@ -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