From 0a9d1aeb746a7072fbc4250b461ed7acc90f584e Mon Sep 17 00:00:00 2001 From: "Nathan J. Mehl" Date: Wed, 21 Jun 2017 20:09:31 -0700 Subject: [PATCH] add support for parsing and validating debian source files --- README.md | 130 ++++++++++--- pydpkg/__init__.py | 281 ++++++++++++++++++++++++++- setup.py | 5 +- tests/test_dsc.py | 90 +++++++++ tests/testdeb_0.0.0-1.debian.tar.xz | Bin 0 -> 232 bytes tests/testdeb_0.0.0-badchecksums.dsc | 21 ++ tests/testdeb_0.0.0.dsc | 21 ++ tests/testdeb_0.0.0.dsc.asc | 34 ++++ tests/testdeb_0.0.0.orig.tar.gz | Bin 0 -> 280 bytes tests/testdeb_1.1.1-bad.dsc | 22 +++ tests/testdeb_1.1.1-bad.dsc.asc | 34 ++++ 11 files changed, 604 insertions(+), 34 deletions(-) create mode 100644 tests/test_dsc.py create mode 100644 tests/testdeb_0.0.0-1.debian.tar.xz create mode 100644 tests/testdeb_0.0.0-badchecksums.dsc create mode 100644 tests/testdeb_0.0.0.dsc create mode 100644 tests/testdeb_0.0.0.dsc.asc create mode 100644 tests/testdeb_0.0.0.orig.tar.gz create mode 100644 tests/testdeb_1.1.1-bad.dsc create mode 100644 tests/testdeb_1.1.1-bad.dsc.asc diff --git a/README.md b/README.md index 0c9c863..ab331e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Build Status](https://travis-ci.org/TheClimateCorporation/python-dpkg.svg?branch=master)](https://travis-ci.org/TheClimateCorporation/python-dpkg) -python-dpkg -=========== +# python-dpkg This library can be used to: @@ -12,6 +11,10 @@ This library can be used to: the algorithm described at https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version +3. Parse debian source description (dsc) files, inspect their contents + and verify that their source files are present and checksums are + correct. + This is primarily intended for use on platforms that do not normally ship [python-apt](http://apt.alioth.debian.org/python-apt-doc/) due to licensing restrictions or the lack of a native libapt.so (e.g. macOS) @@ -20,8 +23,7 @@ Currently only tested on CPython 2.7 and 3.5, but at least in theory should run on any python distribution that can install the [arpy](https://pypi.python.org/pypi/arpy/) library. -Installing -========== +## Installing Install the 'pydpkg' package from [PyPi](https://pypi.python.org) using the [pip](https://packaging.python.org/installing/) tool: @@ -32,11 +34,11 @@ the [pip](https://packaging.python.org/installing/) tool: Installing collected packages: pydpkg Successfully installed pydpkg-1.1 -Usage -===== +## Usage -Read and extract headers ------------------------- +### Binary Packages + +#### Read and extract headers >>> from pydpkg import Dpkg >>> dp = Dpkg('/tmp/testdeb_1:0.0.0-test_all.deb') @@ -55,16 +57,14 @@ Read and extract headers Description: testdeb a bogus debian package for testing dpkg builds -Interact directly with the package control message --------------------------------------------------- +#### Interact directly with the package control message >>> dp.message >>> dp.message.get_content_type() 'text/plain' -Get package file fingerprints ------------------------------ +#### Get package file fingerprints >>> dp.fileinfo {'sha256': '547500652257bac6f6bc83f0667d0d66c8abd1382c776c4de84b89d0f550ab7f', 'sha1': 'a5d28ae2f23e726a797349d7dd5f21baf8aa02b4', 'filesize': 910, 'md5': '149e61536a9fe36374732ec95cf7945d'} @@ -77,8 +77,7 @@ Get package file fingerprints >>> dp.filesize 910 -Get the components of the package version ------------------------------------------ +#### Get the components of the package version >>> d.epoch 1 @@ -87,8 +86,7 @@ Get the components of the package version >>> d.debian_revision u'test' -Get an arbitrary control header, case-independent -------------------------------------------------- +#### Get an arbitrary control header, case-independent >>> d.version u'1:0.0.0-test' @@ -102,8 +100,7 @@ Get an arbitrary control header, case-independent >>> d.get('nosuchheader', 'default') 'default' -Compare current version to a candidate version ----------------------------------------------- +#### Compare current version to a candidate version >>> dp.compare_version_with('1.0') 1 @@ -111,8 +108,7 @@ Compare current version to a candidate version >>> dp.compare_version_with('1:1.0') -1 -Compare two arbitrary version strings -------------------------------------- +#### Compare two arbitrary version strings >>> from pydpkg import Dpkg >>> ver_1 = '0:1.0-test1' @@ -120,15 +116,13 @@ Compare two arbitrary version strings >>> Dpkg.compare_versions(ver_1, ver_2) -1 -Use as a key function to sort a list of version strings -------------------------------------------------------- +#### Use as a key function to sort a list of version strings >>> from pydpkg import Dpkg >>> sorted(['0:1.0-test1', '1:0.0-test0', '0:1.0-test2'] , key=Dpkg.compare_versions_key) ['0:1.0-test1', '0:1.0-test2', '1:0.0-test0'] -Use the `dpkg-inspect.py` script to inspect packages ----------------------------------------------------- +#### Use the `dpkg-inspect.py` script to inspect packages $ dpkg-inspect.py ~/testdeb*deb Filename: /Home/n/testdeb_1:0.0.0-test_all.deb @@ -147,3 +141,91 @@ Use the `dpkg-inspect.py` script to inspect packages Description: testdeb a bogus debian package for testing dpkg builds +### Source Packages + +#### Read and extract headers + + >>> from pydpkg import Dsc + >>> dsc = Dsc('testdeb_0.0.0.dsc') + >>> dsc.headers + {'Uploaders': 'Nathan J. Mehl ', 'Binary': 'testdeb', 'Maintainer': 'Nathan J. Mehl ', 'Format': '3.0 (quilt)', 'Build-Depends': 'python (>= 2.6.6-3), debhelper (>= 9)', 'Source': 'testdeb', 'Version': '0.0.0-1', 'Standards-Version': '3.9.6', 'Architecture': 'all', 'Files': ' 142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz\n fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz', 'Checksums-Sha1': ' f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_0.0.0.orig.tar.gz\n cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz', 'Package-List': 'testdeb', 'Homepage': 'https://github.com/TheClimateCorporation', 'Checksums-Sha256': ' aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_0.0.0.orig.tar.gz\n 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_0.0.0-1.debian.tar.xz'} + +#### Interact directly with the dsc message + + >>> dsc.message + + >>> dsc.message.get_content_type() + 'text/plain' + >>> dsc.message.get('uploaders') + 'Nathan J. Mehl ' + +#### Render the dsc message as a string + + >>> print(dsc.message_str) + Format: 3.0 (quilt) + Source: testdeb + Binary: testdeb + Architecture: all + Version: 0.0.0-1 + Maintainer: Nathan J. Mehl + Uploaders: Nathan J. Mehl + Homepage: https://github.com/TheClimateCorporation + Standards-Version: 3.9.6 + Build-Depends: python (>= 2.6.6-3), debhelper (>= 9) + Package-List: testdeb + Checksums-Sha1: f250ac0a426b31df24fc2c98050f4fab90e456cd 280 + testdeb_0.0.0.orig.tar.gz + cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz + Checksums-Sha256: aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 + testdeb_0.0.0.orig.tar.gz + 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 + testdeb_0.0.0-1.debian.tar.xz + Files: 142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz + fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz + +#### List the package source files from the dsc + + >>> dsc.files + ['/home/n/testdeb_0.0.0.orig.tar.gz', 'home/n/testdeb_0.0.0-1.debian.tar.xz'] + +#### Validate that the package source files are present + + >>> dsc.missing_files + [] + >>> dsc.all_files_present + True + >>> dsc.validate() + >>> + + >>> bad = Dsc('testdeb_1.1.1-bad.dsc') + >>> bad.missing_files + ['/home/n/testdeb_1.1.1.orig.tar.gz', '/home/n/testdeb_1.1.1-1.debian.tar.xz'] + >>> bad.all_files_present + False + >>> bad.validate() + pydpkg.DscMissingFileError: ['/home/n/testdeb_1.1.1.orig.tar.gz', '/home/n/testdeb_1.1.1-1.debian.tar.xz'] + +#### Inspect the source file checksums from the dsc + + >>> pp(dsc.checksums) + {'sha1': {'/home/n/testdeb_0.0.0-1.debian.tar.xz': 'cb3474ff94053018957ebcf1d8a2b45f75dda449', + '/home/n/testdeb_0.0.0.orig.tar.gz': 'f250ac0a426b31df24fc2c98050f4fab90e456cd'}, + 'sha256': {'/home/n/testdeb_0.0.0-1.debian.tar.xz': '1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858', + '/home/n/testdeb_0.0.0.orig.tar.gz': 'aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7'}} + +#### Validate that all source file checksums are correct + + >>> dsc.corrected_checksums + {} + >>> dsc.all_checksums_correct + True + >>> dsc.validate() + >>> + + >>> bad = Dsc('testdeb_0.0.0-badchecksums.dsc') + >>> bad.corrected_checksums + {'sha256': defaultdict(None, {'/home/n/testdeb_0.0.0-1.debian.tar.xz': '1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858', '/home/n/testdeb_0.0.0.orig.tar.gz': 'aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7'}), 'sha1': defaultdict(None, {'/home/n/testdeb_0.0.0-1.debian.tar.xz': 'cb3474ff94053018957ebcf1d8a2b45f75dda449', '/home/n/testdeb_0.0.0.orig.tar.gz': 'f250ac0a426b31df24fc2c98050f4fab90e456cd'})} + >>> bad.all_checksums_correct + False + >>> bad.validate() + pydpkg.DscBadChecksumsError: {'sha256': defaultdict(None, {'/home/n/testdeb_0.0.0-1.debian.tar.xz': '1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858', '/home/n/testdeb_0.0.0.orig.tar.gz': 'aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7'}), 'sha1': defaultdict(None, {'/home/n/testdeb_0.0.0-1.debian.tar.xz': 'cb3474ff94053018957ebcf1d8a2b45f75dda449', '/home/n/testdeb_0.0.0.orig.tar.gz': 'f250ac0a426b31df24fc2c98050f4fab90e456cd'})} diff --git a/pydpkg/__init__.py b/pydpkg/__init__.py index 37c89b8..8257b9b 100644 --- a/pydpkg/__init__.py +++ b/pydpkg/__init__.py @@ -6,17 +6,20 @@ from __future__ import absolute_import # stdlib imports +import hashlib import io import logging import os import tarfile + +from collections import defaultdict from gzip import GzipFile -from hashlib import md5, sha1, sha256 -from email import message_from_string as Message +from email import message_from_string, message_from_file from functools import cmp_to_key # pypi imports import six +import pgpy from arpy import Archive REQUIRED_HEADERS = ('package', 'version', 'architecture') @@ -25,11 +28,16 @@ logging.basicConfig() class DpkgError(Exception): - """Base error class for pydpkg""" + """Base error class for Dpkg errors""" pass -class DpkgVersionError(Exception): +class DscError(Exception): + """Base error class for Dsc errors""" + pass + + +class DpkgVersionError(DpkgError): """Corrupt or unparseable version string""" pass @@ -49,6 +57,21 @@ class DpkgMissingRequiredHeaderError(DpkgError): pass +class DscMissingFileError(DscError): + """We were not able to find some of the files listed in the dsc""" + pass + + +class DscBadChecksumsError(DscError): + """Some of the files in the dsc have incorrect checksums""" + pass + + +class DscBadSignatureError(DscError): + """A dsc file has an invalid openpgp signature(s)""" + pass + + # pylint: disable=too-many-instance-attributes,too-many-public-methods class Dpkg(object): @@ -152,9 +175,9 @@ class Dpkg(object): :returns: dict """ if self._fileinfo is None: - h_md5 = md5() - h_sha1 = sha1() - h_sha256 = sha256() + h_md5 = hashlib.md5() + h_sha1 = hashlib.sha1() + h_sha256 = hashlib.sha256() with open(self.filename, 'rb') as dpkg_file: for chunk in iter(lambda: dpkg_file.read(128), b''): h_md5.update(chunk) @@ -296,7 +319,7 @@ class Dpkg(object): # py27 lacks email.message_from_bytes, so... if isinstance(message_body, bytes): message_body = message_body.decode('utf-8') - message = Message(message_body) + message = message_from_string(message_body) self._log.debug('got control message: %s', message) for req in REQUIRED_HEADERS: @@ -535,3 +558,245 @@ class Dpkg(object): function to a function suitable to passing to sorted() and friends as a key.""" return cmp_to_key(Dpkg.dstringcmp)(x) + + +class Dsc(object): + """Class allowing import and manipulation of a debian source + description (dsc) file.""" + def __init__(self, filename=None, logger=None): + self.filename = os.path.expanduser(filename) + self._dirname = os.path.dirname(self.filename) + self._log = logger or logging.getLogger(__name__) + self._message = None + self._files = None + self._sizes = None + self._message_str = None + self._checksums = None + self._corrected_checksums = None + self._pgp_message = None + + def __repr__(self): + return repr(self.message_str) + + def __str__(self): + return six.text_type(self.message_str) + + def __getattr__(self, attr): + """Overload getattr to treat message headers as object + attributes (so long as they do not conflict with an existing + attribute). + + :param attr: string + :returns: string + :raises: AttributeError + """ + # handle attributes with dashes :-( + munged = attr.replace('_', '-') + # beware: email.Message[nonexistent] returns None not KeyError + if munged in self.message: + return self.message[munged] + else: + raise AttributeError("'Dsc' object has no attribute '%s'" % attr) + + def __getitem__(self, item): + """Overload getitem to treat the message plus our local + properties as items. + + :param item: string + :returns: string + :raises: KeyError + """ + try: + return getattr(self, item) + except AttributeError: + try: + return self.__getattr__(item) + except AttributeError: + raise KeyError(item) + + def get(self, item, ret=None): + """Public wrapper for getitem""" + try: + return self.__getitem__(item) + except KeyError: + return ret + + @property + def message(self): + """Return an email.Message object containing the parsed dsc file""" + if self._message is None: + self._message = self._process_dsc_file() + return self._message + + @property + def headers(self): + """Return a dictionary of the message items""" + if self._message is None: + self._message = self._process_dsc_file() + return dict(self._message.items()) + + @property + def pgp_message(self): + """Return a pgpy.PGPMessage object containing the signed dsc + message (or None if the message is unsigned)""" + if self._message is None: + self._message = self._process_dsc_file() + return self._pgp_message + + @property + def files(self): + """Return a list of source files found in the dsc file""" + if self._files is None: + self._files = self._process_source_files() + return [x[0] for x in self._files] + + @property + def all_files_present(self): + """Return true if all files listed in the dsc have been found""" + if self._files is None: + self._files = self._process_source_files() + return all([x[2] for x in self._files]) + + @property + def all_checksums_correct(self): + """Return true if all checksums are correct""" + return not self.corrected_checksums + + @property + def corrected_checksums(self): + """Returns a dict of the CORRECT checksums in any case + where the ones provided by the dsc file are incorrect.""" + if self._corrected_checksums is None: + self._corrected_checksums = self._validate_checksums() + return self._corrected_checksums + + @property + def missing_files(self): + """Return a list of all files from the dsc that we failed to find""" + if self._files is None: + self._files = self._process_source_files() + return [x[0] for x in self._files if x[2] is False] + + @property + def sizes(self): + """Return a list of source files found in the dsc file""" + if self._files is None: + self._files = self._process_source_files() + return dict([(x[0], x[1]) for x in self._files]) + + @property + def message_str(self): + """Return the dsc message as a string + + :returns: string + """ + if self._message_str is None: + self._message_str = self.message.as_string() + return self._message_str + + @property + def checksums(self): + """Return a dictionary of checksums for the source files found + in the dsc file, keyed first by hash type and then by filename.""" + if self._checksums is None: + self._checksums = self._process_checksums() + return self._checksums + + def validate(self): + """Raise exceptions if files are missing or checksums are bad.""" + if not self.all_files_present: + raise DscMissingFileError( + [x[0] for x in self._files if not x[2]]) + if not self.all_checksums_correct: + raise DscBadChecksumsError(self.corrected_checksums) + + def _process_checksums(self): + """Walk through the dsc message looking for any keys in the + format 'Checksum-hashtype'. Return a nested dictionary in + the form {hashtype: {filename: {digest}}}""" + sums = {} + for key in self.message.keys(): + if key.lower().startswith('checksums'): + hashtype = key.split('-')[1].lower() + sums[hashtype] = {} + source = self.message[key] + for line in source.split('\n'): + if line: + digest, _, filename = line.strip().split(' ') + pathname = os.path.abspath( + os.path.join(self._dirname, filename)) + sums[hashtype][pathname] = digest + return sums + + def _process_dsc_file(self): + """Extract the dsc message from a file: parse the dsc body + and return an email.Message object. Attempt to extract the + RFC822 message from an OpenPGP message if necessary.""" + if not self.filename.endswith('.dsc'): + self._log.warning( + 'File %s does not appear to be a dsc file; pressing ' + 'on but we may experience some turbulence and possibly ' + 'explode.', self.filename) + try: + self._pgp_message = pgpy.PGPMessage.from_file(self.filename) + msg = message_from_string(self._pgp_message.message) + except TypeError as ex: + self._log.exception(ex) + self._log.fatal( + 'dsc file %s has a corrupt signature: %s', self.filename, ex) + raise DscBadSignatureError + # '%s has a corrupt signature' % self.filename) + except IOError as ex: + self._log.fatal('Could not read dsc file "%s": %s', + self.filename, ex) + raise + except (ValueError, pgpy.errors.PGPError) as ex: + self._log.warning('dsc file %s is not signed: %s', + self.filename, ex) + with open(self.filename) as fileobj: + msg = message_from_file(fileobj) + return msg + + def _process_source_files(self): + """Walk through the list of lines in the 'Files' section of + the dsc message, and verify that the file exists in the same + location on our filesystem as the dsc file. Return a list + of tuples: the normalized pathname for the file, the + size of the file (as claimed by the dsc) and whether the file + is actually present in the filesystem locally. + + Also extract the file size from the message lines and fill + out the _files dictionary. + """ + filenames = [] + try: + files = self.message['Files'] + except KeyError: + self._log.fatal('DSC file "%s" does not have a Files section', + self.filename) + raise + for line in files.split('\n'): + if line: + _, size, filename = line.strip().split(' ') + pathname = os.path.abspath( + os.path.join(self._dirname, filename)) + filenames.append( + (pathname, int(size), os.path.isfile(pathname))) + return filenames + + def _validate_checksums(self): + """Iterate over the dict of asserted checksums from the + dsc file. Check each in turn. If any checksum is invalid, + append the correct checksum to a similarly structured dict + and return them all at the end.""" + bad_hashes = defaultdict(lambda: defaultdict(None)) + for hashtype, filenames in six.iteritems(self.checksums): + for filename, digest in six.iteritems(filenames): + hasher = getattr(hashlib, hashtype)() + with open(filename, 'rb') as fileobj: + # pylint: disable=cell-var-from-loop + for chunk in iter(lambda: fileobj.read(128), b''): + hasher.update(chunk) + if hasher.hexdigest() != digest: + bad_hashes[hashtype][filename] = hasher.hexdigest() + return dict(bad_hashes) diff --git a/setup.py b/setup.py index 2ba9434..afa353f 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from distutils.core import setup -__VERSION__ = '1.2.1' +__VERSION__ = '1.3.0' setup( name='pydpkg', @@ -14,7 +14,8 @@ setup( keywords=['apt', 'debian', 'dpkg', 'packaging'], install_requires=[ 'arpy==1.1.1', - 'six==1.10.0' + 'six==1.10.0', + 'PGPy==0.4.1' ], extras_require={ 'test': ['pep8==1.7.0', 'pytest==3.1.1', 'pylint==1.7.1'] diff --git a/tests/test_dsc.py b/tests/test_dsc.py new file mode 100644 index 0000000..232a4ed --- /dev/null +++ b/tests/test_dsc.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import os +import unittest +import pytest +from email.message import Message + +from pydpkg import Dsc, DscMissingFileError, DscBadSignatureError, DscBadChecksumsError +from pgpy import PGPMessage + + +TEST_DSC_FILE = 'testdeb_0.0.0.dsc' +TEST_SIGNED_DSC_FILE = 'testdeb_0.0.0.dsc.asc' +TEST_BAD_DSC_FILE = 'testdeb_1.1.1-bad.dsc' +TEST_BAD_SIGNED_FILE = 'testdeb_1.1.1-bad.dsc.asc' +TEST_BAD_CHECKSUMS_FILE = 'testdeb_0.0.0-badchecksums.dsc' + + +class DscTest(unittest.TestCase): + def setUp(self): + goodfile = os.path.join(os.path.dirname(__file__), TEST_DSC_FILE) + signed = os.path.join(os.path.dirname(__file__), TEST_SIGNED_DSC_FILE) + badfile = os.path.join(os.path.dirname(__file__), TEST_BAD_DSC_FILE) + badsigned = os.path.join(os.path.dirname(__file__), TEST_BAD_SIGNED_FILE) + badchecksums = os.path.join(os.path.dirname(__file__), TEST_BAD_CHECKSUMS_FILE) + self.good = Dsc(goodfile) + self.signed = Dsc(signed) + self.bad = Dsc(badfile) + self.badsigned = Dsc(badsigned) + self.badchecksums = Dsc(badchecksums) + + def test_get_message_headers(self): + self.assertEqual(self.good.source, 'testdeb') + self.assertEqual(self.good.SOURCE, 'testdeb') + self.assertEqual(self.good['source'], 'testdeb') + self.assertEqual(self.good['SOURCE'], 'testdeb') + self.assertEqual(self.good.get('source'), 'testdeb') + self.assertEqual(self.good.get('SOURCE'), 'testdeb') + self.assertEqual(self.good.get('nonexistent'), None) + self.assertEqual(self.good.get('nonexistent', 'foo'), 'foo') + + def test_attr_munging(self): + self.assertEqual(self.good['package-list'], 'testdeb') + self.assertEqual(self.good.package_list, 'testdeb') + + def test_missing_header(self): + self.assertRaises(KeyError, self.good.__getitem__, 'xyzzy') + self.assertRaises(AttributeError, self.good.__getattr__, 'xyzzy') + + def test_message(self): + self.assertIsInstance(self.good.message, type(Message())) + + def test_found_files(self): + self.assertEqual( + self.good.files, + [os.path.join(os.path.dirname(__file__), + 'testdeb_0.0.0.orig.tar.gz'), + os.path.join(os.path.dirname(__file__), + 'testdeb_0.0.0-1.debian.tar.xz')] + ) + + def test_missing_files(self): + self.assertEqual(True, self.good.all_files_present) + self.assertEqual(False, self.bad.all_files_present) + self.assertEqual( + [os.path.join(os.path.dirname(__file__), + 'testdeb_1.1.1.orig.tar.gz'), + os.path.join(os.path.dirname(__file__), + 'testdeb_1.1.1-1.debian.tar.xz')], + self.bad.missing_files) + with pytest.raises(DscMissingFileError): + self.bad.validate() + + def test_pgp_validation(self): + self.assertEqual(None, self.good.pgp_message) + self.assertEqual(self.signed.source, 'testdeb') + with pytest.raises(DscBadSignatureError): + self.badsigned.files + self.assertIsInstance(self.signed.pgp_message, PGPMessage) + + def test_checksum_validation(self): + self.assertEqual(True, self.good.all_checksums_correct) + self.assertEqual(False, self.badchecksums.all_checksums_correct) + with pytest.raises(DscBadChecksumsError): + self.badchecksums.validate() + + +if __name__ == "__main__": + suite = unittest.TestLoader().loadTestsFromTestCase(DscTest) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/testdeb_0.0.0-1.debian.tar.xz b/tests/testdeb_0.0.0-1.debian.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..357b41b37f40deb2d6965089afa1b196aea591a2 GIT binary patch literal 232 zcmVuf_BuOauz~MZO26t+V zdx9Vi!?~YFF1mvBPZRc*M%VN?4BD`A$Tdd6upRiG-i&2WSGR$cWL8GgEhtB;MeQ_A2lW{BulD;3$fM=c=80TUHgBmr z)*)=bfTJ}SQ0Gk@UK76f=`m|eoLCq!%0^_CKK=}V%i8N5 +Uploaders: Nathan J. Mehl +Homepage: https://github.com/TheClimateCorporation +Standards-Version: 3.9.6 +Build-Depends: python (>= 2.6.6-3), debhelper (>= 9) +Package-List: testdeb +Checksums-Sha1: + 250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_0.0.0.orig.tar.gz + b3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz +Checksums-Sha256: + a57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_0.0.0.orig.tar.gz + ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_0.0.0-1.debian.tar.xz +Files: + 42ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz + c80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz + diff --git a/tests/testdeb_0.0.0.dsc b/tests/testdeb_0.0.0.dsc new file mode 100644 index 0000000..239d059 --- /dev/null +++ b/tests/testdeb_0.0.0.dsc @@ -0,0 +1,21 @@ +Format: 3.0 (quilt) +Source: testdeb +Binary: testdeb +Architecture: all +Version: 0.0.0-1 +Maintainer: Nathan J. Mehl +Uploaders: Nathan J. Mehl +Homepage: https://github.com/TheClimateCorporation +Standards-Version: 3.9.6 +Build-Depends: python (>= 2.6.6-3), debhelper (>= 9) +Package-List: testdeb +Checksums-Sha1: + f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_0.0.0.orig.tar.gz + cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz +Checksums-Sha256: + aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_0.0.0.orig.tar.gz + 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_0.0.0-1.debian.tar.xz +Files: + 142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz + fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz + diff --git a/tests/testdeb_0.0.0.dsc.asc b/tests/testdeb_0.0.0.dsc.asc new file mode 100644 index 0000000..0775e53 --- /dev/null +++ b/tests/testdeb_0.0.0.dsc.asc @@ -0,0 +1,34 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +Format: 3.0 (quilt) +Source: testdeb +Binary: testdeb +Architecture: all +Version: 0.0.0-1 +Maintainer: Nathan J. Mehl +Uploaders: Nathan J. Mehl +Homepage: https://github.com/TheClimateCorporation +Standards-Version: 3.9.6 +Build-Depends: python (>= 2.6.6-3), debhelper (>= 9) +Package-List: testdeb +Checksums-Sha1: + f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_0.0.0.orig.tar.gz + cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz +Checksums-Sha256: + aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_0.0.0.orig.tar.gz + 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_0.0.0-1.debian.tar.xz +Files: + 142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz + fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz + +-----BEGIN PGP SIGNATURE----- + +iQEcBAEBCAAGBQJZSyK8AAoJEDBtfbmvfY+UTc4H/RMC3nwCM4JF227bxR2+uxTI +UwYncODHRqrhwp31ntQNMEtahSBipY75ockymLSsZEkNYhDSE5WcLv5gDybpAt5P +18BA7sn+K3Y99L7eNhX4Tsn4Gs+ip0Z1QdHhBHURMbGQfayqWgATV0z7Xg9Uo2GA +hgolnXtpNNchl1RMjwR9e2urqQZhzbiwGhsigBnj8F+v4Gl7etr6BmOricIKp61w +rBGNzXrGPR80xSdNr5iFwyRpKLvobAkJqbVxSlCLbRf5nXH1H96p7Q5EyaVEecdF +X2sK95GJlwGveQV4hoe0Lq5c97q2qKo7ZaXIL8FRQkG545fS/HsictgetPkyIsw= +=goqi +-----END PGP SIGNATURE----- diff --git a/tests/testdeb_0.0.0.orig.tar.gz b/tests/testdeb_0.0.0.orig.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..474b5845f15e40094dc95b936dd43bbaeb5d5933 GIT binary patch literal 280 zcmb2|=3uz^#4D14`R%3MS%(co*dDC87%XED8ymYwCFR_O>k~wOI>~BA7^&vR-@I@o z_Ll0h)Bh!l5A4@F?)0*%I@j5!aI@UGpLN?8uax^%weDv4)0j+OCr-_;aas#IxK~cO zClJ4JdAp&S@sqGW@{jGJp8tyfG~eFU|4l)t>p`xn)8Y;1)MlP?IlfH4cEhsy?^sJG z9($oTh3V#l*XtzN-B*@Ydh7^2wlDbkWa+Hr&pxM?KY4C)FS+P?W|^qdtD0b;xy(N& zoY;2E{IZl~i5>6vNl*1!Y^K=QC;h%L+jH&dqr2C(CFor{VzPYs +Uploaders: Nathan J. Mehl +Homepage: https://github.com/TheClimateCorporation +Standards-Version: 3.9.6 +Build-Depends: python (>= 2.6.6-3), debhelper (>= 9) +Package-List: + testdeb deb admin optional arch=all +Checksums-Sha1: + f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_1.1.1.orig.tar.gz + cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_1.1.1-1.debian.tar.xz +Checksums-Sha256: + aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_1.1.1.orig.tar.gz + 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_1.1.1-1.debian.tar.xz +Files: + 142ca7334ed1f70302b4504566e0c233 280 testdeb_1.1.1.orig.tar.gz + fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_1.1.1-1.debian.tar.xz + diff --git a/tests/testdeb_1.1.1-bad.dsc.asc b/tests/testdeb_1.1.1-bad.dsc.asc new file mode 100644 index 0000000..c9e2b0b --- /dev/null +++ b/tests/testdeb_1.1.1-bad.dsc.asc @@ -0,0 +1,34 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +Format: 3.0 (quilt) +Source: testdeb +Binary: testdeb +Architecture: all +Version: 1.1.1-1 +Maintainer: Nathan J. Mehl +Uploaders: Nathan J. Mehl +Homepage: https://github.com/TheClimateCorporation +Standards-Version: 3.9.6 +Build-Depends: python (>= 2.6.6-3), debhelper (>= 9) +Package-List: + testdeb deb admin optional arch=all +Checksums-Sha1: + f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_1.1.1.orig.tar.gz + cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_1.1.1-1.debian.tar.xz +Checksums-Sha256: + aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_1.1.1.orig.tar.gz + 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_1.1.1-1.debian.tar.xz +Files: + 142ca7334ed1f70302b4504566e0c233 280 testdeb_1.1.1.orig.tar.gz + fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_1.1.1-1.debian.tar.xz +-----BEGIN PGP SIGNATURE----- + +iQEcBAEBCAAGBQJZSyL/AAoJEDBtfbmvfY+UH6EIALzdWgPraB/8xrzSum/g600+ +lPGrObE1vgtH0IdkWyBhLwZKZGSgeEI7U11EOC+z78ULTTDafjeXZDWTPhO+F23d +VFSOTHnzhqTtoqa1lT4qL2A6kWCc3QxpFtI6dLuZtXFlEEKk9w2tu/GzSWtuzVKQ +qNRDdLsiFz7kPwMWjvlaqVjz5wqtY3j5TpYpVH8uqtrCuzaycGjzTgMLxHa+o2T+ +AHYnZLRN22hLb1DeFaZDkNiC6qgx45FV98jh0sYTZA7A15MbwCvNLD0uws4ICwPl +8ex3e0rf4FlpUuMNJTQmShuxaMGlbwtzKFy75SxQxxJ0r+7TL4/B37vBE9+BJqs= +=Of1F +-----END PGP SIGNATURE-----