Skip to content

Commit fa06361

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

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
@@ -255,6 +255,48 @@ def test_PypiVersionRange_raises_ivr_for_unsupported_and_invalid_ranges(range, w
255255
assert expected == str(PypiVersionRange.from_native(range))
256256

257257

258+
@pytest.mark.parametrize(
259+
"string, expected",
260+
[
261+
( # OSSA-2016-013
262+
"<=5.0.3, >=6.0.0 <=6.1.0 and ==7.0.0",
263+
"vers:pypi/<=5.0.3|>=6.0.0|<=6.1.0|7.0.0",
264+
),
265+
( # OSSA-2017-005
266+
"<=14.0.10, >=15.0.0 <=15.0.8, >=16.0.0 <=16.0.3",
267+
"vers:pypi/<=14.0.10|>=15.0.0|<=15.0.8|>=16.0.0|<=16.0.3",
268+
),
269+
( # OSSA-2019-003
270+
"<17.0.12, >=18.0.0 <18.2.2, >=19.0.0 <19.0.2",
271+
"vers:pypi/<17.0.12|>=18.0.0|<18.2.2|>=19.0.0|<19.0.2",
272+
),
273+
( # OSSA-2020-006
274+
"<19.3.1, >=20.0.0 <20.3.1, ==21.0.0",
275+
"vers:pypi/<19.3.1|>=20.0.0|<20.3.1|21.0.0",
276+
),
277+
( # OSSA-2026-001
278+
">=10.5.0 <10.7.2, >=10.8.0 <10.9.1, >=10.10.0 <10.12.1",
279+
"vers:pypi/>=10.5.0|<10.7.2|>=10.8.0|<10.9.1|>=10.10.0|<10.12.1",
280+
),
281+
( # OSSA-2021-001
282+
"<16.3.3, >=17.0.0 <17.1.3, =18.0.0",
283+
"vers:pypi/<16.3.3|>=17.0.0|<17.1.3|18.0.0",
284+
),
285+
( # empty string should raise InvalidVersionRange
286+
"",
287+
"InvalidVersionRange",
288+
),
289+
],
290+
)
291+
def test_PypiVersionRange_from_ossa_native(string, expected):
292+
if expected == "InvalidVersionRange":
293+
with pytest.raises(InvalidVersionRange):
294+
PypiVersionRange.from_ossa_native(string)
295+
else:
296+
result = PypiVersionRange.from_ossa_native(string)
297+
assert expected == str(result)
298+
299+
258300
def test_invert():
259301
vers_with_equal_operator = VersionRange.from_string("vers:gem/1.0")
260302
assert str(vers_with_equal_operator.invert()) == "vers:gem/!=1.0"

0 commit comments

Comments
 (0)