Skip to content

Commit 536dd91

Browse files
committed
add utilities for vulntotal
Signed-off-by: Keshav Priyadarshi <[email protected]>
1 parent 491c318 commit 536dd91

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

vulntotal/vulntotal_utils.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import operator
11+
from typing import Union
12+
13+
14+
class GenericVersion:
15+
def __init__(self, version):
16+
self.value = version.replace(" ", "").lstrip("v")
17+
18+
self.decomposed = tuple(
19+
[int(com) if com.isnumeric() else com for com in self.value.split(".")]
20+
)
21+
22+
def __str__(self):
23+
return str(self.value)
24+
25+
def __eq__(self, other):
26+
if not isinstance(other, self.__class__):
27+
return NotImplemented
28+
return self.value.__eq__(other.value)
29+
30+
def __lt__(self, other):
31+
if not isinstance(other, self.__class__):
32+
return NotImplemented
33+
for i, j in zip(self.decomposed, other.decomposed):
34+
if not isinstance(i, type(j)):
35+
continue
36+
if i.__gt__(j):
37+
return False
38+
return True
39+
40+
def __le__(self, other):
41+
if not isinstance(other, self.__class__):
42+
return NotImplemented
43+
return self.__lt__(other) or self.__eq__(other)
44+
45+
46+
def compare(version, package_comparator, package_version):
47+
operator_comparator = {
48+
"<": operator.lt,
49+
">": operator.gt,
50+
"=": operator.eq,
51+
"<=": operator.le,
52+
">=": operator.ge,
53+
"==": operator.eq,
54+
"!=": operator.ne,
55+
")": operator.lt,
56+
"]": operator.le,
57+
"(": operator.gt,
58+
"[": operator.ge,
59+
}
60+
compare = operator_comparator[package_comparator]
61+
return compare(version, package_version)
62+
63+
64+
def parse_constraint(constraint):
65+
"""
66+
Return operator and version from a constraint
67+
For example:
68+
>>> assert parse_constraint(">=7.0.0") == ('>=', '7.0.0')
69+
>>> assert parse_constraint("=7.0.0") == ('=', '7.0.0')
70+
>>> assert parse_constraint("[3.0.0") == ('[', '3.0.0')
71+
>>> assert parse_constraint("3.1.25]") == (']', '3.1.25')
72+
"""
73+
if constraint.startswith(("<=", ">=", "==", "!=")):
74+
return constraint[:2], constraint[2:]
75+
76+
if constraint.startswith(("<", ">", "=", "[", "(")):
77+
return constraint[0], constraint[1:]
78+
79+
if constraint.endswith(("]", ")")):
80+
return constraint[-1], constraint[:-1]
81+
82+
83+
def github_constraints_satisfied(github_constrain, version):
84+
"""
85+
Return True or False depending on whether the given version satisfies the github constrain
86+
For example:
87+
>>> assert github_constraints_satisfied(">= 7.0.0, <= 7.6.57", "7.1.1") == True
88+
>>> assert github_constraints_satisfied(">= 10.4.0, <= 10.4.1", "10.6.0") == False
89+
"""
90+
gh_constraints = github_constrain.strip().replace(" ", "")
91+
constraints = gh_constraints.split(",")
92+
for constraint in constraints:
93+
gh_comparator, gh_version = parse_constraint(constraint)
94+
if not gh_version:
95+
continue
96+
if not compare(GenericVersion(version), gh_comparator, GenericVersion(gh_version)):
97+
return False
98+
return True
99+
100+
101+
def snky_constraints_satisfied(snyk_constrain, version):
102+
"""
103+
Return True or False depending on whether the given version satisfies the snyk constrain
104+
For example:
105+
>>> assert snky_constraints_satisfied(">=4.0.0, <4.0.10.16", "4.0.10.15") == True
106+
>>> assert snky_constraints_satisfied(" >=4.1.0, <4.4.15.7", "4.0.10.15") == False
107+
>>> assert snky_constraints_satisfied("[3.0.0,3.1.25)", "3.0.2") == True
108+
"""
109+
snyk_constraints = snyk_constrain.strip().replace(" ", "")
110+
constraints = snyk_constraints.split(",")
111+
for constraint in constraints:
112+
snyk_comparator, snyk_version = parse_constraint(constraint)
113+
if not snyk_version:
114+
continue
115+
if not compare(GenericVersion(version), snyk_comparator, GenericVersion(snyk_version)):
116+
return False
117+
return True
118+
119+
120+
def gitlab_constraints_satisfied(gitlab_constrain, version):
121+
"""
122+
Return True or False depending on whether the given version satisfies the gitlab constrain
123+
For example:
124+
>>> assert gitlab_constraints_satisfied("[7.0.0,7.0.11),[7.2.0,7.2.4)", "7.2.1") == True
125+
>>> assert gitlab_constraints_satisfied("[7.0.0,7.0.11),[7.2.0,7.2.4)", "8.2.1") == False
126+
>>> assert gitlab_constraints_satisfied( ">=4.0,<4.3||>=5.0,<5.2", "5.4") == False
127+
>>> assert gitlab_constraints_satisfied( ">=0.19.0 <0.30.0", "0.24") == True
128+
>>> assert gitlab_constraints_satisfied( ">=1.5,<1.5.2", "2.2") == False
129+
"""
130+
131+
gitlab_constraints = gitlab_constrain.strip()
132+
if gitlab_constraints.startswith(("[", "(")):
133+
# transform "[7.0.0,7.0.11),[7.2.0,7.2.4)" -> [ "[7.0.0,7.0.11)", "[7.2.0,7.2.4)" ]
134+
splitted = gitlab_constraints.split(",")
135+
constraints = [f"{a},{b}" for a, b in zip(splitted[::2], splitted[1::2])]
136+
delimiter = ","
137+
138+
else:
139+
# transform ">=4.0,<4.3||>=5.0,<5.2" -> [ ">=4.0,<4.3", ">=5.0,<5.2" ]
140+
# transform ">=0.19.0 <0.30.0" -> [ ">=0.19.0 <0.30.0" ]
141+
# transform ">=1.5,<1.5.2" -> [ ">=1.5,<1.5.2" ]
142+
delimiter = "," if "," in gitlab_constraints else " "
143+
constraints = gitlab_constraints.split("||")
144+
145+
for constraint in constraints:
146+
is_constraint_satisfied = True
147+
for subcontraint in constraint.strip().split(delimiter):
148+
gitlab_comparator, gitlab_version = parse_constraint(subcontraint.strip())
149+
if not gitlab_version:
150+
continue
151+
if not compare(
152+
GenericVersion(version), gitlab_comparator, GenericVersion(gitlab_version)
153+
):
154+
is_constraint_satisfied = False
155+
break
156+
157+
if is_constraint_satisfied:
158+
return True
159+
return False
160+
161+
162+
def get_item(entity: Union[dict, list], *attributes):
163+
"""
164+
Return `item` by going through all the `attributes` present in the `dictionary/list`
165+
166+
Do a DFS for the `item` in the `dictionary/list` by traversing the `attributes`
167+
and return None if can not traverse through the `attributes`
168+
For example:
169+
>>> get_item({'a': {'b': {'c': 'd'}}}, 'a', 'b', 'e')
170+
Traceback (most recent call last):
171+
...
172+
KeyError: "Missing attribute e in {'c': 'd'}"
173+
>>> assert get_item({'a': {'b': {'c': 'd'}}}, 'a', 'b', 'c') == 'd'
174+
>>> assert get_item({'a': [{'b': {'c': 'd'}}]}, 'a', 0, 'b') == {'c': 'd'}
175+
>>> assert get_item(['b', ['c', ['d']]], 1, 1, 0) == 'd'
176+
"""
177+
for attribute in attributes:
178+
if not entity:
179+
return
180+
if not isinstance(entity, (dict, list)):
181+
raise TypeError(f"Entity must be of type `dict` or `list` not {type(entity)}")
182+
if isinstance(entity, dict) and attribute not in entity:
183+
raise KeyError(f"Missing attribute {attribute} in {entity}")
184+
if isinstance(entity, list) and not isinstance(attribute, int):
185+
raise TypeError(f"List indices must be integers not {type(attribute)}")
186+
if isinstance(entity, list) and len(entity) <= attribute:
187+
raise IndexError(f"Index {attribute} out of range for {entity}")
188+
189+
entity = entity[attribute]
190+
return entity

0 commit comments

Comments
 (0)