xref: /freebsd/contrib/pkgconf/cli/spdxtool/core.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 "util.h"
14 #include "serialize.h"
15 #include "core.h"
16 #include "software.h"
17 #include "simplelicensing.h"
18 
19 /*
20  * !doc
21  *
22  * .. c:function:: spdxtool_core_agent_t *spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name)
23  *
24  *    Create new /Core/Agent struct
25  *
26  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
27  *    :param char *creation_info_id: CreationInfo spdxId
28  *    :param char *name: Name of agent
29  *    :return: NULL if some problem occurs and Agent struct if not
30  */
31 spdxtool_core_agent_t *
spdxtool_core_agent_new(pkgconf_client_t * client,const char * creation_info_id,const char * name)32 spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name)
33 {
34 	if (!client || !creation_info_id || !name)
35 		return NULL;
36 
37 	spdxtool_core_agent_t *agent = calloc(1, sizeof(spdxtool_core_agent_t));
38 	if (!agent)
39 		goto err;
40 
41 	agent->type = "Agent";
42 
43 	char *spdx_id_name = strdup(name);
44 	if (!spdx_id_name)
45 		goto err;
46 
47 	spdxtool_util_string_correction(spdx_id_name);
48 
49 	agent->spdx_id = spdxtool_util_get_spdx_id_string(client, agent->type, spdx_id_name);
50 	free(spdx_id_name);
51 
52 	agent->creation_info = strdup(creation_info_id);
53 	agent->name = strdup(name);
54 
55 	if (!agent->spdx_id || !agent->creation_info || !agent->name)
56 		goto err;
57 
58 	return agent;
59 
60 err:
61 	pkgconf_error(client, "spdxtool_core_agent_new: out of memory");
62 	spdxtool_core_agent_free(agent);
63 	return NULL;
64 }
65 
66 /*
67  * !doc
68  *
69  * .. c:function:: void spdxtool_core_agent_free(spdxtool_core_agent_t *agent)
70  *
71  *    Free /Core/Agent struct
72  *
73  *    :param spdxtool_core_agent_t *agent: Agent struct to be freed.
74  *    :return: nothing
75  */
76 void
spdxtool_core_agent_free(spdxtool_core_agent_t * agent)77 spdxtool_core_agent_free(spdxtool_core_agent_t *agent)
78 {
79 	if (!agent)
80 		return;
81 
82 	free(agent->creation_info);
83 	free(agent->spdx_id);
84 	free(agent->name);
85 
86 	free(agent);
87 }
88 
89 /*
90  * !doc
91  *
92  * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent)
93  *
94  *    Serialize /Core/Agent struct to a JSON value tree.
95  *
96  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
97  *    :param const spdxtool_core_agent_t *agent: Agent struct to be serialized.
98  *    :return: spdxtool_serialize_value_t * representing the Agent object.
99  */
100 spdxtool_serialize_value_t *
spdxtool_core_agent_to_object(pkgconf_client_t * client,const spdxtool_core_agent_t * agent)101 spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent)
102 {
103 	spdxtool_serialize_value_t *ret = NULL;
104 	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
105 	if (!object_list)
106 		goto err;
107 
108 	if (!(spdxtool_serialize_object_add_string(object_list, "type", agent->type) &&
109 		spdxtool_serialize_object_add_string(object_list, "creationInfo", agent->creation_info) &&
110 		spdxtool_serialize_object_add_string(object_list, "spdxId", agent->spdx_id) &&
111 		spdxtool_serialize_object_add_string(object_list, "name", agent->name)))
112 	{
113 		goto err;
114 	}
115 
116 	ret = spdxtool_serialize_value_object(object_list);
117 	object_list = NULL;
118 
119 err:
120 	if (!ret)
121 		pkgconf_error(client, "spdxtool_core_agent_to_object: out of memory");
122 
123 	spdxtool_serialize_object_list_free(object_list);
124 	return ret;
125 }
126 
127 /*
128  * !doc
129  *
130  * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time)
131  *
132  *    Create new /Core/CreationInfo struct
133  *
134  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
135  *    :param const char *agent_id: Agent spdxId
136  *    :param const char *id: Id for creation info
137  *    :param const char *time: If NULL current time is used if not then
138  *                             this time string is used. Time string should be
139  *                             in ISO8601 format: YYYY-MM-DDTHH:MM:SSZ
140  *    :return: NULL if some problem occurs and CreationInfo struct if not
141  */
142 spdxtool_core_creation_info_t *
spdxtool_core_creation_info_new(pkgconf_client_t * client,const char * agent_id,const char * id,const char * time)143 spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time)
144 {
145 	if (!client || !agent_id || !id)
146 		return NULL;
147 
148 	spdxtool_core_creation_info_t *creation = calloc(1, sizeof(spdxtool_core_creation_info_t));
149 	if (!creation)
150 		goto err;
151 
152 	creation->type = "CreationInfo";
153 	creation->created_using = "pkgconf spdxtool";
154 	creation->id = strdup(id);
155 	creation->created = time ? strdup(time) : spdxtool_util_get_current_iso8601_time();
156 	creation->created_by = strdup(agent_id);
157 	creation->spec_version = strdup(spdxtool_util_get_spdx_version(client));
158 
159 	if (!creation->id || !creation->created || !creation->created_by || !creation->spec_version)
160 		goto err;
161 
162 	return creation;
163 
164 err:
165 	pkgconf_error(client, "spdxtool_core_creation_info_new: out of memory");
166 	spdxtool_core_creation_info_free(creation);
167 	return NULL;
168 }
169 
170 /*
171  * !doc
172  *
173  * .. c:function:: void spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation)
174  *
175  *    Free /Core/CreationInfo struct
176  *
177  *    :param spdxtool_core_creation_info_t *creation: CreationInfo struct to be freed.
178  *    :return: nothing
179  */
180 void
spdxtool_core_creation_info_free(spdxtool_core_creation_info_t * creation)181 spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation)
182 {
183 	if (!creation)
184 		return;
185 
186 	free(creation->id);
187 	free(creation->created);
188 	free(creation->created_by);
189 	free(creation->spec_version);
190 
191 	free(creation);
192 }
193 
194 /*
195  * !doc
196  *
197  * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation)
198  *
199  *    Serialize /Core/CreationInfo struct to a JSON value tree.
200  *
201  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
202  *    :param const spdxtool_core_creation_info_t *creation: CreationInfo struct to be serialized.
203  *    :return: spdxtool_serialize_value_t * representing the CreationInfo object.
204  */
205 spdxtool_serialize_value_t *
spdxtool_core_creation_info_to_object(pkgconf_client_t * client,const spdxtool_core_creation_info_t * creation)206 spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation)
207 {
208 	spdxtool_serialize_value_t *ret = NULL;
209 	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
210 	if (!object_list)
211 		goto err;
212 
213 	spdxtool_serialize_array_t *created_by = spdxtool_serialize_array_new();
214 	if (!created_by)
215 		goto err;
216 
217 	if (!spdxtool_serialize_array_add_string(created_by, creation->created_by))
218 	{
219 		spdxtool_serialize_array_free(created_by);
220 		goto err;
221 	}
222 
223 	if (!(spdxtool_serialize_object_add_string(object_list, "type", creation->type) &&
224 		spdxtool_serialize_object_add_string(object_list, "@id", creation->id) &&
225 		spdxtool_serialize_object_add_string(object_list, "created", creation->created)))
226 	{
227 		/* created_by has not been handed to the object list yet */
228 		spdxtool_serialize_array_free(created_by);
229 		goto err;
230 	}
231 
232 	/* object_add_array takes ownership of created_by, freeing it on failure */
233 	if (!(spdxtool_serialize_object_add_array(object_list, "createdBy", created_by) &&
234 		spdxtool_serialize_object_add_string(object_list, "specVersion", creation->spec_version)))
235 	{
236 		goto err;
237 	}
238 
239 	ret = spdxtool_serialize_value_object(object_list);
240 	object_list = NULL;
241 
242 err:
243 	if (!ret)
244 		pkgconf_error(client, "spdxtool_core_creation_info_to_object: out of memory");
245 
246 	spdxtool_serialize_object_list_free(object_list);
247 	return ret;
248 }
249 
250 /*
251  * !doc
252  *
253  * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent)
254  *
255  *    Create new /Core/SpdxDocument struct
256  *    In SPDX Lite SBOM there can be only one SpdxDocument
257  *
258  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
259  *    :param const char *spdx_id: Id of this SpdxDocument
260  *    :param const char *creation_id: Id for creation info
261  *    :param const char *agent_id: Agent for this document
262  *    :return: NULL if some problem occurs and SpdxDocument struct if not
263  */
264 spdxtool_core_spdx_document_t *
spdxtool_core_spdx_document_new(pkgconf_client_t * client,const char * spdx_id,const char * creation_id,const char * agent_id)265 spdxtool_core_spdx_document_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent_id)
266 {
267 	if (!client || !spdx_id || !creation_id || !agent_id)
268 		return NULL;
269 
270 	spdxtool_core_spdx_document_t *spdx = calloc(1, sizeof(spdxtool_core_spdx_document_t));
271 	if (!spdx)
272 		goto err;
273 
274 	spdx->type = "SpdxDocument";
275 	spdx->spdx_id = strdup(spdx_id);
276 	spdx->agent = strdup(agent_id);
277 	spdx->creation_info = strdup(creation_id);
278 
279 	if (!spdx->spdx_id || !spdx->agent || !spdx->creation_info)
280 		goto err;
281 
282 	return spdx;
283 
284 err:
285 	pkgconf_error(client, "spdxtool_core_spdx_document_new: out of memory");
286 	spdxtool_core_spdx_document_free(spdx);
287 	return NULL;
288 }
289 
290 /*
291  * !doc
292  *
293  * .. c:function:: void spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx)
294  *
295  *    Free /Core/SpdxDocument struct
296  *
297  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be freed.
298  *    :return: nothing
299  */
300 void
spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t * spdx)301 spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx)
302 {
303 	pkgconf_node_t *iter = NULL, *iter_next = NULL;
304 
305 	if (!spdx)
306 		return;
307 
308 	free(spdx->spdx_id);
309 	free(spdx->creation_info);
310 	free(spdx->agent);
311 
312 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->rootElement.head, iter_next, iter)
313 	{
314 		spdxtool_software_sbom_t *sbom = iter->data;
315 		spdxtool_software_sbom_free(sbom);
316 		free(iter);
317 	}
318 
319 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->element.head, iter_next, iter)
320 	{
321 		free(iter->data);
322 		free(iter);
323 	}
324 
325 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->licenses.head, iter_next, iter)
326 	{
327 		spdxtool_simplelicensing_license_expression_t *expression = iter->data;
328 		spdxtool_simplelicensing_licenseExpression_free(expression);
329 		free(iter);
330 	}
331 
332 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->relationships.head, iter_next, iter)
333 	{
334 		spdxtool_core_relationship_t *relationship = iter->data;
335 		spdxtool_core_relationship_free(relationship);
336 		free(iter);
337 	}
338 
339 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->maintainers.head, iter_next, iter)
340 	{
341 		spdxtool_core_agent_t *maintainer = iter->data;
342 		spdxtool_core_agent_free(maintainer);
343 		free(iter);
344 	}
345 
346 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->packages.head, iter_next, iter)
347 	{
348 		free(iter);
349 	}
350 
351 	free(spdx);
352 }
353 
354 /*
355  * !doc
356  *
357  * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx)
358  *
359  *    Serialize /Core/SpdxDocument struct to a JSON value tree. This function
360  *    should be called after all SBOMs and packages have been serialized so that
361  *    the document's element and rootElement lists are fully populated.
362  *
363  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
364  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be serialized.
365  *    :return: spdxtool_serialize_value_t * representing the SpdxDocument object.
366  */
367 spdxtool_serialize_value_t *
spdxtool_core_spdx_document_to_object(pkgconf_client_t * client,spdxtool_core_spdx_document_t * spdx)368 spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx)
369 {
370 	spdxtool_serialize_value_t *ret = NULL;
371 	spdxtool_serialize_object_list_t *object_list = NULL;
372 	spdxtool_serialize_array_t *root_element_array = NULL;
373 	spdxtool_serialize_array_t *element_array = NULL;
374 
375 	object_list = spdxtool_serialize_object_list_new();
376 	if (!object_list)
377 		goto err;
378 
379 	root_element_array = spdxtool_serialize_array_new();
380 	if (!root_element_array)
381 		goto err;
382 
383 	pkgconf_node_t *iter = NULL;
384 	PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
385 	{
386 		spdxtool_software_sbom_t *sbom = iter->data;
387 		if (!spdxtool_serialize_array_add_string(root_element_array, sbom->spdx_id))
388 			goto err;
389 	}
390 
391 	element_array = spdxtool_serialize_array_new();
392 	if (!element_array)
393 		goto err;
394 
395 	if (!spdxtool_serialize_array_add_string(element_array, spdx->agent))
396 		goto err;
397 
398 	PKGCONF_FOREACH_LIST_ENTRY(spdx->element.head, iter)
399 	{
400 		char *element_id = iter->data;
401 		if (!spdxtool_serialize_array_add_string(element_array, element_id))
402 			goto err;
403 	}
404 
405 	PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
406 	{
407 		spdxtool_software_sbom_t *sbom = iter->data;
408 		char *pkg_spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId");
409 		if (!pkg_spdx_id)
410 			goto err;
411 
412 		bool ok = spdxtool_serialize_array_add_string(element_array, sbom->spdx_id) &&
413 			spdxtool_serialize_array_add_string(element_array, pkg_spdx_id);
414 		free(pkg_spdx_id);
415 
416 		if (!ok)
417 			goto err;
418 	}
419 
420 	if (!(spdxtool_serialize_object_add_string(object_list, "type", spdx->type) &&
421 		spdxtool_serialize_object_add_string(object_list, "creationInfo", spdx->creation_info) &&
422 		spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id)))
423 	{
424 		goto err;
425 	}
426 
427 	/* object_add_array always takes ownership of the array (it is freed even on
428 	 * failure), so clear our reference before checking the result to avoid a
429 	 * double free at the error label.
430 	 */
431 	bool ok = spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array);
432 	root_element_array = NULL;
433 	if (!ok)
434 		goto err;
435 
436 	ok = spdxtool_serialize_object_add_array(object_list, "element", element_array);
437 	element_array = NULL;
438 	if (!ok)
439 		goto err;
440 
441 	ret = spdxtool_serialize_value_object(object_list);
442 	object_list = NULL;
443 
444 err:
445 	if (!ret)
446 		pkgconf_error(client, "spdxtool_core_spdx_document_to_object: out of memory");
447 
448 	spdxtool_serialize_object_list_free(object_list);
449 	spdxtool_serialize_array_free(root_element_array);
450 	spdxtool_serialize_array_free(element_array);
451 	return ret;
452 }
453 
454 /*
455  * !doc
456  *
457  * .. c:function:: bool spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
458  *
459  *    Find out if specific license is already there.
460  *
461  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
462  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
463  *    :param const char *license: SPDX name of license
464  *    :return: true is license is there and false if not
465  */
466 bool
spdxtool_core_spdx_document_is_license(pkgconf_client_t * client,const spdxtool_core_spdx_document_t * spdx,const char * license)467 spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license)
468 {
469 	pkgconf_node_t *iter = NULL;
470 	spdxtool_simplelicensing_license_expression_t *expression = NULL;
471 
472 	(void) client;
473 
474 	if (!license || !spdx)
475 	{
476 		return false;
477 	}
478 
479 	PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter)
480 	{
481 		expression = iter->data;
482 		if (!strcmp(expression->license_expression, license))
483 		{
484 			return true;
485 		}
486 	}
487 
488 	return false;
489 }
490 
491 /*
492  * !doc
493  *
494  * .. c:function:: bool spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
495  *
496  *    Add license to SpdxDocument and make sure that specific license is not already there.
497  *
498  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
499  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
500  *    :param const char *license: SPDX name of license
501  *    :return: true on success, false on failure
502  */
503 bool
spdxtool_core_spdx_document_add_license(pkgconf_client_t * client,spdxtool_core_spdx_document_t * spdx,const char * license)504 spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
505 {
506 	if (!license || !spdx)
507 		return false;
508 
509 	if (spdxtool_core_spdx_document_is_license(client, spdx, license))
510 		return true;
511 
512 	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
513 	if (!node)
514 	{
515 		pkgconf_error(client, "spdxtool_core_spdx_document_add_license: out of memory");
516 		return false;
517 	}
518 
519 	spdxtool_simplelicensing_license_expression_t *expression = spdxtool_simplelicensing_licenseExpression_new(client, license);
520 	if (!expression)
521 	{
522 		free(node);
523 		return false;
524 	}
525 
526 	pkgconf_node_insert_tail(node, expression, &spdx->licenses);
527 	if (!spdxtool_core_spdx_document_add_element(client, spdx, expression->spdx_id))
528 		return false;
529 
530 	return true;
531 }
532 
533 /*
534  * !doc
535  *
536  * .. c:function:: bool spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element)
537  *
538  *    Add element spdxId to SpdxDocument
539  *
540  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
541  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
542  *    :param char *element: spdxId of element
543  *    :return: true on success, false on failure
544  */
545 bool
spdxtool_core_spdx_document_add_element(pkgconf_client_t * client,spdxtool_core_spdx_document_t * spdx,const char * element)546 spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element)
547 {
548 	if (!element || !spdx)
549 		return false;
550 
551 	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
552 	if (!node)
553 	{
554 		pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory");
555 		return false;
556 	}
557 
558 	char *nelement = strdup(element);
559 	if (!nelement)
560 	{
561 		pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory");
562 		free(node);
563 		return false;
564 	}
565 
566 	pkgconf_node_insert_tail(node, nelement, &spdx->element);
567 	return true;
568 }
569 
570 /*
571  * !doc
572  *
573  * .. c:function:: const char *spdxtool_core_spdx_document_add_maintainer(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *name)
574  *
575  *    Register a package maintainer as an Agent and add it to the SpdxDocument so that
576  *    packages may reference it via their ``suppliedBy`` field.  Maintainers are
577  *    deduplicated by their spdxId, so a maintainer shared between packages is only
578  *    emitted once.  The first time a maintainer is seen, its spdxId is also added to
579  *    the document element list.
580  *
581  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
582  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
583  *    :param const char *name: Maintainer name as declared in the package.
584  *    :return: the maintainer Agent spdxId (owned by the document) on success, NULL on failure
585  */
586 const char *
spdxtool_core_spdx_document_add_maintainer(pkgconf_client_t * client,spdxtool_core_spdx_document_t * spdx,const char * name)587 spdxtool_core_spdx_document_add_maintainer(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *name)
588 {
589 	pkgconf_node_t *iter = NULL;
590 
591 	if (!client || !spdx || !name)
592 		return NULL;
593 
594 	spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, spdx->creation_info, name);
595 	if (!agent)
596 		return NULL;
597 
598 	PKGCONF_FOREACH_LIST_ENTRY(spdx->maintainers.head, iter)
599 	{
600 		spdxtool_core_agent_t *existing = iter->data;
601 		if (!strcmp(existing->spdx_id, agent->spdx_id))
602 		{
603 			spdxtool_core_agent_free(agent);
604 			return existing->spdx_id;
605 		}
606 	}
607 
608 	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
609 	if (!node)
610 	{
611 		pkgconf_error(client, "spdxtool_core_spdx_document_add_maintainer: out of memory");
612 		spdxtool_core_agent_free(agent);
613 		return NULL;
614 	}
615 
616 	pkgconf_node_insert_tail(node, agent, &spdx->maintainers);
617 
618 	if (!spdxtool_core_spdx_document_add_element(client, spdx, agent->spdx_id))
619 		return NULL;
620 
621 	return agent->spdx_id;
622 }
623 
624 /*
625  * !doc
626  *
627  * .. c:function:: bool spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship)
628  *
629  *    Add relationship rel to SpdxDocument
630  *
631  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
632  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
633  *    :param spdxtool_core_relationship_t *relationship: relationship to add.
634  *    :return: true on success, false on failure
635  */
636 bool
spdxtool_core_spdx_document_add_relationship(pkgconf_client_t * client,spdxtool_core_spdx_document_t * spdx,spdxtool_core_relationship_t * relationship)637 spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship)
638 {
639 	if (!client || !spdx || !relationship)
640 		return false;
641 
642 	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
643 	if (!node)
644 	{
645 		pkgconf_error(client, "spdxtool_core_spdx_document_add_relationship: out of memory");
646 		return false;
647 	}
648 
649 	pkgconf_node_insert_tail(node, relationship, &spdx->relationships);
650 	return true;
651 }
652 
653 /*
654  * !doc
655  *
656  * .. c:function:: bool spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg)
657  *
658  *    Register a package with the SpdxDocument for later serialization. The document
659  *    does not take ownership of the package pointer; the package must outlive the
660  *    document and will not be freed by spdxtool_core_spdx_document_free.
661  *
662  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
663  *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to register the package with.
664  *    :param pkgconf_pkg_t *pkg: Package to register. Ownership is NOT transferred.
665  *    :return: true on success, false on failure
666  */
667 bool
spdxtool_core_spdx_document_add_package(pkgconf_client_t * client,spdxtool_core_spdx_document_t * spdx,pkgconf_pkg_t * pkg)668 spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg)
669 {
670 	if (!client || !spdx || !pkg)
671 		return false;
672 
673 	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
674 	if (!node)
675 	{
676 		pkgconf_error(client, "spdxtool_core_spdx_document_add_package: out of memory");
677 		return false;
678 	}
679 
680 	pkgconf_node_insert_tail(node, pkg, &spdx->packages);
681 	return true;
682 }
683 
684 /*
685  * !doc
686  *
687  * .. c:function:: spdxtool_core_relationship_t *spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, const char *to, const char *relationship_type)
688  *
689  *    Create new /Core/Relationship struct
690  *
691  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
692  *    :param const char *creation_id: Id for creation info
693  *    :param const char *spdx_id: Id of this SpdxDocument
694  *    :param const char *from: from spdxId
695  *    :param const char *to: to spdxId
696  *    :param const char *relationship_type: These can be found on SPDX documentation
697  *    :return: NULL if some problem occurs and SpdxDocument struct if not
698  */
699 spdxtool_core_relationship_t *
spdxtool_core_relationship_new(pkgconf_client_t * client,const char * creation_info_id,const char * spdx_id,const char * from,pkgconf_list_t * to,const char * relationship_type)700 spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, pkgconf_list_t *to, const char *relationship_type)
701 {
702 	if (!client || !creation_info_id || !spdx_id || !from || !to || !relationship_type)
703 		return NULL;
704 
705 	spdxtool_core_relationship_t *relationship = calloc(1, sizeof(spdxtool_core_relationship_t));
706 	if (!relationship)
707 		goto err;
708 
709 	relationship->type = "Relationship";
710 	relationship->creation_info = strdup(creation_info_id);
711 	relationship->spdx_id = strdup(spdx_id);
712 	relationship->from = strdup(from);
713 	relationship->to = to;
714 	relationship->relationship_type = strdup(relationship_type);
715 
716 	if (!relationship->creation_info || !relationship->spdx_id || !relationship->from || !relationship->relationship_type)
717 		goto err;
718 
719 	return relationship;
720 
721 err:
722 	pkgconf_error(client, "spdxtool_core_relationship_new: out of memory");
723 	spdxtool_core_relationship_free(relationship);
724 	return NULL;
725 }
726 
727 /*
728  * !doc
729  *
730  * .. c:function:: bool spdxtool_core_relationship_set_scope(pkgconf_client_t *client, spdxtool_core_relationship_t *relationship, const char *scope)
731  *
732  *    Promote a relationship to a /Core/LifecycleScopedRelationship by attaching a lifecycle
733  *    scope (for example, ``development`` for a private build dependency).  This is the SPDX 3.0
734  *    representation of SPDX 2.x's ``*_DEPENDENCY_OF`` relationship variants.
735  *
736  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
737  *    :param spdxtool_core_relationship_t *relationship: Relationship struct to scope.
738  *    :param const char *scope: LifecycleScopeType value, e.g. "development".
739  *    :return: true on success, false on failure
740  */
741 bool
spdxtool_core_relationship_set_scope(pkgconf_client_t * client,spdxtool_core_relationship_t * relationship,const char * scope)742 spdxtool_core_relationship_set_scope(pkgconf_client_t *client, spdxtool_core_relationship_t *relationship, const char *scope)
743 {
744 	if (!relationship || !scope)
745 		return false;
746 
747 	char *scope_copy = strdup(scope);
748 	if (!scope_copy)
749 	{
750 		pkgconf_error(client, "spdxtool_core_relationship_set_scope: out of memory");
751 		return false;
752 	}
753 
754 	free(relationship->scope);
755 	relationship->scope = scope_copy;
756 	relationship->type = "LifecycleScopedRelationship";
757 
758 	return true;
759 }
760 
761 /*
762  * !doc
763  *
764  * .. c:function:: void spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship)
765  *
766  *    Free /Core/Relationship struct
767  *
768  *    :param spdxtool_core_relationship_t *relationship: Relationship struct to be freed.
769  *    :return: nothing
770  */
771 void
spdxtool_core_relationship_free(spdxtool_core_relationship_t * relationship)772 spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship)
773 {
774 	if (!relationship)
775 		return;
776 
777 	free(relationship->spdx_id);
778 	free(relationship->creation_info);
779 	free(relationship->from);
780 	pkgconf_license_free(relationship->to);
781 	free(relationship->to);
782 	free(relationship->relationship_type);
783 	free(relationship->scope);
784 
785 	free(relationship);
786 }
787 
788 /*
789  * !doc
790  *
791  * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship)
792  *
793  *    Serialize /Core/Relationship struct to a JSON value tree.
794  *
795  *    :param pkgconf_client_t *client: The pkgconf client being accessed.
796  *    :param const spdxtool_core_relationship_t *relationship: Relationship struct to be serialized.
797  *    :return: spdxtool_serialize_value_t * representing the Relationship object.
798  */
799 spdxtool_serialize_value_t *
spdxtool_core_relationship_to_object(pkgconf_client_t * client,const spdxtool_core_relationship_t * relationship)800 spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship)
801 {
802 	spdxtool_serialize_value_t *ret = NULL;
803 	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
804 	if (!object_list)
805 		goto err;
806 
807 	spdxtool_serialize_array_t *to = spdxtool_serialize_array_new();
808 	if (!to)
809 		goto err;
810 
811 	pkgconf_node_t *node = NULL;
812 	PKGCONF_FOREACH_LIST_ENTRY(relationship->to->head, node)
813 	{
814 		const pkgconf_license_t *license = node->data;
815 		if (!spdxtool_serialize_array_add_string(to, license->data))
816 		{
817 			spdxtool_serialize_array_free(to);
818 			goto err;
819 		}
820 	}
821 
822 	if (!(spdxtool_serialize_object_add_string(object_list, "type", relationship->type) &&
823 		spdxtool_serialize_object_add_string(object_list, "creationInfo", relationship->creation_info) &&
824 		spdxtool_serialize_object_add_string(object_list, "spdxId", relationship->spdx_id) &&
825 		spdxtool_serialize_object_add_string(object_list, "from", relationship->from)))
826 	{
827 		/* none of the above transfers ownership of `to` */
828 		spdxtool_serialize_array_free(to);
829 		goto err;
830 	}
831 
832 	/* object_add_array always takes ownership of `to` (it is freed even on failure) */
833 	if (!spdxtool_serialize_object_add_array(object_list, "to", to))
834 		goto err;
835 
836 	if (!spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type))
837 		goto err;
838 
839 	if (relationship->scope != NULL &&
840 		!spdxtool_serialize_object_add_string(object_list, "scope", relationship->scope))
841 	{
842 		goto err;
843 	}
844 
845 	ret = spdxtool_serialize_value_object(object_list);
846 	object_list = NULL;
847 
848 err:
849 	if (!ret)
850 		pkgconf_error(client, "spdxtool_core_relationship_to_object: out of memory");
851 
852 	spdxtool_serialize_object_list_free(object_list);
853 	return ret;
854 }
855