38
38
from binaryornot .helpers import is_binary_string
39
39
from git import DiffIndex
40
40
from git import Repo
41
+ from license_expression import Licensing
41
42
from packageurl import PackageURL
42
43
from univers .version_range import VersionRange
43
44
from univers .versions import Version
@@ -117,22 +118,28 @@ class AffectedPackage:
117
118
"""
118
119
Contains a range of affected versions and a fixed version of a given package
119
120
The PackageURL supplied must *not* have a version
121
+ It must contain either `affected_version_range` or `fixed_version`
120
122
"""
121
123
122
124
package : PackageURL
123
- affected_version_range : VersionRange
125
+ affected_version_range : Optional [ VersionRange ] = None
124
126
fixed_version : Optional [Version ] = None
125
127
126
128
def __post_init__ (self ):
127
129
if self .package .version :
128
- raise ValueError
130
+ raise ValueError ("The PackageURL supplied must not have a version" )
131
+ if not (self .affected_version_range or self .fixed_version ):
132
+ raise ValueError (
133
+ "Affected Package should at least have either a fixed version or affected version range"
134
+ )
129
135
130
136
def get_fixed_purl (self ):
131
137
"""
132
138
Return PackageURL corresponding to object's fixed_version
133
139
"""
134
- fixed_version = self .fixed_version
135
- fixed_purl = self .package ._replace (version = str (fixed_version ))
140
+ if not self .fixed_version :
141
+ raise ValueError ("Affected package should have a fixed version" )
142
+ fixed_purl = self .package ._replace (version = str (self .fixed_version ))
136
143
return fixed_purl
137
144
138
145
@classmethod
@@ -152,7 +159,8 @@ def merge(cls, affected_packages: Iterable):
152
159
fixed_versions = set ()
153
160
purls = set ()
154
161
for pkg in affected_packages :
155
- affected_version_ranges .add (pkg .affected_version_range )
162
+ if pkg .affected_version_range :
163
+ affected_version_ranges .add (pkg .affected_version_range )
156
164
if pkg .fixed_version :
157
165
fixed_versions .add (pkg .fixed_version )
158
166
purls .add (pkg .package )
@@ -164,9 +172,12 @@ def to_dict(self):
164
172
"""
165
173
Return a serializable dict that can be converted back using self.from_dict
166
174
"""
175
+ affected_version_range = None
176
+ if self .affected_version_range :
177
+ affected_version_range = str (self .affected_version_range )
167
178
return {
168
179
"package" : self .package .to_dict (),
169
- "affected_version_range" : str ( self . affected_version_range ) ,
180
+ "affected_version_range" : affected_version_range ,
170
181
"fixed_version" : str (self .fixed_version ) if self .fixed_version else None ,
171
182
}
172
183
@@ -176,9 +187,16 @@ def from_dict(cls, affected_pkg: dict):
176
187
Return an AffectedPackage object from dict generated by self.to_dict
177
188
"""
178
189
package = PackageURL (** affected_pkg ["package" ])
179
- affected_version_range = VersionRange .from_string (affected_pkg ["affected_version_range" ])
190
+ affected_version_range = None
191
+ if (
192
+ affected_pkg ["affected_version_range" ]
193
+ and affected_pkg ["affected_version_range" ] != "None"
194
+ ):
195
+ affected_version_range = VersionRange .from_string (
196
+ affected_pkg ["affected_version_range" ]
197
+ )
180
198
fixed_version = affected_pkg ["fixed_version" ]
181
- if fixed_version :
199
+ if fixed_version and affected_version_range :
182
200
# TODO: revisit after https://github.com/nexB/univers/issues/10
183
201
fixed_version = affected_version_range .version_class (fixed_version )
184
202
@@ -203,7 +221,7 @@ class AdvisoryData:
203
221
"""
204
222
205
223
aliases : List [str ] = dataclasses .field (default_factory = list )
206
- summary : str = None
224
+ summary : Optional [ str ] = None
207
225
affected_packages : List [AffectedPackage ] = dataclasses .field (default_factory = list )
208
226
references : List [Reference ] = dataclasses .field (default_factory = list )
209
227
date_published : Optional [datetime .datetime ] = None
@@ -217,17 +235,29 @@ class NoLicenseError(Exception):
217
235
pass
218
236
219
237
238
+ class InvalidSPDXLicense (Exception ):
239
+ pass
240
+
241
+
220
242
class Importer :
221
243
"""
222
244
An Importer collects data from various upstreams and returns corresponding AdvisoryData objects
223
245
in its advisory_data method. Subclass this class to implement an importer
224
246
"""
225
247
226
248
spdx_license_expression = ""
249
+ license_url = ""
227
250
228
251
def __init__ (self ):
229
252
if not self .spdx_license_expression :
230
253
raise Exception (f"Cannot run importer { self !r} without a license" )
254
+ licensing = Licensing ()
255
+ try :
256
+ licensing .parse (self .spdx_license_expression )
257
+ except InvalidSPDXLicense as e :
258
+ raise ValueError (
259
+ f"{ self .spdx_license_expression !r} is not a valid SPDX license expression"
260
+ ) from e
231
261
232
262
@classproperty
233
263
def qualified_name (cls ):
0 commit comments