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 *
spdxtool_software_sbom_new(pkgconf_client_t * client,const char * spdx_id,const char * creation_id,const char * sbom_type)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
spdxtool_software_sbom_free(spdxtool_software_sbom_t * sbom)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 *
spdxtool_software_sbom_to_object(pkgconf_client_t * client,spdxtool_software_sbom_t * sbom)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
serialize_copyright_lines_to_object(spdxtool_serialize_object_list_t * object_list,const pkgconf_list_t * copyright_lines)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 *
spdxtool_software_package_to_object(pkgconf_client_t * client,pkgconf_pkg_t * pkg,spdxtool_core_spdx_document_t * spdx)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