xref: /freebsd/contrib/pkgconf/cli/spdxtool/software.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
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(&copyright_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(&copyright_buf)) != NULL;
300 	pkgconf_buffer_finalize(&copyright_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