1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2025 The FreeBSD Foundation 5 * 6 * Portions of this software were developed by 7 * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from 8 * the FreeBSD Foundation 9 */ 10 11 #include <stdlib.h> 12 #include <string.h> 13 #include <libpkgconf/libpkgconf.h> 14 #include "util.h" 15 #include "serialize.h" 16 #include "software.h" 17 #include "core.h" 18 19 /* 20 * !doc 21 * 22 * .. c:function:: spdxtool_software_sbom_t *spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type) 23 * 24 * Create new /Software/Sbom struct 25 * 26 * :param pkgconf_client_t *client: The pkgconf client being accessed. 27 * :param const char *spdx_id: spdxId for this SBOM element 28 * :param const char *creation_id: id for CreationInfo 29 * :param const char *sbom_type: Sbom types can be found SPDX documention 30 * :return: NULL if some problem occurs and Sbom struct if not 31 */ 32 spdxtool_software_sbom_t * 33 spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type) 34 { 35 if (!client || !spdx_id || !creation_id || !sbom_type) 36 return NULL; 37 38 spdxtool_software_sbom_t *sbom = calloc(1, sizeof(spdxtool_software_sbom_t)); 39 if (!sbom) 40 goto err; 41 42 sbom->type = "software_Sbom"; 43 sbom->spdx_id = strdup(spdx_id); 44 sbom->creation_info = strdup(creation_id); 45 sbom->sbom_type = strdup(sbom_type); 46 47 if (!sbom->spdx_id || !sbom->creation_info || !sbom->sbom_type) 48 goto err; 49 50 return sbom; 51 52 err: 53 pkgconf_error(client, "spdxtool_software_sbom_new: out of memory"); 54 spdxtool_software_sbom_free(sbom); 55 return NULL; 56 } 57 58 /* 59 * !doc 60 * 61 * .. c:function:: void spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom) 62 * 63 * Free /Software/Sbom struct 64 * 65 * :param spdxtool_software_sbom_t *sbom: Sbom struct to be freed. 66 * :return: nothing 67 */ 68 void 69 spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom) 70 { 71 if(!sbom) 72 return; 73 74 free(sbom->spdx_id); 75 free(sbom->creation_info); 76 free(sbom->sbom_type); 77 78 free(sbom); 79 } 80 81 /* 82 * !doc 83 * 84 * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom) 85 * 86 * Serialize /Software/Sbom struct to a JSON value tree. As a side effect, 87 * the package associated with the SBOM's rootElement is registered on the 88 * document via spdxtool_core_spdx_document_add_package, and relationship 89 * element IDs are registered via spdxtool_core_spdx_document_add_element. 90 * 91 * :param pkgconf_client_t *client: The pkgconf client being accessed. 92 * :param spdxtool_software_sbom_t *sbom: Sbom struct to be serialized. 93 * :return: spdxtool_serialize_value_t * representing the Sbom object. 94 */ 95 spdxtool_serialize_value_t * 96 spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom) 97 { 98 spdxtool_serialize_value_t *ret = NULL; 99 spdxtool_serialize_object_list_t *object_list = NULL; 100 spdxtool_serialize_array_t *sbom_type_array = NULL; 101 spdxtool_serialize_array_t *root_element_array = NULL; 102 spdxtool_serialize_array_t *element_array = NULL; 103 char *spdx_id = NULL; 104 105 char sep = spdxtool_util_get_uri_separator(client); 106 107 spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId"); 108 if (!spdx_id) 109 goto err; 110 111 object_list = spdxtool_serialize_object_list_new(); 112 if (!object_list) 113 goto err; 114 115 sbom_type_array = spdxtool_serialize_array_new(); 116 if (!sbom_type_array) 117 goto err; 118 119 if (!spdxtool_serialize_array_add_string(sbom_type_array, sbom->sbom_type)) 120 goto err; 121 122 root_element_array = spdxtool_serialize_array_new(); 123 if (!root_element_array) 124 goto err; 125 126 if (!spdxtool_serialize_array_add_string(root_element_array, spdx_id)) 127 goto err; 128 129 element_array = spdxtool_serialize_array_new(); 130 if (!element_array) 131 goto err; 132 133 pkgconf_node_t *node = NULL; 134 PKGCONF_FOREACH_LIST_ENTRY(sbom->rootElement->required.head, node) 135 { 136 pkgconf_dependency_t *dep = node->data; 137 pkgconf_pkg_t *match = dep->match; 138 pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; 139 140 /* an unresolved (but tolerated) dependency has no match */ 141 if (match == NULL) 142 continue; 143 144 pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id); 145 char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); 146 if (!relationship_str) 147 goto err; 148 149 char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); 150 free(relationship_str); 151 if (!spdx_id_relation) 152 goto err; 153 154 if (!spdxtool_serialize_array_add_string(element_array, spdx_id_relation)) 155 { 156 free(spdx_id_relation); 157 goto err; 158 } 159 160 if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, spdx_id_relation)) 161 { 162 free(spdx_id_relation); 163 goto err; 164 } 165 166 free(spdx_id_relation); 167 } 168 169 PKGCONF_FOREACH_LIST_ENTRY(sbom->rootElement->requires_private.head, node) 170 { 171 pkgconf_dependency_t *dep = node->data; 172 pkgconf_pkg_t *match = dep->match; 173 pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; 174 175 /* an unresolved (but tolerated) dependency has no match */ 176 if (match == NULL) 177 continue; 178 179 pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id); 180 char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); 181 if (!relationship_str) 182 goto err; 183 184 char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); 185 free(relationship_str); 186 if (!spdx_id_relation) 187 goto err; 188 189 if (!spdxtool_serialize_array_add_string(element_array, spdx_id_relation)) 190 { 191 free(spdx_id_relation); 192 goto err; 193 } 194 195 if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, spdx_id_relation)) 196 { 197 free(spdx_id_relation); 198 goto err; 199 } 200 201 free(spdx_id_relation); 202 } 203 204 char *value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasDeclaredLicense"); 205 if (value) 206 { 207 if (!spdxtool_serialize_array_add_string(element_array, value)) 208 { 209 free(value); 210 goto err; 211 } 212 213 if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value)) 214 { 215 free(value); 216 goto err; 217 } 218 219 free(value); 220 } 221 222 value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasConcludedLicense"); 223 if (value) 224 { 225 if (!spdxtool_serialize_array_add_string(element_array, value)) 226 { 227 free(value); 228 goto err; 229 } 230 231 if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value)) 232 { 233 free(value); 234 goto err; 235 } 236 237 free(value); 238 } 239 240 if (!(spdxtool_serialize_object_add_string(object_list, "type", sbom->type) && 241 spdxtool_serialize_object_add_string(object_list, "creationInfo", sbom->creation_info) && 242 spdxtool_serialize_object_add_string(object_list, "spdxId", sbom->spdx_id))) 243 { 244 goto err; 245 } 246 247 /* object_add_array always takes ownership of the array (it is freed even on 248 * failure), so clear our reference before checking the result to avoid a 249 * double free at the error label. 250 */ 251 bool ok = spdxtool_serialize_object_add_array(object_list, "software_sbomType", sbom_type_array); 252 sbom_type_array = NULL; 253 if (!ok) 254 goto err; 255 256 ok = spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array); 257 root_element_array = NULL; 258 if (!ok) 259 goto err; 260 261 ok = spdxtool_serialize_object_add_array(object_list, "element", element_array); 262 element_array = NULL; 263 if (!ok) 264 goto err; 265 266 if (!spdxtool_core_spdx_document_add_package(client, sbom->spdx_document, sbom->rootElement)) 267 goto err; 268 269 ret = spdxtool_serialize_value_object(object_list); 270 object_list = NULL; 271 272 err: 273 if (!ret) 274 pkgconf_error(client, "spdxtool_software_sbom_to_object: out of memory"); 275 276 free(spdx_id); 277 spdxtool_serialize_object_list_free(object_list); 278 spdxtool_serialize_array_free(sbom_type_array); 279 spdxtool_serialize_array_free(root_element_array); 280 spdxtool_serialize_array_free(element_array); 281 return ret; 282 } 283 284 static bool 285 serialize_copyright_lines_to_object(spdxtool_serialize_object_list_t *object_list, const pkgconf_list_t *copyright_lines) 286 { 287 pkgconf_buffer_t copyright_buf = PKGCONF_BUFFER_INITIALIZER; 288 const pkgconf_node_t *node; 289 290 if (copyright_lines->head == NULL) 291 return spdxtool_serialize_object_add_string(object_list, "software_copyrightText", "NOASSERTION") != NULL; 292 293 PKGCONF_FOREACH_LIST_ENTRY(copyright_lines->head, node) 294 { 295 const pkgconf_bufferset_t *set = node->data; 296 pkgconf_buffer_join(©right_buf, '\n', pkgconf_buffer_str_or_empty(&set->buffer), NULL); 297 } 298 299 bool ok = spdxtool_serialize_object_add_string(object_list, "software_copyrightText", pkgconf_buffer_str_or_empty(©right_buf)) != NULL; 300 pkgconf_buffer_finalize(©right_buf); 301 return ok; 302 } 303 304 /* 305 * !doc 306 * 307 * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx) 308 * 309 * Serialize /Software/Package struct to a JSON value tree. As a side effect, 310 * any license and dependency relationships generated during serialization are 311 * added to the document via spdxtool_core_spdx_document_add_relationship. 312 * 313 * :param pkgconf_client_t *client: The pkgconf client being accessed. 314 * :param pkgconf_pkg_t *pkg: Package struct to be serialized. 315 * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument to which generated relationships are added. 316 * :return: spdxtool_serialize_value_t * representing the Package object. 317 */ 318 spdxtool_serialize_value_t * 319 spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx) 320 { 321 spdxtool_serialize_value_t *ret = NULL; 322 spdxtool_serialize_object_list_t *object_list = NULL; 323 spdxtool_serialize_array_t *originated_by = NULL; 324 spdxtool_serialize_array_t *supplied_by = NULL; 325 char *creation_info = NULL; 326 char *spdx_id = NULL; 327 char *agent = NULL; 328 char *supplier = NULL; 329 char *spdx_id_license = NULL; 330 pkgconf_list_t relations = PKGCONF_LIST_INITIALIZER; 331 pkgconf_list_t *cpy_relations = NULL; 332 pkgconf_node_t *node = NULL; 333 char sep = spdxtool_util_get_uri_separator(client); 334 335 creation_info = spdxtool_util_tuple_lookup(client, &pkg->vars, "creationInfo"); 336 spdx_id = spdxtool_util_tuple_lookup(client, &pkg->vars, "spdxId"); 337 agent = spdxtool_util_tuple_lookup(client, &pkg->vars, "agent"); 338 339 if (!creation_info || !spdx_id || !agent) 340 goto err; 341 342 object_list = spdxtool_serialize_object_list_new(); 343 if (!object_list) 344 goto err; 345 346 originated_by = spdxtool_serialize_array_new(); 347 if (!originated_by) 348 goto err; 349 350 if (!spdxtool_serialize_array_add_string(originated_by, agent)) 351 goto err; 352 353 if (!(spdxtool_serialize_object_add_string(object_list, "type", "software_Package") && 354 spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) && 355 spdxtool_serialize_object_add_string(object_list, "spdxId", spdx_id) && 356 spdxtool_serialize_object_add_string(object_list, "name", pkg->realname))) 357 { 358 goto err; 359 } 360 361 /* object_add_array always takes ownership of the array (it is freed even on 362 * failure), so clear our reference before checking the result to avoid a 363 * double free at the error label. 364 */ 365 bool ok = spdxtool_serialize_object_add_array(object_list, "originatedBy", originated_by); 366 originated_by = NULL; 367 if (!ok) 368 goto err; 369 370 supplier = spdxtool_util_tuple_lookup(client, &pkg->vars, "suppliedBy"); 371 if (supplier) 372 { 373 supplied_by = spdxtool_serialize_array_new(); 374 if (!supplied_by) 375 goto err; 376 377 if (!spdxtool_serialize_array_add_string(supplied_by, supplier)) 378 goto err; 379 380 ok = spdxtool_serialize_object_add_array(object_list, "suppliedBy", supplied_by); 381 supplied_by = NULL; 382 if (!ok) 383 goto err; 384 } 385 386 if (!serialize_copyright_lines_to_object(object_list, &pkg->copyright)) 387 goto err; 388 389 if (!spdxtool_serialize_object_add_string(object_list, "software_homePage", 390 pkg->url ? pkg->url : "")) 391 { 392 goto err; 393 } 394 395 if (!spdxtool_serialize_object_add_string(object_list, "software_downloadLocation", 396 pkg->source ? pkg->source : "")) 397 { 398 goto err; 399 } 400 401 if (!spdxtool_serialize_object_add_string(object_list, "software_packageVersion", pkg->version)) 402 goto err; 403 404 PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) 405 { 406 const pkgconf_license_t *license = node->data; 407 if (license->type == PKGCONF_LICENSE_EXPRESSION) 408 { 409 spdx_id_license = spdxtool_util_get_spdx_id_string(client, "simplelicensing_LicenseExpression", license->data); 410 if (!spdx_id_license) 411 goto err; 412 413 pkgconf_license_insert(client, &relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_license); 414 free(spdx_id_license); 415 spdx_id_license = NULL; 416 } 417 } 418 419 char *tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasDeclaredLicense"); 420 if (tuple_license) 421 { 422 cpy_relations = calloc(1, sizeof(pkgconf_list_t)); 423 if (!cpy_relations) 424 { 425 free(tuple_license); 426 goto err; 427 } 428 429 pkgconf_license_copy_list(client, cpy_relations, &relations); 430 spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasDeclaredLicense"); 431 free(tuple_license); 432 if (!relationship) 433 goto err; 434 if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) 435 goto err; 436 cpy_relations = NULL; 437 } 438 439 tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasConcludedLicense"); 440 if (tuple_license) 441 { 442 cpy_relations = calloc(1, sizeof(pkgconf_list_t)); 443 if (!cpy_relations) 444 { 445 free(tuple_license); 446 goto err; 447 } 448 449 pkgconf_license_copy_list(client, cpy_relations, &relations); 450 spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasConcludedLicense"); 451 free(tuple_license); 452 if (!relationship) 453 goto err; 454 if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) 455 goto err; 456 cpy_relations = NULL; 457 } 458 pkgconf_license_free(&relations); 459 460 PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) 461 { 462 pkgconf_dependency_t *dep = node->data; 463 pkgconf_pkg_t *match = dep->match; 464 pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; 465 466 /* an unresolved (but tolerated) dependency has no match */ 467 if (match == NULL) 468 continue; 469 470 pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id); 471 char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); 472 if (!relationship_str) 473 goto err; 474 475 char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); 476 free(relationship_str); 477 if (!spdx_id_relation) 478 goto err; 479 480 char *spdx_id_package = spdxtool_util_get_spdx_id_string(client, "Package", match->id); 481 if (!spdx_id_package) 482 { 483 free(spdx_id_relation); 484 goto err; 485 } 486 487 cpy_relations = calloc(1, sizeof(pkgconf_list_t)); 488 if (!cpy_relations) 489 { 490 free(spdx_id_relation); 491 free(spdx_id_package); 492 goto err; 493 } 494 495 pkgconf_license_insert(client, cpy_relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_package); 496 spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, spdx_id_relation, spdx_id, cpy_relations, "dependsOn"); 497 free(spdx_id_relation); 498 free(spdx_id_package); 499 if (!relationship) 500 goto err; 501 if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) 502 goto err; 503 cpy_relations = NULL; 504 } 505 506 PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) 507 { 508 pkgconf_dependency_t *dep = node->data; 509 pkgconf_pkg_t *match = dep->match; 510 pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; 511 512 /* an unresolved (but tolerated) dependency has no match */ 513 if (match == NULL) 514 continue; 515 516 pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id); 517 char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); 518 if (!relationship_str) 519 goto err; 520 521 char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); 522 free(relationship_str); 523 if (!spdx_id_relation) 524 goto err; 525 526 char *spdx_id_package = spdxtool_util_get_spdx_id_string(client, "Package", match->id); 527 if (!spdx_id_package) 528 { 529 free(spdx_id_relation); 530 goto err; 531 } 532 533 cpy_relations = calloc(1, sizeof(pkgconf_list_t)); 534 if (!cpy_relations) 535 { 536 free(spdx_id_relation); 537 free(spdx_id_package); 538 goto err; 539 } 540 541 pkgconf_license_insert(client, cpy_relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_package); 542 spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, spdx_id_relation, spdx_id, cpy_relations, "dependsOn"); 543 free(spdx_id_relation); 544 free(spdx_id_package); 545 if (!relationship) 546 goto err; 547 cpy_relations = NULL; 548 if (!spdxtool_core_relationship_set_scope(client, relationship, "development")) 549 { 550 spdxtool_core_relationship_free(relationship); 551 goto err; 552 } 553 if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) 554 { 555 spdxtool_core_relationship_free(relationship); 556 goto err; 557 } 558 } 559 560 ret = spdxtool_serialize_value_object(object_list); 561 object_list = NULL; 562 563 err: 564 if (!ret) 565 pkgconf_error(client, "spdxtool_software_package_to_object: out of memory"); 566 567 free(creation_info); 568 free(spdx_id); 569 free(agent); 570 free(supplier); 571 free(spdx_id_license); 572 spdxtool_serialize_object_list_free(object_list); 573 spdxtool_serialize_array_free(originated_by); 574 spdxtool_serialize_array_free(supplied_by); 575 return ret; 576 } 577