1# 2# SPDX-License-Identifier: GPL-2.0-only 3# 4 5# 6# This library is intended to capture the JSON SPDX specification in a type 7# safe manner. It is not intended to encode any particular OE specific 8# behaviors, see the sbom.py for that. 9# 10# The documented SPDX spec document doesn't cover the JSON syntax for 11# particular configuration, which can make it hard to determine what the JSON 12# syntax should be. I've found it is actually much simpler to read the official 13# SPDX JSON schema which can be found here: https://github.com/spdx/spdx-spec 14# in schemas/spdx-schema.json 15# 16 17import hashlib 18import itertools 19import json 20 21SPDX_VERSION = "2.2" 22 23 24# 25# The following are the support classes that are used to implement SPDX object 26# 27 28class _Property(object): 29 """ 30 A generic SPDX object property. The different types will derive from this 31 class 32 """ 33 34 def __init__(self, *, default=None): 35 self.default = default 36 37 def setdefault(self, dest, name): 38 if self.default is not None: 39 dest.setdefault(name, self.default) 40 41 42class _String(_Property): 43 """ 44 A scalar string property for an SPDX object 45 """ 46 47 def __init__(self, **kwargs): 48 super().__init__(**kwargs) 49 50 def set_property(self, attrs, name): 51 def get_helper(obj): 52 return obj._spdx[name] 53 54 def set_helper(obj, value): 55 obj._spdx[name] = value 56 57 def del_helper(obj): 58 del obj._spdx[name] 59 60 attrs[name] = property(get_helper, set_helper, del_helper) 61 62 def init(self, source): 63 return source 64 65 66class _Object(_Property): 67 """ 68 A scalar SPDX object property of a SPDX object 69 """ 70 71 def __init__(self, cls, **kwargs): 72 super().__init__(**kwargs) 73 self.cls = cls 74 75 def set_property(self, attrs, name): 76 def get_helper(obj): 77 if not name in obj._spdx: 78 obj._spdx[name] = self.cls() 79 return obj._spdx[name] 80 81 def set_helper(obj, value): 82 obj._spdx[name] = value 83 84 def del_helper(obj): 85 del obj._spdx[name] 86 87 attrs[name] = property(get_helper, set_helper) 88 89 def init(self, source): 90 return self.cls(**source) 91 92 93class _ListProperty(_Property): 94 """ 95 A list of SPDX properties 96 """ 97 98 def __init__(self, prop, **kwargs): 99 super().__init__(**kwargs) 100 self.prop = prop 101 102 def set_property(self, attrs, name): 103 def get_helper(obj): 104 if not name in obj._spdx: 105 obj._spdx[name] = [] 106 return obj._spdx[name] 107 108 def del_helper(obj): 109 del obj._spdx[name] 110 111 attrs[name] = property(get_helper, None, del_helper) 112 113 def init(self, source): 114 return [self.prop.init(o) for o in source] 115 116 117class _StringList(_ListProperty): 118 """ 119 A list of strings as a property for an SPDX object 120 """ 121 122 def __init__(self, **kwargs): 123 super().__init__(_String(), **kwargs) 124 125 126class _ObjectList(_ListProperty): 127 """ 128 A list of SPDX objects as a property for an SPDX object 129 """ 130 131 def __init__(self, cls, **kwargs): 132 super().__init__(_Object(cls), **kwargs) 133 134 135class MetaSPDXObject(type): 136 """ 137 A metaclass that allows properties (anything derived from a _Property 138 class) to be defined for a SPDX object 139 """ 140 def __new__(mcls, name, bases, attrs): 141 attrs["_properties"] = {} 142 143 for key in attrs.keys(): 144 if isinstance(attrs[key], _Property): 145 prop = attrs[key] 146 attrs["_properties"][key] = prop 147 prop.set_property(attrs, key) 148 149 return super().__new__(mcls, name, bases, attrs) 150 151 152class SPDXObject(metaclass=MetaSPDXObject): 153 """ 154 The base SPDX object; all SPDX spec classes must derive from this class 155 """ 156 def __init__(self, **d): 157 self._spdx = {} 158 159 for name, prop in self._properties.items(): 160 prop.setdefault(self._spdx, name) 161 if name in d: 162 self._spdx[name] = prop.init(d[name]) 163 164 def serializer(self): 165 return self._spdx 166 167 def __setattr__(self, name, value): 168 if name in self._properties or name == "_spdx": 169 super().__setattr__(name, value) 170 return 171 raise KeyError("%r is not a valid SPDX property" % name) 172 173# 174# These are the SPDX objects implemented from the spec. The *only* properties 175# that can be added to these objects are ones directly specified in the SPDX 176# spec, however you may add helper functions to make operations easier. 177# 178# Defaults should *only* be specified if the SPDX spec says there is a certain 179# required value for a field (e.g. dataLicense), or if the field is mandatory 180# and has some sane "this field is unknown" (e.g. "NOASSERTION") 181# 182 183class SPDXAnnotation(SPDXObject): 184 annotationDate = _String() 185 annotationType = _String() 186 annotator = _String() 187 comment = _String() 188 189class SPDXChecksum(SPDXObject): 190 algorithm = _String() 191 checksumValue = _String() 192 193 194class SPDXRelationship(SPDXObject): 195 spdxElementId = _String() 196 relatedSpdxElement = _String() 197 relationshipType = _String() 198 comment = _String() 199 annotations = _ObjectList(SPDXAnnotation) 200 201 202class SPDXExternalReference(SPDXObject): 203 referenceCategory = _String() 204 referenceType = _String() 205 referenceLocator = _String() 206 207 208class SPDXPackageVerificationCode(SPDXObject): 209 packageVerificationCodeValue = _String() 210 packageVerificationCodeExcludedFiles = _StringList() 211 212 213class SPDXPackage(SPDXObject): 214 name = _String() 215 SPDXID = _String() 216 versionInfo = _String() 217 downloadLocation = _String(default="NOASSERTION") 218 packageSupplier = _String(default="NOASSERTION") 219 homepage = _String() 220 licenseConcluded = _String(default="NOASSERTION") 221 licenseDeclared = _String(default="NOASSERTION") 222 summary = _String() 223 description = _String() 224 sourceInfo = _String() 225 copyrightText = _String(default="NOASSERTION") 226 licenseInfoFromFiles = _StringList(default=["NOASSERTION"]) 227 externalRefs = _ObjectList(SPDXExternalReference) 228 packageVerificationCode = _Object(SPDXPackageVerificationCode) 229 hasFiles = _StringList() 230 packageFileName = _String() 231 annotations = _ObjectList(SPDXAnnotation) 232 233 234class SPDXFile(SPDXObject): 235 SPDXID = _String() 236 fileName = _String() 237 licenseConcluded = _String(default="NOASSERTION") 238 copyrightText = _String(default="NOASSERTION") 239 licenseInfoInFiles = _StringList(default=["NOASSERTION"]) 240 checksums = _ObjectList(SPDXChecksum) 241 fileTypes = _StringList() 242 243 244class SPDXCreationInfo(SPDXObject): 245 created = _String() 246 licenseListVersion = _String() 247 comment = _String() 248 creators = _StringList() 249 250 251class SPDXExternalDocumentRef(SPDXObject): 252 externalDocumentId = _String() 253 spdxDocument = _String() 254 checksum = _Object(SPDXChecksum) 255 256 257class SPDXExtractedLicensingInfo(SPDXObject): 258 name = _String() 259 comment = _String() 260 licenseId = _String() 261 extractedText = _String() 262 263 264class SPDXDocument(SPDXObject): 265 spdxVersion = _String(default="SPDX-" + SPDX_VERSION) 266 dataLicense = _String(default="CC0-1.0") 267 SPDXID = _String(default="SPDXRef-DOCUMENT") 268 name = _String() 269 documentNamespace = _String() 270 creationInfo = _Object(SPDXCreationInfo) 271 packages = _ObjectList(SPDXPackage) 272 files = _ObjectList(SPDXFile) 273 relationships = _ObjectList(SPDXRelationship) 274 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef) 275 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo) 276 277 def __init__(self, **d): 278 super().__init__(**d) 279 280 def to_json(self, f, *, sort_keys=False, indent=None, separators=None): 281 class Encoder(json.JSONEncoder): 282 def default(self, o): 283 if isinstance(o, SPDXObject): 284 return o.serializer() 285 286 return super().default(o) 287 288 sha1 = hashlib.sha1() 289 for chunk in Encoder( 290 sort_keys=sort_keys, 291 indent=indent, 292 separators=separators, 293 ).iterencode(self): 294 chunk = chunk.encode("utf-8") 295 f.write(chunk) 296 sha1.update(chunk) 297 298 return sha1.hexdigest() 299 300 @classmethod 301 def from_json(cls, f): 302 return cls(**json.load(f)) 303 304 def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None): 305 if isinstance(_from, SPDXObject): 306 from_spdxid = _from.SPDXID 307 else: 308 from_spdxid = _from 309 310 if isinstance(_to, SPDXObject): 311 to_spdxid = _to.SPDXID 312 else: 313 to_spdxid = _to 314 315 r = SPDXRelationship( 316 spdxElementId=from_spdxid, 317 relatedSpdxElement=to_spdxid, 318 relationshipType=relationship, 319 ) 320 321 if comment is not None: 322 r.comment = comment 323 324 if annotation is not None: 325 r.annotations.append(annotation) 326 327 self.relationships.append(r) 328 329 def find_by_spdxid(self, spdxid): 330 for o in itertools.chain(self.packages, self.files): 331 if o.SPDXID == spdxid: 332 return o 333 return None 334 335 def find_external_document_ref(self, namespace): 336 for r in self.externalDocumentRefs: 337 if r.spdxDocument == namespace: 338 return r 339 return None 340