Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,39 @@ def from_native(cls, string):
return cls(constraints=constraints)


class OpensslVersionRange(VersionRange):
"""
Openssl version range.
openssl doesn't use <,>,<= or >=
For more see 'https://www.openssl.org/news/vulnerabilities.xml'

For exmaple::
Comment thread
keshav-space marked this conversation as resolved.
Outdated
>>> from univers.versions import OpensslVersion
>>> constraints = (
... VersionConstraint(version=OpensslVersion("1.0.1af")),
... VersionConstraint(comparator="=", version=OpensslVersion("3.0.1")),
... VersionConstraint(comparator="=", version=OpensslVersion("1.1.1nf")),
... )
>>> range = OpensslVersionRange(constraints=constraints)
>>> assert str(range) == 'vers:openssl/1.0.1af|1.1.1nf|3.0.1'
"""

scheme = "openssl"
version_class = versions.OpensslVersion
vers_by_native_comparators = {"=": "="}
Comment thread
keshav-space marked this conversation as resolved.
Outdated

@classmethod
def from_native(cls, string):
cleaned = remove_spaces(string).lower()
constraints = set()
Comment thread
keshav-space marked this conversation as resolved.
Outdated
# plain single version
Comment thread
keshav-space marked this conversation as resolved.
Outdated
for clause in cleaned.split(","):
Comment thread
keshav-space marked this conversation as resolved.
Outdated
version = cls.version_class(clause)
constraint = VersionConstraint(comparator="=", version=version)
constraints.add(constraint)
return cls(constraints=list(constraints))


def is_even(s):
"""
Return True if the string "s" is an even number and False if this is an odd
Expand Down Expand Up @@ -902,4 +935,5 @@ def is_even(s):
"ebuild": EbuildVersionRange,
"archlinux": ArchLinuxVersionRange,
"nginx": NginxVersionRange,
"openssl": OpensslVersionRange,
}
170 changes: 170 additions & 0 deletions src/univers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,173 @@ def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return gentoo.vercmp(self.value, other.value) > 0


@attr.s(frozen=True, order=False, eq=False, hash=True)
class LegacyOpensslVersion(Version):
Comment thread
keshav-space marked this conversation as resolved.
"""
Represent an Legacy Openssl Version .

For example::

Comment thread
keshav-space marked this conversation as resolved.
Outdated
Comment thread
keshav-space marked this conversation as resolved.
Outdated
# 1.0.1f|0.9.7d|1.0.2ac
Comment thread
keshav-space marked this conversation as resolved.
Outdated
"""

@classmethod
def is_valid(cls, string):
return bool(cls.parse(string))

@classmethod
def parse(cls, string):

Comment thread
keshav-space marked this conversation as resolved.
Outdated
"""
Returns the tuple containig the 4 segments (i.e major, minor, build, patch) of Legacy Version,
Comment thread
keshav-space marked this conversation as resolved.
Outdated
False if not valid Legacy Openssl Version.

For example::
>>> LegacyOpensslVersion.parse("1.0.1f")
(1, 0, 1, 'f')
>>> LegacyOpensslVersion.parse("1.0.2ac")
(1, 0, 2, 'ac')
>>> LegacyOpensslVersion.parse("2.0.2az")
False
"""
Comment thread
keshav-space marked this conversation as resolved.

# All legacy base version of openssl that ever exited/exists.
Comment thread
keshav-space marked this conversation as resolved.
Outdated
all_legacy_base = (
"0.9.1",
"0.9.2",
"0.9.3",
"0.9.4",
"0.9.5",
"0.9.6",
"0.9.7",
"0.9.8",
"1.0.0",
"1.0.1",
"1.0.2",
"1.1.0",
"1.1.1",
)
# check if starts with a valid base
Comment thread
keshav-space marked this conversation as resolved.
Outdated
if not string.startswith(all_legacy_base):
return False

segments = string.split(".")
if not len(segments) == 3:
return False
major, minor, build = segments
major = int(major)
minor = int(minor)
if build.isdigit():
build = int(build)
patch = ""
else:
patch = build[1:]
build = int(build[0])
if patch and patch[0].isdigit():
Comment thread
keshav-space marked this conversation as resolved.
Outdated
return False
return major, minor, build, patch

@classmethod
def build_value(cls, string):
return cls.parse(string)

def __str__(self):
return self.normalized_string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about instead:

Suggested change
return self.normalized_string
return f"{self.major}.{self.minor}.{self.build}{self.patch}"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems more pragmatic...👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't work.

>>> from univers.versions import LegacyOpensslVersion
>>> vers=LegacyOpensslVersion("1.0.2a")
>>> str(vers)
Traceback (most recent call last):
    ...
AttributeError: 'LegacyOpensslVersion' object has no attribute 'major'

We can have something like this

return f"{self.value[0]}.{self.value[1]}.{self.value[2]}{self.value[3]}"



@attr.s(frozen=True, order=False, eq=False, hash=True)
class OpensslVersion(Version):

Comment thread
keshav-space marked this conversation as resolved.
Outdated
"""
Internally tracks two types of openssl versions
- Legacy versions: Implemented in LegacyOpensslVersion
Comment thread
keshav-space marked this conversation as resolved.
Outdated
- New versions: Semver
Comment thread
keshav-space marked this conversation as resolved.
Outdated
For example::
>>> old = OpensslVersion("1.1.0f")
>>> new = OpensslVersion("3.0.1")
>>> assert old == OpensslVersion(string="1.1.0f")
>>> assert new == OpensslVersion(string="3.0.1")
>>> assert old.value == LegacyOpensslVersion(string="1.1.0f")
>>> assert new.value == SemverVersion(string="3.0.1")
>>> OpensslVersion("1.2.4fg")
Traceback (most recent call last):
...
univers.versions.InvalidVersion: '1.2.4fg' is not a valid <class 'univers.versions.OpensslVersion'>
"""
Comment thread
keshav-space marked this conversation as resolved.

@classmethod
def is_valid(cls, string):
return cls.is_valid_new(string) or cls.is_valid_legacy(string)

@classmethod
def build_value(cls, string):
"""
Return a wrapped version "value" object depending on
whether version is legacy or semver.
"""
if cls.is_valid_legacy(string):
return LegacyOpensslVersion(string)
if cls.is_valid_new(string):
return SemverVersion(string)

@classmethod
def is_valid_new(cls, string):
"""
Checks the validity of new Openssl Version.
Comment thread
keshav-space marked this conversation as resolved.
Outdated

