Skip to content

Commit 063a468

Browse files
committed
Add support for Openstack Advisories
Signed-off-by: Samk <sampurnapyne1710@gmail.com>
1 parent f94ff10 commit 063a468

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

src/univers/version_range.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#
55
# Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download.
66

7+
import re
78
from typing import List
89
from typing import Union
910

@@ -772,6 +773,71 @@ def from_native(cls, string):
772773

773774
return cls(constraints=constraints)
774775

776+
@classmethod
777+
def from_ossa_native(cls, string):
778+
"""
779+
Returns a PypiVersionRange built from an OpenStack Security Advisory (OSSA) version constraint ``string``.
780+
781+
See: https://github.com/openstack/ossa
782+
783+
For example::
784+
785+
>>> str(PypiVersionRange.from_ossa_native("<=5.0.3, >=6.0.0 <=6.1.0 and ==7.0.0"))
786+
'vers:pypi/<=5.0.3|>=6.0.0|<=6.1.0|7.0.0'
787+
788+
>>> str(PypiVersionRange.from_ossa_native("<=14.0.10, >=15.0.0 <=15.0.8, >=16.0.0 <=16.0.3"))
789+
'vers:pypi/<=14.0.10|>=15.0.0|<=15.0.8|>=16.0.0|<=16.0.3'
790+
791+
>>> str(PypiVersionRange.from_ossa_native("<20.2.1, >=21.0.0 <21.2.1, ==22.0.0"))
792+
'vers:pypi/<20.2.1|>=21.0.0|<21.2.1|22.0.0'
793+
"""
794+
795+
# Normalize "and" keyword to comma
796+
# "<=5.0.3, >=6.0.0 <=6.1.0 and ==7.0.0" -> "<=5.0.3, >=6.0.0 <=6.1.0, ==7.0.0"
797+
string = string.replace(" and ", ",")
798+
799+
# Remove spaces around operators
800+
# "<=5.0.3, >=6.0.0 <=6.1.0, ==7.0.0" -> "<=5.0.3,>=6.0.0<=6.1.0,==7.0.0"
801+
string = re.sub(r"\s+([<>=!]+)", r"\1", string)
802+
string = re.sub(r"([<>=!]+)\s+", r"\1", string)
803+
804+
# Insert comma between consecutive constraints
805+
# "<=5.0.3,>=6.0.0<=6.1.0,==7.0.0" -> "<=5.0.3,>=6.0.0,<=6.1.0,==7.0.0"
806+
string = re.sub(r"(\d)([<>=!])", r"\1,\2", string)
807+
808+
constraints = []
809+
for part in string.split(","):
810+
811+
# Default to exact match for bare version numbers
812+
# "1.16.0" -> "=1.16.0"
813+
comparator = "="
814+
version = part
815+
816+
for op, vers_op in cls.vers_by_native_comparators.items():
817+
if part.startswith(op):
818+
comparator = vers_op
819+
version = part[len(op) :]
820+
break
821+
822+
# Handle bare "=" for exact match
823+
# "=18.0.0" -> "18.0.0"
824+
if version.startswith("="):
825+
version = version[1:]
826+
827+
try:
828+
constraints.append(
829+
VersionConstraint(
830+
comparator=comparator,
831+
version=cls.version_class(version),
832+
)
833+
)
834+
except (ValueError, TypeError) as e:
835+
raise InvalidVersionRange(
836+
f"Invalid version constraint {part!r} in OSSA version string {string!r}: {e}"
837+
) from e
838+
839+
return cls(constraints=constraints)
840+
775841

776842
class MavenVersionRange(VersionRange):
777843
"""

tests/test_version_range.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,48 @@ def test_PypiVersionRange_raises_ivr_for_unsupported_and_invalid_ranges(range, w
281281
assert expected == str(PypiVersionRange.from_native(range))
282282

283283

284+
@pytest.mark.parametrize(
285+
"string, expected",
286+
[
287+
( # OSSA-2016-013
288+
"<=5.0.3, >=6.0.0 <=6.1.0 and ==7.0.0",
289+
"vers:pypi/<=5.0.3|>=6.0.0|<=6.1.0|7.0.0",
290+
),
291+
( # OSSA-2017-005
292+
"<=14.0.10, >=15.0.0 <=15.0.8, >=16.0.0 <=16.0.3",
293+
"vers:pypi/<=14.0.10|>=15.0.0|<=15.0.8|>=16.0.0|<=16.0.3",
294+
),
295+
( # OSSA-2019-003
296+
"<17.0.12, >=18.0.0 <18.2.2, >=19.0.0 <19.0.2",
297+
"vers:pypi/<17.0.12|>=18.0.0|<18.2.2|>=19.0.0|<19.0.2",
298+
),
299+
( # OSSA-2020-006
300+
"<19.3.1, >=20.0.0 <20.3.1, ==21.0.0",
301+
"vers:pypi/<19.3.1|>=20.0.0|<20.3.1|21.0.0",
302+
),
303+
( # OSSA-2026-001
304+
">=10.5.0 <10.7.2, >=10.8.0 <10.9.1, >=10.10.0 <10.12.1",
305+
"vers:pypi/>=10.5.0|<10.7.2|>=10.8.0|<10.9.1|>=10.10.0|<10.12.1",
306+
),
307+
( # OSSA-2021-001
308+
"<16.3.3, >=17.0.0 <17.1.3, =18.0.0",
309+
"vers:pypi/<16.3.3|>=17.0.0|<17.1.3|18.0.0",
310+
),
311+
( # empty string should raise InvalidVersionRange
312+
"",
313+
"InvalidVersionRange",
314+
),
315+
],
316+
)
317+
def test_PypiVersionRange_from_ossa_native(string, expected):
318+
if expected == "InvalidVersionRange":
319+
with pytest.raises(InvalidVersionRange):
320+
PypiVersionRange.from_ossa_native(string)
321+
else:
322+
result = PypiVersionRange.from_ossa_native(string)
323+
assert expected == str(result)
324+
325+
284326
def test_invert():
285327
vers_with_equal_operator = VersionRange.from_string("vers:gem/1.0")
286328
assert str(vers_with_equal_operator.invert()) == "vers:gem/!=1.0"

0 commit comments

Comments
 (0)