summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Drake <michael.drake@codethink.co.uk>2015-04-16 14:24:08 +0000
committerMichael Drake <michael.drake@codethink.co.uk>2015-04-16 14:24:08 +0000
commit75c3c7df9b02ceded0e77f41eb019b3db0b46ac2 (patch)
tree31d4526de12e903903abedc2659e7b5be7111e6c
parent408c622fa5edaa71dc82b39904ef6a8fba452586 (diff)
downloadmorph-75c3c7df9b02ceded0e77f41eb019b3db0b46ac2.tar.gz
Add debian version comparason code.
Change-Id: I81e96588b90763e57e2ad6d8ab9f8a4abd7db8e6
-rw-r--r--morphlib/plugins/cve_check_plugin.py221
1 files changed, 221 insertions, 0 deletions
diff --git a/morphlib/plugins/cve_check_plugin.py b/morphlib/plugins/cve_check_plugin.py
index 33b2bd24..73351924 100644
--- a/morphlib/plugins/cve_check_plugin.py
+++ b/morphlib/plugins/cve_check_plugin.py
@@ -262,3 +262,224 @@ class VersionGuesser(object):
# Fall back to `git describe` which always gives something
return cached.version_guess(ref)
+
+
+#------------------------------------------------------------------------------#
+# Beyond here is code from the `python-debian` module.
+#------------------------------------------------------------------------------#
+
+# debian_support.py -- Python module for Debian metadata
+# Copyright (C) 2005 Florian Weimer <fw@deneb.enyo.de>
+# Copyright (C) 2010 John Wright <jsw@debian.org>
+
+"""This module implements facilities to deal with Debian-specific metadata."""
+
+# From python-debian (GPL2)
+class BaseVersion(object):
+ """Base class for classes representing Debian versions
+
+ It doesn't implement any comparison, but it does check for valid versions
+ according to Section 5.6.12 in the Debian Policy Manual. Since splitting
+ the version into epoch, upstream_version, and debian_revision components is
+ pretty much free with the validation, it sets those fields as properties of
+ the object, and sets the raw version to the full_version property. A
+ missing epoch or debian_revision results in the respective property set to
+ None. Setting any of the properties results in the full_version being
+ recomputed and the rest of the properties set from that.
+
+ It also implements __str__, just returning the raw version given to the
+ initializer.
+ """
+
+ re_valid_version = re.compile(
+ r"^((?P<epoch>\d+):)?"
+ "(?P<upstream_version>[A-Za-z0-9.+:~-]+?)"
+ "(-(?P<debian_revision>[A-Za-z0-9+.~]+))?$")
+ magic_attrs = ('full_version', 'epoch', 'upstream_version',
+ 'debian_revision', 'debian_version')
+
+ def __init__(self, version):
+ self.full_version = version
+
+ def _set_full_version(self, version):
+ m = self.re_valid_version.match(version)
+ if not m:
+ raise ValueError("Invalid version string %r" % version)
+ # If there no epoch ("1:..."), then the upstream version can not
+ # contain a :.
+ if (m.group("epoch") is None and ":" in m.group("upstream_version")):
+ raise ValueError("Invalid version string %r" % version)
+
+ self.__full_version = version
+ self.__epoch = m.group("epoch")
+ self.__upstream_version = m.group("upstream_version")
+ self.__debian_revision = m.group("debian_revision")
+
+ def __setattr__(self, attr, value):
+ if attr not in self.magic_attrs:
+ super(BaseVersion, self).__setattr__(attr, value)
+ return
+
+ # For compatibility with the old changelog.Version class
+ if attr == "debian_version":
+ attr = "debian_revision"
+
+ if attr == "full_version":
+ self._set_full_version(str(value))
+ else:
+ if value is not None:
+ value = str(value)
+ private = "_BaseVersion__%s" % attr
+ old_value = getattr(self, private)
+ setattr(self, private, value)
+ try:
+ self._update_full_version()
+ except ValueError:
+ # Don't leave it in an invalid state
+ setattr(self, private, old_value)
+ self._update_full_version()
+ raise ValueError("Setting %s to %r results in invalid version"
+ % (attr, value))
+
+ def __getattr__(self, attr):
+ if attr not in self.magic_attrs:
+ return super(BaseVersion, self).__getattribute__(attr)
+
+ # For compatibility with the old changelog.Version class
+ if attr == "debian_version":
+ attr = "debian_revision"
+
+ private = "_BaseVersion__%s" % attr
+ return getattr(self, private)
+
+ def _update_full_version(self):
+ version = ""
+ if self.__epoch is not None:
+ version += self.__epoch + ":"
+ version += self.__upstream_version
+ if self.__debian_revision:
+ version += "-" + self.__debian_revision
+ self.full_version = version
+
+ def __str__(self):
+ return self.full_version
+
+ def __repr__(self):
+ return "%s('%s')" % (self.__class__.__name__, self)
+
+ def _compare(self, other):
+ raise NotImplementedError
+
+ # TODO: Once we support only Python >= 2.7, we can simplify this using
+ # @functools.total_ordering.
+
+ def __lt__(self, other):
+ return self._compare(other) < 0
+
+ def __le__(self, other):
+ return self._compare(other) <= 0
+
+ def __eq__(self, other):
+ return self._compare(other) == 0
+
+ def __ne__(self, other):
+ return self._compare(other) != 0
+
+ def __ge__(self, other):
+ return self._compare(other) >= 0
+
+ def __gt__(self, other):
+ return self._compare(other) > 0
+
+ def __hash__(self):
+ return hash(str(self))
+
+
+# From python-debian (GPL2)
+# Version based on the DpkgVersion class by Raphael Hertzog in
+# svn://svn.debian.org/qa/trunk/pts/www/bin/common.py r2361
+class Version(BaseVersion):
+ """Represents a Debian package version, with native Python comparison"""
+
+ re_all_digits_or_not = re.compile("\d+|\D+")
+ re_digits = re.compile("\d+")
+ re_digit = re.compile("\d")
+ re_alpha = re.compile("[A-Za-z]")
+
+ def _compare(self, other):
+ # Convert other into an instance of BaseVersion if it's not already.
+ # (All we need is epoch, upstream_version, and debian_revision
+ # attributes, which BaseVersion gives us.) Requires other's string
+ # representation to be the raw version.
+ if not isinstance(other, BaseVersion):
+ try:
+ other = BaseVersion(str(other))
+ except ValueError as e:
+ raise ValueError("Couldn't convert %r to BaseVersion: %s"
+ % (other, e))
+
+ lepoch = int(self.epoch or "0")
+ repoch = int(other.epoch or "0")
+ if lepoch < repoch:
+ return -1
+ elif lepoch > repoch:
+ return 1
+ res = self._version_cmp_part(self.upstream_version,
+ other.upstream_version)
+ if res != 0:
+ return res
+ return self._version_cmp_part(self.debian_revision or "0",
+ other.debian_revision or "0")
+
+ @classmethod
+ def _order(cls, x):
+ """Return an integer value for character x"""
+ if x == '~':
+ return -1
+ elif cls.re_digit.match(x):
+ return int(x) + 1
+ elif cls.re_alpha.match(x):
+ return ord(x)
+ else:
+ return ord(x) + 256
+
+ @classmethod
+ def _version_cmp_string(cls, va, vb):
+ la = [cls._order(x) for x in va]
+ lb = [cls._order(x) for x in vb]
+ while la or lb:
+ a = 0
+ b = 0
+ if la:
+ a = la.pop(0)
+ if lb:
+ b = lb.pop(0)
+ if a < b:
+ return -1
+ elif a > b:
+ return 1
+ return 0
+
+ @classmethod
+ def _version_cmp_part(cls, va, vb):
+ la = cls.re_all_digits_or_not.findall(va)
+ lb = cls.re_all_digits_or_not.findall(vb)
+ while la or lb:
+ a = "0"
+ b = "0"
+ if la:
+ a = la.pop(0)
+ if lb:
+ b = lb.pop(0)
+ if cls.re_digits.match(a) and cls.re_digits.match(b):
+ a = int(a)
+ b = int(b)
+ if a < b:
+ return -1
+ elif a > b:
+ return 1
+ else:
+ res = cls._version_cmp_string(a, b)
+ if res != 0:
+ return res
+ return 0