For example::
>>> OpensslVersion.is_valid_new("1.0.1f")
False
>>> OpensslVersion.is_valid_new("3.0.0")
True
>>> OpensslVersion.is_valid_new("3.0.2")
True
"""
Comment thread
keshav-space marked this conversation as resolved.
if SemverVersion.is_valid(string):
sem = semantic_version.Version.coerce(string)
return sem.major >= 3

@classmethod
def is_valid_legacy(cls, string):
return LegacyOpensslVersion.is_valid(string)

def __eq__(self, other):
Comment thread
keshav-space marked this conversation as resolved.
if not isinstance(other, self.__class__):
return NotImplemented
if not isinstance(other.value, self.value.__class__):
return NotImplemented
return self.value.__eq__(other.value)

def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__lt__(other.value)
# By construction legacy version is always behind Semver
return isinstance(self.value, LegacyOpensslVersion)

def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__gt__(other.value)
# By construction semver version is always ahead of legacy
return isinstance(self.value, SemverVersion)

def __le__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__le__(other.value)
# version value are of diff type, then legacy one is always behind semver
return isinstance(self.value, LegacyOpensslVersion)

def __ge__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if isinstance(other.value, self.value.__class__):
return self.value.__ge__(other.value)
# version value are of diff type, then semver one is always ahead of legacy
return isinstance(self.value, SemverVersion)
32 changes: 32 additions & 0 deletions tests/test_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
from univers.version_range import VersionRange
from univers.version_range import RANGE_CLASS_BY_SCHEMES
from univers.version_range import NpmVersionRange
from univers.version_range import OpensslVersionRange
from univers.versions import PypiVersion
from univers.versions import RubygemsVersion
from univers.versions import SemverVersion
from univers.versions import OpensslVersion


class TestVersionRange(TestCase):
Expand Down Expand Up @@ -233,10 +235,40 @@ def test_NpmVersionRange_from_native_with_approximately_equal_to_operator(self):
version_range = NpmVersionRange.from_native(npm_range)
assert version_range == expected

def test_OpensslVersionRange_from_native_single_legacy(self):
openssl_range = "0.9.8j"
expected = OpensslVersionRange(
constraints=(
VersionConstraint(comparator="=", version=OpensslVersion(string="0.9.8j")),
)
)
version_range = OpensslVersionRange.from_native(openssl_range)
assert version_range == expected

def test_OpensslVersionRange_from_native_single_new_semver(self):
openssl_range = "3.0.1"
expected = OpensslVersionRange(
constraints=(VersionConstraint(comparator="=", version=OpensslVersion(string="3.0.1")),)
)
version_range = OpensslVersionRange.from_native(openssl_range)
assert version_range == expected

def test_OpensslVersionRange_from_native_mixed(self):
openssl_range = "3.0.0, 1.0.1b"
expected = OpensslVersionRange(
constraints=(
VersionConstraint(comparator="=", version=OpensslVersion(string="1.0.1b")),
VersionConstraint(comparator="=", version=OpensslVersion(string="3.0.0")),
)
)
version_range = OpensslVersionRange.from_native(openssl_range)
assert version_range == expected


VERSION_RANGE_TESTS_BY_SCHEME = {
"nginx": ["0.8.40+", "0.7.52-0.8.39", "0.9.10", "1.5.0+, 1.4.1+"],
"npm": ["^1.2.9", "~3.8.2", "5.0.0 - 7.2.3", "2.1 || 2.6", "1.1.2 1.2.2", "<=2.1 >=1.1"],
"openssl": ["1.1.1ak", "1.1.0", "3.0.2", "3.0.1, 0.9.7a", "1.0.2ck, 3.1.2"],
}


Expand Down