diff options
author | Michael Drake <michael.drake@codethink.co.uk> | 2015-04-16 14:24:08 +0000 |
---|---|---|
committer | Michael Drake <michael.drake@codethink.co.uk> | 2015-04-16 14:24:08 +0000 |
commit | 75c3c7df9b02ceded0e77f41eb019b3db0b46ac2 (patch) | |
tree | 31d4526de12e903903abedc2659e7b5be7111e6c | |
parent | 408c622fa5edaa71dc82b39904ef6a8fba452586 (diff) | |
download | morph-75c3c7df9b02ceded0e77f41eb019b3db0b46ac2.tar.gz |
Add debian version comparason code.
Change-Id: I81e96588b90763e57e2ad6d8ab9f8a4abd7db8e6
-rw-r--r-- | morphlib/plugins/cve_check_plugin.py | 221 |
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 |