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 * Copyright (C) 2026 Elizabeth Ashford. 11 */ 12 13 #include <stdlib.h> 14 #include <string.h> 15 #include "util.h" 16 #include "core.h" 17 #include "software.h" 18 #include "simplelicensing.h" 19 #include "serialize.h" 20 21 static void 22 serialize_escape_string(pkgconf_buffer_t *buffer, const char *s) 23 { 24 for (const char *p = s; *p; p++) 25 { 26 switch (*p) 27 { 28 case '\"': 29 pkgconf_buffer_append(buffer, "\\\""); 30 break; 31 case '\\': 32 pkgconf_buffer_append(buffer, "\\\\"); 33 break; 34 case '\b': 35 pkgconf_buffer_append(buffer, "\\b"); 36 break; 37 case '\f': 38 pkgconf_buffer_append(buffer, "\\f"); 39 break; 40 case '\n': 41 pkgconf_buffer_append(buffer, "\\n"); 42 break; 43 case '\r': 44 pkgconf_buffer_append(buffer, "\\r"); 45 break; 46 case '\t': 47 pkgconf_buffer_append(buffer, "\\t"); 48 break; 49 default: 50 if ((unsigned char) *p < 0x20) 51 pkgconf_buffer_append_fmt(buffer, "\\u%04x", (unsigned int)(unsigned char) *p); 52 else 53 pkgconf_buffer_push_byte(buffer, *p); 54 } 55 } 56 } 57 58 static inline void 59 serialize_add_indent(pkgconf_buffer_t *buffer, unsigned int level) 60 { 61 for (; level; level--) 62 pkgconf_buffer_append(buffer, " "); 63 } 64 65 /* 66 * !doc 67 * 68 * .. c:function:: void spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent) 69 * 70 * Serialize the given JSON to the buffer 71 * 72 * :param pkgconf_buffer_t *buffer: Buffer to add to. 73 * :param spdxtool_serialize_value *value: Value to serialize. 74 * :param unsigned int indent: Indent level 75 * :return: true on success, false on failure 76 */ 77 bool 78 spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent) 79 { 80 if (!buffer || !value) 81 return false; 82 83 switch(value->type) { 84 case SPDXTOOL_SERIALIZE_TYPE_STRING: 85 pkgconf_buffer_push_byte(buffer, '"'); 86 serialize_escape_string(buffer, value->value.s ? value->value.s : ""); 87 pkgconf_buffer_push_byte(buffer, '"'); 88 break; 89 case SPDXTOOL_SERIALIZE_TYPE_INT: 90 pkgconf_buffer_append_fmt(buffer, "%d", value->value.i); 91 break; 92 case SPDXTOOL_SERIALIZE_TYPE_BOOL: 93 pkgconf_buffer_append(buffer, value->value.b ? "true" : "false"); 94 break; 95 case SPDXTOOL_SERIALIZE_TYPE_NULL: 96 pkgconf_buffer_append(buffer, "null"); 97 break; 98 case SPDXTOOL_SERIALIZE_TYPE_OBJECT: 99 { 100 pkgconf_node_t *iter; 101 pkgconf_buffer_push_byte(buffer, '{'); 102 pkgconf_buffer_push_byte(buffer, '\n'); 103 104 PKGCONF_FOREACH_LIST_ENTRY(value->value.o->entries.head, iter) 105 { 106 spdxtool_serialize_object_t *entry = iter->data; 107 serialize_add_indent(buffer, indent + 1); 108 pkgconf_buffer_append_fmt(buffer, "\"%s\": ", entry->key); 109 spdxtool_serialize_value_to_buf(buffer, entry->value, indent + 1); 110 if (iter->next) 111 pkgconf_buffer_push_byte(buffer, ','); 112 pkgconf_buffer_push_byte(buffer, '\n'); 113 } 114 115 serialize_add_indent(buffer, indent); 116 pkgconf_buffer_push_byte(buffer, '}'); 117 break; 118 } 119 case SPDXTOOL_SERIALIZE_TYPE_ARRAY: 120 { 121 pkgconf_node_t *iter; 122 pkgconf_buffer_push_byte(buffer, '['); 123 pkgconf_buffer_push_byte(buffer, '\n'); 124 125 PKGCONF_FOREACH_LIST_ENTRY(value->value.a->items.head, iter) 126 { 127 spdxtool_serialize_value_t *entry = iter->data; 128 serialize_add_indent(buffer, indent + 1); 129 spdxtool_serialize_value_to_buf(buffer, entry, indent + 1); 130 if (iter->next) 131 pkgconf_buffer_push_byte(buffer, ','); 132 pkgconf_buffer_push_byte(buffer, '\n'); 133 } 134 serialize_add_indent(buffer, indent); 135 pkgconf_buffer_push_byte(buffer, ']'); 136 break; 137 } 138 } 139 140 return true; 141 } 142 143 /* 144 * !doc 145 * 146 * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value) 147 * 148 * Add a key-value pair to a JSON object list. The key is copied internally. 149 * The object list takes ownership of the value. 150 * 151 * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. 152 * :param const char *key: Key string, copied internally. 153 * :param spdxtool_serialize_value_t *value: Value to associate with the key. Ownership transfers to the object list. 154 * :return: The value added, not owned by the caller. 155 */ 156 spdxtool_serialize_value_t * 157 spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value) 158 { 159 if (!object_list || !value) 160 { 161 spdxtool_serialize_value_free(value); 162 return NULL; 163 } 164 165 pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); 166 spdxtool_serialize_object_t *object = calloc(1, sizeof(spdxtool_serialize_object_t)); 167 char *keycopy = key ? strdup(key) : strdup(""); 168 if (!node || !object || !keycopy) 169 { 170 free(node); 171 free(keycopy); 172 /* object->key/value are not assigned yet; free the struct itself */ 173 free(object); 174 spdxtool_serialize_value_free(value); 175 return NULL; 176 } 177 178 object->key = keycopy; 179 object->value = value; 180 pkgconf_node_insert_tail(node, object, &object_list->entries); 181 return value; 182 } 183 184 /* 185 * !doc 186 * 187 * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t value) 188 * 189 * Add a value to a JSON array. The array takes ownership of the value. 190 * 191 * :param spdxtool_serialize_array_t *array: Array to add to. 192 * :param spdxtool_serialize_value_t value: Value to append. Ownership transfers to the array. 193 * :return: The value added, not owned by the caller. 194 */ 195 spdxtool_serialize_value_t * 196 spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t *value) 197 { 198 if (!array) 199 { 200 // Taking value, so free 201 spdxtool_serialize_value_free(value); 202 return NULL; 203 } 204 205 pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); 206 if (!node) 207 { 208 spdxtool_serialize_value_free(value); 209 return NULL; 210 } 211 212 pkgconf_node_insert_tail(node, value, &array->items); 213 return value; 214 } 215 216 /* 217 * !doc 218 * 219 * .. c:function:: spdxtool_serialize_object_list_t *spdxtool_serialize_object_list_new(void) 220 * 221 * Allocate and initialize a new empty JSON object list. 222 * 223 * :return: Pointer to a new spdxtool_serialize_object_list_t, or NULL on allocation failure. 224 */ 225 spdxtool_serialize_object_list_t * 226 spdxtool_serialize_object_list_new(void) 227 { 228 return calloc(1, sizeof(spdxtool_serialize_object_list_t)); 229 } 230 231 /* 232 * !doc 233 * 234 * .. c:function:: spdxtool_serialize_array_t *spdxtool_serialize_array_new(void) 235 * 236 * Allocate and initialize a new empty JSON array. 237 * 238 * :return: Pointer to a new spdxtool_serialize_array_t, or NULL on allocation failure. 239 */ 240 spdxtool_serialize_array_t * 241 spdxtool_serialize_array_new(void) 242 { 243 return calloc(1, sizeof(spdxtool_serialize_array_t)); 244 } 245 246 /* 247 * !doc 248 * 249 * .. c:function:: void spdxtool_serialize_value_free(spdxtool_serialize_value_t *value) 250 * 251 * Free all resources owned by a JSON value. For strings, frees the string. 252 * For objects and arrays, recursively frees all children. The value pointer 253 * itself is not freed as it is assumed to be stack-allocated. 254 * 255 * :param spdxtool_serialize_value_t *value: Value to free. May be NULL. 256 * :return: nothing 257 */ 258 void 259 spdxtool_serialize_value_free(spdxtool_serialize_value_t *value) 260 { 261 if (!value) 262 return; 263 264 switch (value->type) 265 { 266 case SPDXTOOL_SERIALIZE_TYPE_STRING: 267 free(value->value.s); 268 break; 269 case SPDXTOOL_SERIALIZE_TYPE_ARRAY: 270 spdxtool_serialize_array_free(value->value.a); 271 break; 272 case SPDXTOOL_SERIALIZE_TYPE_OBJECT: 273 spdxtool_serialize_object_list_free(value->value.o); 274 break; 275 default: 276 // Nothing to do 277 break; 278 } 279 280 free(value); 281 } 282 283 /* 284 * !doc 285 * 286 * .. c:function:: void spdxtool_serialize_object_free(spdxtool_serialize_object_t *object) 287 * 288 * Free a JSON object entry, including its key string and owned value. 289 * The object pointer itself is not freed by this function. 290 * 291 * :param spdxtool_serialize_object_t *object: Object entry to free. May be NULL. 292 * :return: nothing 293 */ 294 void 295 spdxtool_serialize_object_free(spdxtool_serialize_object_t *object) 296 { 297 if (!object) 298 return; 299 300 free(object->key); 301 spdxtool_serialize_value_free(object->value); 302 } 303 304 /* 305 * !doc 306 * 307 * .. c:function:: void spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list) 308 * 309 * Free a JSON object list and all of its entries, including their keys and values. 310 * 311 * :param spdxtool_serialize_object_list_t *object_list: Object list to free. May be NULL. 312 * :return: nothing 313 */ 314 void 315 spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list) 316 { 317 if (!object_list) 318 return; 319 320 pkgconf_node_t *iter_next = NULL, *iter = NULL; 321 PKGCONF_FOREACH_LIST_ENTRY_SAFE(object_list->entries.head, iter_next, iter) 322 { 323 spdxtool_serialize_object_t *object = iter->data; 324 spdxtool_serialize_object_free(object); 325 free(object); 326 free(iter); 327 } 328 329 free(object_list); 330 } 331 332 /* 333 * !doc 334 * 335 * .. c:function:: void spdxtool_serialize_array_free(spdxtool_serialize_array_t *array) 336 * 337 * Free a JSON array and all of its elements. 338 * 339 * :param spdxtool_serialize_array_t *array: Array to free. May be NULL. 340 * :return: nothing 341 */ 342 void 343 spdxtool_serialize_array_free(spdxtool_serialize_array_t *array) 344 { 345 if (!array) 346 return; 347 348 pkgconf_node_t *iter_next = NULL, *iter = NULL; 349 PKGCONF_FOREACH_LIST_ENTRY_SAFE(array->items.head, iter_next, iter) 350 { 351 spdxtool_serialize_value_t *value = iter->data; 352 spdxtool_serialize_value_free(value); 353 free(iter); 354 } 355 356 free(array); 357 } 358 359 /* 360 * !doc 361 * 362 * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx) 363 * 364 * Serialize a complete SPDX SBOM document to a JSON-LD value tree. Iterates 365 * all SBOMs, packages, relationships, and license expressions registered on 366 * the document. The SpdxDocument object is emitted last to ensure all element 367 * IDs have been populated by prior iteration. This function must be called 368 * after pkgconf_pkg_traverse has completed so that all packages and their 369 * dependencies are registered on spdx. 370 * 371 * :param pkgconf_client_t *client: The pkgconf client being accessed. 372 * :param spdxtool_core_agent_t *agent: Agent struct to include in the document. 373 * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to include in the document. 374 * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct containing all registered SBOMs, packages, relationships, and licenses. 375 * :return: spdxtool_serialize_value_t * representing the complete JSON-LD document, or a null string value on allocation failure. 376 */ 377 spdxtool_serialize_value_t * 378 spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx) 379 { 380 const char *errstr = "out of memory"; 381 spdxtool_serialize_value_t *ret = NULL; 382 spdxtool_serialize_array_t *graph = NULL; 383 spdxtool_serialize_object_list_t *root = spdxtool_serialize_object_list_new(); 384 if (!root) 385 goto err; 386 387 if (!spdxtool_serialize_object_add_string(root, "@context", "https://spdx.org/rdf/3.0.1/spdx-context.jsonld")) 388 goto err; 389 390 graph = spdxtool_serialize_array_new(); 391 if (!graph) 392 goto err; 393 394 if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_agent_to_object(client, agent))) 395 goto err; 396 397 if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_creation_info_to_object(client, creation))) 398 goto err; 399 400 pkgconf_node_t *iter = NULL; 401 PKGCONF_FOREACH_LIST_ENTRY(spdx->maintainers.head, iter) 402 { 403 spdxtool_core_agent_t *maintainer = iter->data; 404 if (!maintainer) 405 { 406 errstr = "maintainers list corrupted"; 407 goto err; 408 } 409 if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_agent_to_object(client, maintainer))) 410 goto err; 411 } 412 413 PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter) 414 { 415 spdxtool_simplelicensing_license_expression_t *expression = iter->data; 416 if (!expression) 417 { 418 errstr = "licenses list corrupted"; 419 goto err; 420 } 421 if (!spdxtool_serialize_array_add_take(graph, spdxtool_simplelicensing_licenseExpression_to_object(client, spdx->creation_info, expression))) 422 goto err; 423 } 424 425 PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) 426 { 427 spdxtool_software_sbom_t *current_sbom = iter->data; 428 if (!current_sbom) 429 { 430 errstr = "sbom list corrupted"; 431 goto err; 432 } 433 if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_sbom_to_object(client, current_sbom))) 434 goto err; 435 } 436 437 PKGCONF_FOREACH_LIST_ENTRY(spdx->packages.head, iter) 438 { 439 pkgconf_pkg_t *pkg = iter->data; 440 if (!pkg) 441 { 442 errstr = "pkg list corrupted"; 443 goto err; 444 } 445 if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_package_to_object(client, pkg, spdx))) 446 goto err; 447 } 448 449 PKGCONF_FOREACH_LIST_ENTRY(spdx->relationships.head, iter) 450 { 451 spdxtool_core_relationship_t *relationship = iter->data; 452 if (!relationship) 453 { 454 errstr = "relationship list corrupted"; 455 goto err; 456 } 457 if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_relationship_to_object(client, relationship))) 458 goto err; 459 } 460 461 // SpdxDocument last — spdx->element must be fully populated first 462 if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_spdx_document_to_object(client, spdx))) 463 goto err; 464 465 bool ok = spdxtool_serialize_object_add_array(root, "@graph", graph); 466 graph = NULL; 467 if (!ok) 468 goto err; 469 470 ret = spdxtool_serialize_value_object(root); 471 root = NULL; 472 473 err: 474 if (!ret) 475 pkgconf_error(client, "spdxtool_serialize_sbom: %s", errstr); 476 477 spdxtool_serialize_object_list_free(root); 478 spdxtool_serialize_array_free(graph); 479 return ret; 480 } 481