1*9e94795aSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*9e94795aSAndroid Build Coastguard Worker# 3*9e94795aSAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project 4*9e94795aSAndroid Build Coastguard Worker# 5*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*9e94795aSAndroid Build Coastguard Worker# 9*9e94795aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*9e94795aSAndroid Build Coastguard Worker# 11*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*9e94795aSAndroid Build Coastguard Worker# limitations under the License. 16*9e94795aSAndroid Build Coastguard Worker 17*9e94795aSAndroid Build Coastguard Worker""" 18*9e94795aSAndroid Build Coastguard WorkerSerialize objects defined in package sbom_data to SPDX format: tagvalue, JSON. 19*9e94795aSAndroid Build Coastguard Worker""" 20*9e94795aSAndroid Build Coastguard Worker 21*9e94795aSAndroid Build Coastguard Workerimport json 22*9e94795aSAndroid Build Coastguard Workerimport sbom_data 23*9e94795aSAndroid Build Coastguard Worker 24*9e94795aSAndroid Build Coastguard WorkerSPDX_VER = 'SPDX-2.3' 25*9e94795aSAndroid Build Coastguard WorkerDATA_LIC = 'CC0-1.0' 26*9e94795aSAndroid Build Coastguard Worker 27*9e94795aSAndroid Build Coastguard Worker 28*9e94795aSAndroid Build Coastguard Workerclass Tags: 29*9e94795aSAndroid Build Coastguard Worker # Common 30*9e94795aSAndroid Build Coastguard Worker SPDXID = 'SPDXID' 31*9e94795aSAndroid Build Coastguard Worker SPDX_VERSION = 'SPDXVersion' 32*9e94795aSAndroid Build Coastguard Worker DATA_LICENSE = 'DataLicense' 33*9e94795aSAndroid Build Coastguard Worker DOCUMENT_NAME = 'DocumentName' 34*9e94795aSAndroid Build Coastguard Worker DOCUMENT_NAMESPACE = 'DocumentNamespace' 35*9e94795aSAndroid Build Coastguard Worker CREATED = 'Created' 36*9e94795aSAndroid Build Coastguard Worker CREATOR = 'Creator' 37*9e94795aSAndroid Build Coastguard Worker EXTERNAL_DOCUMENT_REF = 'ExternalDocumentRef' 38*9e94795aSAndroid Build Coastguard Worker 39*9e94795aSAndroid Build Coastguard Worker # Package 40*9e94795aSAndroid Build Coastguard Worker PACKAGE_NAME = 'PackageName' 41*9e94795aSAndroid Build Coastguard Worker PACKAGE_DOWNLOAD_LOCATION = 'PackageDownloadLocation' 42*9e94795aSAndroid Build Coastguard Worker PACKAGE_VERSION = 'PackageVersion' 43*9e94795aSAndroid Build Coastguard Worker PACKAGE_SUPPLIER = 'PackageSupplier' 44*9e94795aSAndroid Build Coastguard Worker FILES_ANALYZED = 'FilesAnalyzed' 45*9e94795aSAndroid Build Coastguard Worker PACKAGE_VERIFICATION_CODE = 'PackageVerificationCode' 46*9e94795aSAndroid Build Coastguard Worker PACKAGE_EXTERNAL_REF = 'ExternalRef' 47*9e94795aSAndroid Build Coastguard Worker # Package license 48*9e94795aSAndroid Build Coastguard Worker PACKAGE_LICENSE_CONCLUDED = 'PackageLicenseConcluded' 49*9e94795aSAndroid Build Coastguard Worker PACKAGE_LICENSE_INFO_FROM_FILES = 'PackageLicenseInfoFromFiles' 50*9e94795aSAndroid Build Coastguard Worker PACKAGE_LICENSE_DECLARED = 'PackageLicenseDeclared' 51*9e94795aSAndroid Build Coastguard Worker PACKAGE_LICENSE_COMMENTS = 'PackageLicenseComments' 52*9e94795aSAndroid Build Coastguard Worker 53*9e94795aSAndroid Build Coastguard Worker # File 54*9e94795aSAndroid Build Coastguard Worker FILE_NAME = 'FileName' 55*9e94795aSAndroid Build Coastguard Worker FILE_CHECKSUM = 'FileChecksum' 56*9e94795aSAndroid Build Coastguard Worker # File license 57*9e94795aSAndroid Build Coastguard Worker FILE_LICENSE_CONCLUDED = 'LicenseConcluded' 58*9e94795aSAndroid Build Coastguard Worker FILE_LICENSE_INFO_IN_FILE = 'LicenseInfoInFile' 59*9e94795aSAndroid Build Coastguard Worker FILE_LICENSE_COMMENTS = 'LicenseComments' 60*9e94795aSAndroid Build Coastguard Worker FILE_COPYRIGHT_TEXT = 'FileCopyrightText' 61*9e94795aSAndroid Build Coastguard Worker FILE_NOTICE = 'FileNotice' 62*9e94795aSAndroid Build Coastguard Worker FILE_ATTRIBUTION_TEXT = 'FileAttributionText' 63*9e94795aSAndroid Build Coastguard Worker 64*9e94795aSAndroid Build Coastguard Worker # Relationship 65*9e94795aSAndroid Build Coastguard Worker RELATIONSHIP = 'Relationship' 66*9e94795aSAndroid Build Coastguard Worker 67*9e94795aSAndroid Build Coastguard Worker # License 68*9e94795aSAndroid Build Coastguard Worker LICENSE_ID = 'LicenseID' 69*9e94795aSAndroid Build Coastguard Worker LICENSE_NAME = 'LicenseName' 70*9e94795aSAndroid Build Coastguard Worker LICENSE_EXTRACTED_TEXT = 'ExtractedText' 71*9e94795aSAndroid Build Coastguard Worker 72*9e94795aSAndroid Build Coastguard Worker 73*9e94795aSAndroid Build Coastguard Workerclass TagValueWriter: 74*9e94795aSAndroid Build Coastguard Worker @staticmethod 75*9e94795aSAndroid Build Coastguard Worker def marshal_doc_headers(sbom_doc): 76*9e94795aSAndroid Build Coastguard Worker headers = [ 77*9e94795aSAndroid Build Coastguard Worker f'{Tags.SPDX_VERSION}: {SPDX_VER}', 78*9e94795aSAndroid Build Coastguard Worker f'{Tags.DATA_LICENSE}: {DATA_LIC}', 79*9e94795aSAndroid Build Coastguard Worker f'{Tags.SPDXID}: {sbom_doc.id}', 80*9e94795aSAndroid Build Coastguard Worker f'{Tags.DOCUMENT_NAME}: {sbom_doc.name}', 81*9e94795aSAndroid Build Coastguard Worker f'{Tags.DOCUMENT_NAMESPACE}: {sbom_doc.namespace}', 82*9e94795aSAndroid Build Coastguard Worker ] 83*9e94795aSAndroid Build Coastguard Worker for creator in sbom_doc.creators: 84*9e94795aSAndroid Build Coastguard Worker headers.append(f'{Tags.CREATOR}: {creator}') 85*9e94795aSAndroid Build Coastguard Worker headers.append(f'{Tags.CREATED}: {sbom_doc.created}') 86*9e94795aSAndroid Build Coastguard Worker for doc_ref in sbom_doc.external_refs: 87*9e94795aSAndroid Build Coastguard Worker headers.append( 88*9e94795aSAndroid Build Coastguard Worker f'{Tags.EXTERNAL_DOCUMENT_REF}: {doc_ref.id} {doc_ref.uri} {doc_ref.checksum}') 89*9e94795aSAndroid Build Coastguard Worker headers.append('') 90*9e94795aSAndroid Build Coastguard Worker return headers 91*9e94795aSAndroid Build Coastguard Worker 92*9e94795aSAndroid Build Coastguard Worker @staticmethod 93*9e94795aSAndroid Build Coastguard Worker def marshal_package(sbom_doc, package, fragment): 94*9e94795aSAndroid Build Coastguard Worker download_location = sbom_data.VALUE_NOASSERTION 95*9e94795aSAndroid Build Coastguard Worker if package.download_location: 96*9e94795aSAndroid Build Coastguard Worker download_location = package.download_location 97*9e94795aSAndroid Build Coastguard Worker tagvalues = [ 98*9e94795aSAndroid Build Coastguard Worker f'{Tags.PACKAGE_NAME}: {package.name}', 99*9e94795aSAndroid Build Coastguard Worker f'{Tags.SPDXID}: {package.id}', 100*9e94795aSAndroid Build Coastguard Worker f'{Tags.PACKAGE_DOWNLOAD_LOCATION}: {download_location}', 101*9e94795aSAndroid Build Coastguard Worker f'{Tags.FILES_ANALYZED}: {str(package.files_analyzed).lower()}', 102*9e94795aSAndroid Build Coastguard Worker ] 103*9e94795aSAndroid Build Coastguard Worker if package.version: 104*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.PACKAGE_VERSION}: {package.version}') 105*9e94795aSAndroid Build Coastguard Worker if package.supplier: 106*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.PACKAGE_SUPPLIER}: {package.supplier}') 107*9e94795aSAndroid Build Coastguard Worker 108*9e94795aSAndroid Build Coastguard Worker license = sbom_data.VALUE_NOASSERTION 109*9e94795aSAndroid Build Coastguard Worker if package.declared_license_ids: 110*9e94795aSAndroid Build Coastguard Worker license = ' OR '.join(package.declared_license_ids) 111*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.PACKAGE_LICENSE_DECLARED}: {license}') 112*9e94795aSAndroid Build Coastguard Worker 113*9e94795aSAndroid Build Coastguard Worker if package.verification_code: 114*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.PACKAGE_VERIFICATION_CODE}: {package.verification_code}') 115*9e94795aSAndroid Build Coastguard Worker if package.external_refs: 116*9e94795aSAndroid Build Coastguard Worker for external_ref in package.external_refs: 117*9e94795aSAndroid Build Coastguard Worker tagvalues.append( 118*9e94795aSAndroid Build Coastguard Worker f'{Tags.PACKAGE_EXTERNAL_REF}: {external_ref.category} {external_ref.type} {external_ref.locator}') 119*9e94795aSAndroid Build Coastguard Worker 120*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 121*9e94795aSAndroid Build Coastguard Worker 122*9e94795aSAndroid Build Coastguard Worker if package.id == sbom_doc.describes and not fragment: 123*9e94795aSAndroid Build Coastguard Worker tagvalues.append( 124*9e94795aSAndroid Build Coastguard Worker f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}') 125*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 126*9e94795aSAndroid Build Coastguard Worker 127*9e94795aSAndroid Build Coastguard Worker for file in sbom_doc.files: 128*9e94795aSAndroid Build Coastguard Worker if file.id in package.file_ids: 129*9e94795aSAndroid Build Coastguard Worker tagvalues += TagValueWriter.marshal_file(file) 130*9e94795aSAndroid Build Coastguard Worker 131*9e94795aSAndroid Build Coastguard Worker return tagvalues 132*9e94795aSAndroid Build Coastguard Worker 133*9e94795aSAndroid Build Coastguard Worker @staticmethod 134*9e94795aSAndroid Build Coastguard Worker def marshal_packages(sbom_doc, fragment): 135*9e94795aSAndroid Build Coastguard Worker tagvalues = [] 136*9e94795aSAndroid Build Coastguard Worker marshaled_relationships = [] 137*9e94795aSAndroid Build Coastguard Worker i = 0 138*9e94795aSAndroid Build Coastguard Worker packages = sbom_doc.packages 139*9e94795aSAndroid Build Coastguard Worker while i < len(packages): 140*9e94795aSAndroid Build Coastguard Worker if (i + 1 < len(packages) 141*9e94795aSAndroid Build Coastguard Worker and packages[i].id.startswith('SPDXRef-SOURCE-') 142*9e94795aSAndroid Build Coastguard Worker and packages[i + 1].id.startswith('SPDXRef-UPSTREAM-')): 143*9e94795aSAndroid Build Coastguard Worker # Output SOURCE, UPSTREAM packages and their VARIANT_OF relationship together, so they are close to each other 144*9e94795aSAndroid Build Coastguard Worker # in SBOMs in tagvalue format. 145*9e94795aSAndroid Build Coastguard Worker tagvalues += TagValueWriter.marshal_package(sbom_doc, packages[i], fragment) 146*9e94795aSAndroid Build Coastguard Worker tagvalues += TagValueWriter.marshal_package(sbom_doc, packages[i + 1], fragment) 147*9e94795aSAndroid Build Coastguard Worker rel = next((r for r in sbom_doc.relationships if 148*9e94795aSAndroid Build Coastguard Worker r.id1 == packages[i].id and 149*9e94795aSAndroid Build Coastguard Worker r.id2 == packages[i + 1].id and 150*9e94795aSAndroid Build Coastguard Worker r.relationship == sbom_data.RelationshipType.VARIANT_OF), None) 151*9e94795aSAndroid Build Coastguard Worker if rel: 152*9e94795aSAndroid Build Coastguard Worker marshaled_relationships.append(rel) 153*9e94795aSAndroid Build Coastguard Worker tagvalues.append(TagValueWriter.marshal_relationship(rel)) 154*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 155*9e94795aSAndroid Build Coastguard Worker 156*9e94795aSAndroid Build Coastguard Worker i += 2 157*9e94795aSAndroid Build Coastguard Worker else: 158*9e94795aSAndroid Build Coastguard Worker tagvalues += TagValueWriter.marshal_package(sbom_doc, packages[i], fragment) 159*9e94795aSAndroid Build Coastguard Worker i += 1 160*9e94795aSAndroid Build Coastguard Worker 161*9e94795aSAndroid Build Coastguard Worker return tagvalues, marshaled_relationships 162*9e94795aSAndroid Build Coastguard Worker 163*9e94795aSAndroid Build Coastguard Worker @staticmethod 164*9e94795aSAndroid Build Coastguard Worker def marshal_file(file): 165*9e94795aSAndroid Build Coastguard Worker tagvalues = [ 166*9e94795aSAndroid Build Coastguard Worker f'{Tags.FILE_NAME}: {file.name}', 167*9e94795aSAndroid Build Coastguard Worker f'{Tags.SPDXID}: {file.id}', 168*9e94795aSAndroid Build Coastguard Worker f'{Tags.FILE_CHECKSUM}: {file.checksum}', 169*9e94795aSAndroid Build Coastguard Worker ] 170*9e94795aSAndroid Build Coastguard Worker license = sbom_data.VALUE_NOASSERTION 171*9e94795aSAndroid Build Coastguard Worker if file.concluded_license_ids: 172*9e94795aSAndroid Build Coastguard Worker license = ' OR '.join(file.concluded_license_ids) 173*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.FILE_LICENSE_CONCLUDED}: {license}') 174*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 175*9e94795aSAndroid Build Coastguard Worker 176*9e94795aSAndroid Build Coastguard Worker return tagvalues 177*9e94795aSAndroid Build Coastguard Worker 178*9e94795aSAndroid Build Coastguard Worker @staticmethod 179*9e94795aSAndroid Build Coastguard Worker def marshal_files(sbom_doc, fragment): 180*9e94795aSAndroid Build Coastguard Worker tagvalues = [] 181*9e94795aSAndroid Build Coastguard Worker files_in_packages = [] 182*9e94795aSAndroid Build Coastguard Worker for package in sbom_doc.packages: 183*9e94795aSAndroid Build Coastguard Worker files_in_packages += package.file_ids 184*9e94795aSAndroid Build Coastguard Worker for file in sbom_doc.files: 185*9e94795aSAndroid Build Coastguard Worker if file.id in files_in_packages: 186*9e94795aSAndroid Build Coastguard Worker continue 187*9e94795aSAndroid Build Coastguard Worker tagvalues += TagValueWriter.marshal_file(file) 188*9e94795aSAndroid Build Coastguard Worker if file.id == sbom_doc.describes and not fragment: 189*9e94795aSAndroid Build Coastguard Worker # Fragment is not a full SBOM document so the relationship DESCRIBES is not applicable. 190*9e94795aSAndroid Build Coastguard Worker tagvalues.append( 191*9e94795aSAndroid Build Coastguard Worker f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}') 192*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 193*9e94795aSAndroid Build Coastguard Worker return tagvalues 194*9e94795aSAndroid Build Coastguard Worker 195*9e94795aSAndroid Build Coastguard Worker @staticmethod 196*9e94795aSAndroid Build Coastguard Worker def marshal_relationship(rel): 197*9e94795aSAndroid Build Coastguard Worker return f'{Tags.RELATIONSHIP}: {rel.id1} {rel.relationship} {rel.id2}' 198*9e94795aSAndroid Build Coastguard Worker 199*9e94795aSAndroid Build Coastguard Worker @staticmethod 200*9e94795aSAndroid Build Coastguard Worker def marshal_relationships(sbom_doc, marshaled_rels): 201*9e94795aSAndroid Build Coastguard Worker tagvalues = [] 202*9e94795aSAndroid Build Coastguard Worker sorted_rels = sorted(sbom_doc.relationships, key=lambda r: r.id2 + r.id1) 203*9e94795aSAndroid Build Coastguard Worker for rel in sorted_rels: 204*9e94795aSAndroid Build Coastguard Worker if any(r.id1 == rel.id1 and r.id2 == rel.id2 and r.relationship == rel.relationship 205*9e94795aSAndroid Build Coastguard Worker for r in marshaled_rels): 206*9e94795aSAndroid Build Coastguard Worker continue 207*9e94795aSAndroid Build Coastguard Worker tagvalues.append(TagValueWriter.marshal_relationship(rel)) 208*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 209*9e94795aSAndroid Build Coastguard Worker return tagvalues 210*9e94795aSAndroid Build Coastguard Worker 211*9e94795aSAndroid Build Coastguard Worker @staticmethod 212*9e94795aSAndroid Build Coastguard Worker def marshal_license(license): 213*9e94795aSAndroid Build Coastguard Worker tagvalues = [] 214*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.LICENSE_ID}: {license.id}') 215*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.LICENSE_NAME}: {license.name}') 216*9e94795aSAndroid Build Coastguard Worker tagvalues.append(f'{Tags.LICENSE_EXTRACTED_TEXT}: <text>{license.text}</text>') 217*9e94795aSAndroid Build Coastguard Worker return tagvalues 218*9e94795aSAndroid Build Coastguard Worker 219*9e94795aSAndroid Build Coastguard Worker @staticmethod 220*9e94795aSAndroid Build Coastguard Worker def marshal_licenses(sbom_doc): 221*9e94795aSAndroid Build Coastguard Worker tagvalues = [] 222*9e94795aSAndroid Build Coastguard Worker for license in sbom_doc.licenses: 223*9e94795aSAndroid Build Coastguard Worker tagvalues += TagValueWriter.marshal_license(license) 224*9e94795aSAndroid Build Coastguard Worker tagvalues.append('') 225*9e94795aSAndroid Build Coastguard Worker return tagvalues 226*9e94795aSAndroid Build Coastguard Worker 227*9e94795aSAndroid Build Coastguard Worker @staticmethod 228*9e94795aSAndroid Build Coastguard Worker def write(sbom_doc, file, fragment=False): 229*9e94795aSAndroid Build Coastguard Worker content = [] 230*9e94795aSAndroid Build Coastguard Worker if not fragment: 231*9e94795aSAndroid Build Coastguard Worker content += TagValueWriter.marshal_doc_headers(sbom_doc) 232*9e94795aSAndroid Build Coastguard Worker content += TagValueWriter.marshal_files(sbom_doc, fragment) 233*9e94795aSAndroid Build Coastguard Worker tagvalues, marshaled_relationships = TagValueWriter.marshal_packages(sbom_doc, fragment) 234*9e94795aSAndroid Build Coastguard Worker content += tagvalues 235*9e94795aSAndroid Build Coastguard Worker content += TagValueWriter.marshal_relationships(sbom_doc, marshaled_relationships) 236*9e94795aSAndroid Build Coastguard Worker content += TagValueWriter.marshal_licenses(sbom_doc) 237*9e94795aSAndroid Build Coastguard Worker file.write('\n'.join(content)) 238*9e94795aSAndroid Build Coastguard Worker 239*9e94795aSAndroid Build Coastguard Worker 240*9e94795aSAndroid Build Coastguard Workerclass PropNames: 241*9e94795aSAndroid Build Coastguard Worker # Common 242*9e94795aSAndroid Build Coastguard Worker SPDXID = 'SPDXID' 243*9e94795aSAndroid Build Coastguard Worker SPDX_VERSION = 'spdxVersion' 244*9e94795aSAndroid Build Coastguard Worker DATA_LICENSE = 'dataLicense' 245*9e94795aSAndroid Build Coastguard Worker NAME = 'name' 246*9e94795aSAndroid Build Coastguard Worker DOCUMENT_NAMESPACE = 'documentNamespace' 247*9e94795aSAndroid Build Coastguard Worker CREATION_INFO = 'creationInfo' 248*9e94795aSAndroid Build Coastguard Worker CREATORS = 'creators' 249*9e94795aSAndroid Build Coastguard Worker CREATED = 'created' 250*9e94795aSAndroid Build Coastguard Worker EXTERNAL_DOCUMENT_REF = 'externalDocumentRefs' 251*9e94795aSAndroid Build Coastguard Worker DOCUMENT_DESCRIBES = 'documentDescribes' 252*9e94795aSAndroid Build Coastguard Worker EXTERNAL_DOCUMENT_ID = 'externalDocumentId' 253*9e94795aSAndroid Build Coastguard Worker EXTERNAL_DOCUMENT_URI = 'spdxDocument' 254*9e94795aSAndroid Build Coastguard Worker EXTERNAL_DOCUMENT_CHECKSUM = 'checksum' 255*9e94795aSAndroid Build Coastguard Worker ALGORITHM = 'algorithm' 256*9e94795aSAndroid Build Coastguard Worker CHECKSUM_VALUE = 'checksumValue' 257*9e94795aSAndroid Build Coastguard Worker 258*9e94795aSAndroid Build Coastguard Worker # Package 259*9e94795aSAndroid Build Coastguard Worker PACKAGES = 'packages' 260*9e94795aSAndroid Build Coastguard Worker PACKAGE_DOWNLOAD_LOCATION = 'downloadLocation' 261*9e94795aSAndroid Build Coastguard Worker PACKAGE_VERSION = 'versionInfo' 262*9e94795aSAndroid Build Coastguard Worker PACKAGE_SUPPLIER = 'supplier' 263*9e94795aSAndroid Build Coastguard Worker FILES_ANALYZED = 'filesAnalyzed' 264*9e94795aSAndroid Build Coastguard Worker PACKAGE_VERIFICATION_CODE = 'packageVerificationCode' 265*9e94795aSAndroid Build Coastguard Worker PACKAGE_VERIFICATION_CODE_VALUE = 'packageVerificationCodeValue' 266*9e94795aSAndroid Build Coastguard Worker PACKAGE_EXTERNAL_REFS = 'externalRefs' 267*9e94795aSAndroid Build Coastguard Worker PACKAGE_EXTERNAL_REF_CATEGORY = 'referenceCategory' 268*9e94795aSAndroid Build Coastguard Worker PACKAGE_EXTERNAL_REF_TYPE = 'referenceType' 269*9e94795aSAndroid Build Coastguard Worker PACKAGE_EXTERNAL_REF_LOCATOR = 'referenceLocator' 270*9e94795aSAndroid Build Coastguard Worker PACKAGE_HAS_FILES = 'hasFiles' 271*9e94795aSAndroid Build Coastguard Worker PACKAGE_LICENSE_DECLARED = 'licenseDeclared' 272*9e94795aSAndroid Build Coastguard Worker 273*9e94795aSAndroid Build Coastguard Worker # File 274*9e94795aSAndroid Build Coastguard Worker FILES = 'files' 275*9e94795aSAndroid Build Coastguard Worker FILE_NAME = 'fileName' 276*9e94795aSAndroid Build Coastguard Worker FILE_CHECKSUMS = 'checksums' 277*9e94795aSAndroid Build Coastguard Worker FILE_LICENSE_CONCLUDED = 'licenseConcluded' 278*9e94795aSAndroid Build Coastguard Worker 279*9e94795aSAndroid Build Coastguard Worker # Relationship 280*9e94795aSAndroid Build Coastguard Worker RELATIONSHIPS = 'relationships' 281*9e94795aSAndroid Build Coastguard Worker REL_ELEMENT_ID = 'spdxElementId' 282*9e94795aSAndroid Build Coastguard Worker REL_RELATED_ELEMENT_ID = 'relatedSpdxElement' 283*9e94795aSAndroid Build Coastguard Worker REL_TYPE = 'relationshipType' 284*9e94795aSAndroid Build Coastguard Worker 285*9e94795aSAndroid Build Coastguard Worker # License 286*9e94795aSAndroid Build Coastguard Worker LICENSES = 'hasExtractedLicensingInfos' 287*9e94795aSAndroid Build Coastguard Worker LICENSE_ID = 'licenseId' 288*9e94795aSAndroid Build Coastguard Worker LICENSE_NAME = 'name' 289*9e94795aSAndroid Build Coastguard Worker LICENSE_EXTRACTED_TEXT = 'extractedText' 290*9e94795aSAndroid Build Coastguard Worker 291*9e94795aSAndroid Build Coastguard Worker 292*9e94795aSAndroid Build Coastguard Workerclass JSONWriter: 293*9e94795aSAndroid Build Coastguard Worker @staticmethod 294*9e94795aSAndroid Build Coastguard Worker def marshal_doc_headers(sbom_doc): 295*9e94795aSAndroid Build Coastguard Worker headers = { 296*9e94795aSAndroid Build Coastguard Worker PropNames.SPDX_VERSION: SPDX_VER, 297*9e94795aSAndroid Build Coastguard Worker PropNames.DATA_LICENSE: DATA_LIC, 298*9e94795aSAndroid Build Coastguard Worker PropNames.SPDXID: sbom_doc.id, 299*9e94795aSAndroid Build Coastguard Worker PropNames.NAME: sbom_doc.name, 300*9e94795aSAndroid Build Coastguard Worker PropNames.DOCUMENT_NAMESPACE: sbom_doc.namespace, 301*9e94795aSAndroid Build Coastguard Worker PropNames.CREATION_INFO: {} 302*9e94795aSAndroid Build Coastguard Worker } 303*9e94795aSAndroid Build Coastguard Worker creators = [creator for creator in sbom_doc.creators] 304*9e94795aSAndroid Build Coastguard Worker headers[PropNames.CREATION_INFO][PropNames.CREATORS] = creators 305*9e94795aSAndroid Build Coastguard Worker headers[PropNames.CREATION_INFO][PropNames.CREATED] = sbom_doc.created 306*9e94795aSAndroid Build Coastguard Worker external_refs = [] 307*9e94795aSAndroid Build Coastguard Worker for doc_ref in sbom_doc.external_refs: 308*9e94795aSAndroid Build Coastguard Worker checksum = doc_ref.checksum.split(': ') 309*9e94795aSAndroid Build Coastguard Worker external_refs.append({ 310*9e94795aSAndroid Build Coastguard Worker PropNames.EXTERNAL_DOCUMENT_ID: f'{doc_ref.id}', 311*9e94795aSAndroid Build Coastguard Worker PropNames.EXTERNAL_DOCUMENT_URI: doc_ref.uri, 312*9e94795aSAndroid Build Coastguard Worker PropNames.EXTERNAL_DOCUMENT_CHECKSUM: { 313*9e94795aSAndroid Build Coastguard Worker PropNames.ALGORITHM: checksum[0], 314*9e94795aSAndroid Build Coastguard Worker PropNames.CHECKSUM_VALUE: checksum[1] 315*9e94795aSAndroid Build Coastguard Worker } 316*9e94795aSAndroid Build Coastguard Worker }) 317*9e94795aSAndroid Build Coastguard Worker if external_refs: 318*9e94795aSAndroid Build Coastguard Worker headers[PropNames.EXTERNAL_DOCUMENT_REF] = external_refs 319*9e94795aSAndroid Build Coastguard Worker headers[PropNames.DOCUMENT_DESCRIBES] = [sbom_doc.describes] 320*9e94795aSAndroid Build Coastguard Worker 321*9e94795aSAndroid Build Coastguard Worker return headers 322*9e94795aSAndroid Build Coastguard Worker 323*9e94795aSAndroid Build Coastguard Worker @staticmethod 324*9e94795aSAndroid Build Coastguard Worker def marshal_packages(sbom_doc): 325*9e94795aSAndroid Build Coastguard Worker packages = [] 326*9e94795aSAndroid Build Coastguard Worker for p in sbom_doc.packages: 327*9e94795aSAndroid Build Coastguard Worker package = { 328*9e94795aSAndroid Build Coastguard Worker PropNames.NAME: p.name, 329*9e94795aSAndroid Build Coastguard Worker PropNames.SPDXID: p.id, 330*9e94795aSAndroid Build Coastguard Worker PropNames.PACKAGE_DOWNLOAD_LOCATION: p.download_location if p.download_location else sbom_data.VALUE_NOASSERTION, 331*9e94795aSAndroid Build Coastguard Worker PropNames.FILES_ANALYZED: p.files_analyzed 332*9e94795aSAndroid Build Coastguard Worker } 333*9e94795aSAndroid Build Coastguard Worker if p.version: 334*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_VERSION] = p.version 335*9e94795aSAndroid Build Coastguard Worker if p.supplier: 336*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_SUPPLIER] = p.supplier 337*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_LICENSE_DECLARED] = sbom_data.VALUE_NOASSERTION 338*9e94795aSAndroid Build Coastguard Worker if p.declared_license_ids: 339*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_LICENSE_DECLARED] = ' OR '.join(p.declared_license_ids) 340*9e94795aSAndroid Build Coastguard Worker if p.verification_code: 341*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_VERIFICATION_CODE] = { 342*9e94795aSAndroid Build Coastguard Worker PropNames.PACKAGE_VERIFICATION_CODE_VALUE: p.verification_code 343*9e94795aSAndroid Build Coastguard Worker } 344*9e94795aSAndroid Build Coastguard Worker if p.external_refs: 345*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_EXTERNAL_REFS] = [] 346*9e94795aSAndroid Build Coastguard Worker for ref in p.external_refs: 347*9e94795aSAndroid Build Coastguard Worker ext_ref = { 348*9e94795aSAndroid Build Coastguard Worker PropNames.PACKAGE_EXTERNAL_REF_CATEGORY: ref.category, 349*9e94795aSAndroid Build Coastguard Worker PropNames.PACKAGE_EXTERNAL_REF_TYPE: ref.type, 350*9e94795aSAndroid Build Coastguard Worker PropNames.PACKAGE_EXTERNAL_REF_LOCATOR: ref.locator, 351*9e94795aSAndroid Build Coastguard Worker } 352*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_EXTERNAL_REFS].append(ext_ref) 353*9e94795aSAndroid Build Coastguard Worker if p.file_ids: 354*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_HAS_FILES] = [] 355*9e94795aSAndroid Build Coastguard Worker for file_id in p.file_ids: 356*9e94795aSAndroid Build Coastguard Worker package[PropNames.PACKAGE_HAS_FILES].append(file_id) 357*9e94795aSAndroid Build Coastguard Worker 358*9e94795aSAndroid Build Coastguard Worker packages.append(package) 359*9e94795aSAndroid Build Coastguard Worker 360*9e94795aSAndroid Build Coastguard Worker return {PropNames.PACKAGES: packages} 361*9e94795aSAndroid Build Coastguard Worker 362*9e94795aSAndroid Build Coastguard Worker @staticmethod 363*9e94795aSAndroid Build Coastguard Worker def marshal_files(sbom_doc): 364*9e94795aSAndroid Build Coastguard Worker files = [] 365*9e94795aSAndroid Build Coastguard Worker for f in sbom_doc.files: 366*9e94795aSAndroid Build Coastguard Worker file = { 367*9e94795aSAndroid Build Coastguard Worker PropNames.FILE_NAME: f.name, 368*9e94795aSAndroid Build Coastguard Worker PropNames.SPDXID: f.id 369*9e94795aSAndroid Build Coastguard Worker } 370*9e94795aSAndroid Build Coastguard Worker checksum = f.checksum.split(': ') 371*9e94795aSAndroid Build Coastguard Worker file[PropNames.FILE_CHECKSUMS] = [{ 372*9e94795aSAndroid Build Coastguard Worker PropNames.ALGORITHM: checksum[0], 373*9e94795aSAndroid Build Coastguard Worker PropNames.CHECKSUM_VALUE: checksum[1], 374*9e94795aSAndroid Build Coastguard Worker }] 375*9e94795aSAndroid Build Coastguard Worker file[PropNames.FILE_LICENSE_CONCLUDED] = sbom_data.VALUE_NOASSERTION 376*9e94795aSAndroid Build Coastguard Worker if f.concluded_license_ids: 377*9e94795aSAndroid Build Coastguard Worker file[PropNames.FILE_LICENSE_CONCLUDED] = ' OR '.join(f.concluded_license_ids) 378*9e94795aSAndroid Build Coastguard Worker files.append(file) 379*9e94795aSAndroid Build Coastguard Worker return {PropNames.FILES: files} 380*9e94795aSAndroid Build Coastguard Worker 381*9e94795aSAndroid Build Coastguard Worker @staticmethod 382*9e94795aSAndroid Build Coastguard Worker def marshal_relationships(sbom_doc): 383*9e94795aSAndroid Build Coastguard Worker relationships = [] 384*9e94795aSAndroid Build Coastguard Worker sorted_rels = sorted(sbom_doc.relationships, key=lambda r: r.relationship + r.id2 + r.id1) 385*9e94795aSAndroid Build Coastguard Worker for r in sorted_rels: 386*9e94795aSAndroid Build Coastguard Worker rel = { 387*9e94795aSAndroid Build Coastguard Worker PropNames.REL_ELEMENT_ID: r.id1, 388*9e94795aSAndroid Build Coastguard Worker PropNames.REL_RELATED_ELEMENT_ID: r.id2, 389*9e94795aSAndroid Build Coastguard Worker PropNames.REL_TYPE: r.relationship, 390*9e94795aSAndroid Build Coastguard Worker } 391*9e94795aSAndroid Build Coastguard Worker relationships.append(rel) 392*9e94795aSAndroid Build Coastguard Worker 393*9e94795aSAndroid Build Coastguard Worker return {PropNames.RELATIONSHIPS: relationships} 394*9e94795aSAndroid Build Coastguard Worker 395*9e94795aSAndroid Build Coastguard Worker @staticmethod 396*9e94795aSAndroid Build Coastguard Worker def marshal_licenses(sbom_doc): 397*9e94795aSAndroid Build Coastguard Worker licenses = [] 398*9e94795aSAndroid Build Coastguard Worker for l in sbom_doc.licenses: 399*9e94795aSAndroid Build Coastguard Worker licenses.append({ 400*9e94795aSAndroid Build Coastguard Worker PropNames.LICENSE_ID: l.id, 401*9e94795aSAndroid Build Coastguard Worker PropNames.LICENSE_NAME: l.name, 402*9e94795aSAndroid Build Coastguard Worker PropNames.LICENSE_EXTRACTED_TEXT: f'<text>{l.text}</text>' 403*9e94795aSAndroid Build Coastguard Worker }) 404*9e94795aSAndroid Build Coastguard Worker return {PropNames.LICENSES: licenses} 405*9e94795aSAndroid Build Coastguard Worker 406*9e94795aSAndroid Build Coastguard Worker @staticmethod 407*9e94795aSAndroid Build Coastguard Worker def write(sbom_doc, file): 408*9e94795aSAndroid Build Coastguard Worker doc = {} 409*9e94795aSAndroid Build Coastguard Worker doc.update(JSONWriter.marshal_doc_headers(sbom_doc)) 410*9e94795aSAndroid Build Coastguard Worker doc.update(JSONWriter.marshal_packages(sbom_doc)) 411*9e94795aSAndroid Build Coastguard Worker doc.update(JSONWriter.marshal_files(sbom_doc)) 412*9e94795aSAndroid Build Coastguard Worker doc.update(JSONWriter.marshal_relationships(sbom_doc)) 413*9e94795aSAndroid Build Coastguard Worker doc.update(JSONWriter.marshal_licenses(sbom_doc)) 414*9e94795aSAndroid Build Coastguard Worker file.write(json.dumps(doc, indent=4)) 415