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 200 201class SPDXExternalReference(SPDXObject): 202 referenceCategory = _String() 203 referenceType = _String() 204 referenceLocator = _String() 205 206 207class SPDXPackageVerificationCode(SPDXObject): 208 packageVerificationCodeValue = _String() 209 packageVerificationCodeExcludedFiles = _StringList() 210 211 212class SPDXPackage(SPDXObject): 213 name = _String() 214 SPDXID = _String() 215 versionInfo = _String() 216 downloadLocation = _String(default="NOASSERTION") 217 packageSupplier = _String(default="NOASSERTION") 218 homepage = _String() 219 licenseConcluded = _String(default="NOASSERTION") 220 licenseDeclared = _String(default="NOASSERTION") 221 summary = _String() 222 description = _String() 223 sourceInfo = _String() 224 copyrightText = _String(default="NOASSERTION") 225 licenseInfoFromFiles = _StringList(default=["NOASSERTION"]) 226 externalRefs = _ObjectList(SPDXExternalReference) 227 packageVerificationCode = _Object(SPDXPackageVerificationCode) 228 hasFiles = _StringList() 229 packageFileName = _String() 230 annotations = _ObjectList(SPDXAnnotation) 231 232 233class SPDXFile(SPDXObject): 234 SPDXID = _String() 235 fileName = _String() 236 licenseConcluded = _String(default="NOASSERTION") 237 copyrightText = _String(default="NOASSERTION") 238 licenseInfoInFiles = _StringList(default=["NOASSERTION"]) 239 checksums = _ObjectList(SPDXChecksum) 240 fileTypes = _StringList() 241 242 243class SPDXCreationInfo(SPDXObject): 244 created = _String() 245 licenseListVersion = _String() 246 comment = _String() 247 creators = _StringList() 248 249 250class SPDXExternalDocumentRef(SPDXObject): 251 externalDocumentId = _String() 252 spdxDocument = _String() 253 checksum = _Object(SPDXChecksum) 254 255 256class SPDXExtractedLicensingInfo(SPDXObject): 257 name = _String() 258 comment = _String() 259 licenseId = _String() 260 extractedText = _String() 261 262 263class SPDXDocument(SPDXObject): 264 spdxVersion = _String(default="SPDX-" + SPDX_VERSION) 265 dataLicense = _String(default="CC0-1.0") 266 SPDXID = _String(default="SPDXRef-DOCUMENT") 267 name = _String() 268 documentNamespace = _String() 269 creationInfo = _Object(SPDXCreationInfo) 270 packages = _ObjectList(SPDXPackage) 271 files = _ObjectList(SPDXFile) 272 relationships = _ObjectList(SPDXRelationship) 273 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef) 274 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo) 275 276 def __init__(self, **d): 277 super().__init__(**d) 278 279 def to_json(self, f, *, sort_keys=False, indent=None, separators=None): 280 class Encoder(json.JSONEncoder): 281 def default(self, o): 282 if isinstance(o, SPDXObject): 283 return o.serializer() 284 285 return super().default(o) 286 287 sha1 = hashlib.sha1() 288 for chunk in Encoder( 289 sort_keys=sort_keys, 290 indent=indent, 291 separators=separators, 292 ).iterencode(self): 293 chunk = chunk.encode("utf-8") 294 f.write(chunk) 295 sha1.update(chunk) 296 297 return sha1.hexdigest() 298 299 @classmethod 300 def from_json(cls, f): 301 return cls(**json.load(f)) 302 303 def add_relationship(self, _from, relationship, _to, *, comment=None): 304 if isinstance(_from, SPDXObject): 305 from_spdxid = _from.SPDXID 306 else: 307 from_spdxid = _from 308 309 if isinstance(_to, SPDXObject): 310 to_spdxid = _to.SPDXID 311 else: 312 to_spdxid = _to 313 314 r = SPDXRelationship( 315 spdxElementId=from_spdxid, 316 relatedSpdxElement=to_spdxid, 317 relationshipType=relationship, 318 ) 319 320 if comment is not None: 321 r.comment = comment 322 323 self.relationships.append(r) 324 325 def find_by_spdxid(self, spdxid): 326 for o in itertools.chain(self.packages, self.files): 327 if o.SPDXID == spdxid: 328 return o 329 return None 330 331 def find_external_document_ref(self, namespace): 332 for r in self.externalDocumentRefs: 333 if r.spdxDocument == namespace: 334 return r 335 return None 336