diff options
Diffstat (limited to 'cli/spdxtool')
| -rw-r--r-- | cli/spdxtool/core.c | 729 | ||||
| -rw-r--r-- | cli/spdxtool/core.h | 77 | ||||
| -rw-r--r-- | cli/spdxtool/main.c | 425 | ||||
| -rw-r--r-- | cli/spdxtool/serialize.c | 467 | ||||
| -rw-r--r-- | cli/spdxtool/serialize.h | 539 | ||||
| -rw-r--r-- | cli/spdxtool/simplelicensing.c | 113 | ||||
| -rw-r--r-- | cli/spdxtool/simplelicensing.h | 36 | ||||
| -rw-r--r-- | cli/spdxtool/software.c | 447 | ||||
| -rw-r--r-- | cli/spdxtool/software.h | 37 | ||||
| -rw-r--r-- | cli/spdxtool/util.c | 331 | ||||
| -rw-r--r-- | cli/spdxtool/util.h | 130 |
11 files changed, 3331 insertions, 0 deletions
diff --git a/cli/spdxtool/core.c b/cli/spdxtool/core.c new file mode 100644 index 000000000000..5cf7f10e81e1 --- /dev/null +++ b/cli/spdxtool/core.c @@ -0,0 +1,729 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "serialize.h" +#include "core.h" +#include "software.h" +#include "simplelicensing.h" + +/* + * !doc + * + * .. c:function:: spdxtool_core_agent_t *spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name) + * + * Create new /Core/Agent struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param char *creation_info_id: CreationInfo spdxId + * :param char *name: Name of agent + * :return: NULL if some problem occurs and Agent struct if not + */ +spdxtool_core_agent_t * +spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name) +{ + if (!client || !creation_info_id || !name) + return NULL; + + spdxtool_core_agent_t *agent = calloc(1, sizeof(spdxtool_core_agent_t)); + if (!agent) + goto err; + + agent->type = "Agent"; + + char *spdx_id_name = strdup(name); + if (!spdx_id_name) + goto err; + + spdxtool_util_string_correction(spdx_id_name); + + agent->spdx_id = spdxtool_util_get_spdx_id_string(client, agent->type, spdx_id_name); + free(spdx_id_name); + + agent->creation_info = strdup(creation_info_id); + agent->name = strdup(name); + + if (!agent->spdx_id || !agent->creation_info || !agent->name) + goto err; + + return agent; + +err: + pkgconf_error(client, "spdxtool_core_agent_new: out of memory"); + spdxtool_core_agent_free(agent); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_agent_free(spdxtool_core_agent_t *agent) + * + * Free /Core/Agent struct + * + * :param spdxtool_core_agent_t *agent: Agent struct to be freed. + * :return: nothing + */ +void +spdxtool_core_agent_free(spdxtool_core_agent_t *agent) +{ + if (!agent) + return; + + free(agent->creation_info); + free(agent->spdx_id); + free(agent->name); + + free(agent); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent) + * + * Serialize /Core/Agent struct to a JSON value tree. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const spdxtool_core_agent_t *agent: Agent struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the Agent object. + */ +spdxtool_serialize_value_t * +spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + if (!(spdxtool_serialize_object_add_string(object_list, "type", agent->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", agent->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", agent->spdx_id) && + spdxtool_serialize_object_add_string(object_list, "name", agent->name))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_agent_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} + +/* + * !doc + * + * .. 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) + * + * Create new /Core/CreationInfo struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *agent_id: Agent spdxId + * :param const char *id: Id for creation info + * :param const char *time: If NULL current time is used if not then + * this time string is used. Time string should be + * in ISO8601 format: YYYY-MM-DDTHH:MM:SSZ + * :return: NULL if some problem occurs and CreationInfo struct if not + */ +spdxtool_core_creation_info_t * +spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time) +{ + if (!client || !agent_id || !id) + return NULL; + + spdxtool_core_creation_info_t *creation = calloc(1, sizeof(spdxtool_core_creation_info_t)); + if (!creation) + goto err; + + creation->type = "CreationInfo"; + creation->created_using = "pkgconf spdxtool"; + creation->id = strdup(id); + creation->created = time ? strdup(time) : spdxtool_util_get_current_iso8601_time(); + creation->created_by = strdup(agent_id); + creation->spec_version = strdup(spdxtool_util_get_spdx_version(client)); + + if (!creation->id || !creation->created || !creation->created_by || !creation->spec_version) + goto err; + + return creation; + +err: + pkgconf_error(client, "spdxtool_core_creation_info_new: out of memory"); + spdxtool_core_creation_info_free(creation); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation) + * + * Free /Core/CreationInfo struct + * + * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to be freed. + * :return: nothing + */ +void +spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation) +{ + if (!creation) + return; + + free(creation->id); + free(creation->created); + free(creation->created_by); + free(creation->spec_version); + + free(creation); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation) + * + * Serialize /Core/CreationInfo struct to a JSON value tree. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const spdxtool_core_creation_info_t *creation: CreationInfo struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the CreationInfo object. + */ +spdxtool_serialize_value_t * +spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + spdxtool_serialize_array_t *created_by = spdxtool_serialize_array_new(); + if (!created_by) + goto err; + + if (!spdxtool_serialize_array_add_string(created_by, creation->created_by)) + { + spdxtool_serialize_array_free(created_by); + goto err; + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", creation->type) && + spdxtool_serialize_object_add_string(object_list, "@id", creation->id) && + spdxtool_serialize_object_add_string(object_list, "created", creation->created) && + spdxtool_serialize_object_add_array(object_list, "createdBy", created_by) && + spdxtool_serialize_object_add_string(object_list, "specVersion", creation->spec_version))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_creation_info_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} + +/* + * !doc + * + * .. 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) + * + * Create new /Core/SpdxDocument struct + * In SPDX Lite SBOM there can be only one SpdxDocument + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *spdx_id: Id of this SpdxDocument + * :param const char *creation_id: Id for creation info + * :param const char *agent_id: Agent for this document + * :return: NULL if some problem occurs and SpdxDocument struct if not + */ +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) +{ + if (!client || !spdx_id || !creation_id || !agent_id) + return NULL; + + spdxtool_core_spdx_document_t *spdx = calloc(1, sizeof(spdxtool_core_spdx_document_t)); + if (!spdx) + goto err; + + spdx->type = "SpdxDocument"; + spdx->spdx_id = strdup(spdx_id); + spdx->agent = strdup(agent_id); + spdx->creation_info = strdup(creation_id); + + if (!spdx->spdx_id || !spdx->agent || !spdx->creation_info) + goto err; + + return spdx; + +err: + pkgconf_error(client, "spdxtool_core_spdx_document_new: out of memory"); + spdxtool_core_spdx_document_free(spdx); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx) + * + * Free /Core/SpdxDocument struct + * + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be freed. + * :return: nothing + */ +void +spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx) +{ + pkgconf_node_t *iter = NULL, *iter_next = NULL; + + if (!spdx) + return; + + free(spdx->spdx_id); + free(spdx->creation_info); + free(spdx->agent); + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->rootElement.head, iter_next, iter) + { + spdxtool_software_sbom_t *sbom = iter->data; + spdxtool_software_sbom_free(sbom); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->element.head, iter_next, iter) + { + free(iter->data); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->licenses.head, iter_next, iter) + { + spdxtool_simplelicensing_license_expression_t *expression = iter->data; + spdxtool_simplelicensing_licenseExpression_free(expression); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->relationships.head, iter_next, iter) + { + spdxtool_core_relationship_t *relationship = iter->data; + spdxtool_core_relationship_free(relationship); + free(iter); + } + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->packages.head, iter_next, iter) + { + free(iter); + } + + free(spdx); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx) + * + * Serialize /Core/SpdxDocument struct to a JSON value tree. This function + * should be called after all SBOMs and packages have been serialized so that + * the document's element and rootElement lists are fully populated. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the SpdxDocument object. + */ +spdxtool_serialize_value_t * +spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = NULL; + spdxtool_serialize_array_t *root_element_array = NULL; + spdxtool_serialize_array_t *element_array = NULL; + + object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + root_element_array = spdxtool_serialize_array_new(); + if (!root_element_array) + goto err; + + pkgconf_node_t *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) + { + spdxtool_software_sbom_t *sbom = iter->data; + if (!spdxtool_serialize_array_add_string(root_element_array, sbom->spdx_id)) + goto err; + } + + element_array = spdxtool_serialize_array_new(); + if (!element_array) + goto err; + + if (!spdxtool_serialize_array_add_string(element_array, spdx->agent)) + goto err; + + PKGCONF_FOREACH_LIST_ENTRY(spdx->element.head, iter) + { + char *element_id = iter->data; + if (!spdxtool_serialize_array_add_string(element_array, element_id)) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) + { + spdxtool_software_sbom_t *sbom = iter->data; + char *pkg_spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId"); + if (!pkg_spdx_id) + goto err; + + bool ok = spdxtool_serialize_array_add_string(element_array, sbom->spdx_id) && + spdxtool_serialize_array_add_string(element_array, pkg_spdx_id); + free(pkg_spdx_id); + + if (!ok) + goto err; + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", spdx->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", spdx->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id) && + spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array) && + spdxtool_serialize_object_add_array(object_list, "element", element_array))) + { + goto err; + } + + root_element_array = NULL; + element_array = NULL; + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_spdx_document_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + spdxtool_serialize_array_free(root_element_array); + spdxtool_serialize_array_free(element_array); + return ret; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license) + * + * Find out if specific license is already there. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param const char *license: SPDX name of license + * :return: true is license is there and false if not + */ +bool +spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license) +{ + pkgconf_node_t *iter = NULL; + spdxtool_simplelicensing_license_expression_t *expression = NULL; + + (void) client; + + if (!license || !spdx) + { + return false; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter) + { + expression = iter->data; + if (!strcmp(expression->license_expression, license)) + { + return true; + } + } + + return false; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license) + * + * Add license to SpdxDocument and make sure that specific license is not already there. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param const char *license: SPDX name of license + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license) +{ + if (!license || !spdx) + return false; + + if (spdxtool_core_spdx_document_is_license(client, spdx, license)) + return true; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_license: out of memory"); + return false; + } + + spdxtool_simplelicensing_license_expression_t *expression = spdxtool_simplelicensing_licenseExpression_new(client, license); + if (!expression) + { + free(node); + return false; + } + + pkgconf_node_insert_tail(node, expression, &spdx->licenses); + if (!spdxtool_core_spdx_document_add_element(client, spdx, expression->spdx_id)) + return false; + + return true; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element) + * + * Add element spdxId to SpdxDocument + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param char *element: spdxId of element + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element) +{ + if (!element || !spdx) + return false; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory"); + return false; + } + + char *nelement = strdup(element); + if (!nelement) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory"); + free(node); + return false; + } + + pkgconf_node_insert_tail(node, nelement, &spdx->element); + return true; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship) + * + * Add relationship rel to SpdxDocument + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used. + * :param spdxtool_core_relationship_t *relationship: relationship to add. + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship) +{ + if (!client || !spdx || !relationship) + return false; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_relationship: out of memory"); + return false; + } + + pkgconf_node_insert_tail(node, relationship, &spdx->relationships); + return true; +} + +/* + * !doc + * + * .. c:function:: bool spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg) + * + * Register a package with the SpdxDocument for later serialization. The document + * does not take ownership of the package pointer; the package must outlive the + * document and will not be freed by spdxtool_core_spdx_document_free. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to register the package with. + * :param pkgconf_pkg_t *pkg: Package to register. Ownership is NOT transferred. + * :return: true on success, false on failure + */ +bool +spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg) +{ + if (!client || !spdx || !pkg) + return false; + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + pkgconf_error(client, "spdxtool_core_spdx_document_add_package: out of memory"); + return false; + } + + pkgconf_node_insert_tail(node, pkg, &spdx->packages); + return true; +} + +/* + * !doc + * + * .. 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) + * + * Create new /Core/Relationship struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *creation_id: Id for creation info + * :param const char *spdx_id: Id of this SpdxDocument + * :param const char *from: from spdxId + * :param const char *to: to spdxId + * :param const char *relationship_type: These can be found on SPDX documentation + * :return: NULL if some problem occurs and SpdxDocument struct if not + */ +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) +{ + if (!client || !creation_info_id || !spdx_id || !from || !to || !relationship_type) + return NULL; + + spdxtool_core_relationship_t *relationship = calloc(1, sizeof(spdxtool_core_relationship_t)); + if (!relationship) + goto err; + + relationship->type = "Relationship"; + relationship->creation_info = strdup(creation_info_id); + relationship->spdx_id = strdup(spdx_id); + relationship->from = strdup(from); + relationship->to = to; + relationship->relationship_type = strdup(relationship_type); + + if (!relationship->creation_info || !relationship->spdx_id || !relationship->from || !relationship->relationship_type) + goto err; + + return relationship; + +err: + pkgconf_error(client, "spdxtool_core_relationship_new: out of memory"); + spdxtool_core_relationship_free(relationship); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship) + * + * Free /Core/Relationship struct + * + * :param spdxtool_core_relationship_t *relationship: Relationship struct to be freed. + * :return: nothing + */ +void +spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship) +{ + if (!relationship) + return; + + free(relationship->spdx_id); + free(relationship->creation_info); + free(relationship->from); + pkgconf_license_free(relationship->to); + free(relationship->to); + free(relationship->relationship_type); + + free(relationship); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship) + * + * Serialize /Core/Relationship struct to a JSON value tree. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const spdxtool_core_relationship_t *relationship: Relationship struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the Relationship object. + */ +spdxtool_serialize_value_t * +spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + spdxtool_serialize_array_t *to = spdxtool_serialize_array_new(); + if (!to) + goto err; + + pkgconf_node_t *node = NULL; + PKGCONF_FOREACH_LIST_ENTRY(relationship->to->head, node) + { + const pkgconf_license_t *license = node->data; + if (!spdxtool_serialize_array_add_string(to, license->data)) + { + spdxtool_serialize_array_free(to); + goto err; + } + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", relationship->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", relationship->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", relationship->spdx_id) && + spdxtool_serialize_object_add_string(object_list, "from", relationship->from) && + spdxtool_serialize_object_add_array(object_list, "to", to) && + spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_core_relationship_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} diff --git a/cli/spdxtool/core.h b/cli/spdxtool/core.h new file mode 100644 index 000000000000..0cbb1f4b3d51 --- /dev/null +++ b/cli/spdxtool/core.h @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef CLI__SPDXTOOL__CORE_H +#define CLI__SPDXTOOL__CORE_H + +#include <stdlib.h> +#include "util.h" +#include "serialize.h" + +#ifdef __cplusplus +extern "C" { +#endif + +spdxtool_core_agent_t * +spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_id, const char *name); + +void +spdxtool_core_agent_free(spdxtool_core_agent_t *agent); + +spdxtool_serialize_value_t * +spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent); + +spdxtool_core_creation_info_t * +spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time); + +void +spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation); + +spdxtool_serialize_value_t * +spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation); + +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); + +bool +spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license); + +bool +spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license); + +bool +spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship); + +bool +spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg); + +bool +spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element); + +void +spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx); + +spdxtool_serialize_value_t * +spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx); + +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); + +void +spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship); + +spdxtool_serialize_value_t * +spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/main.c b/cli/spdxtool/main.c new file mode 100644 index 000000000000..70cdce2dd891 --- /dev/null +++ b/cli/spdxtool/main.c @@ -0,0 +1,425 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include "libpkgconf/config.h" +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "getopt_long.h" +#include "util.h" +#include "core.h" +#include "software.h" +#include "serialize.h" +#include "simplelicensing.h" + +#define PKG_VERSION (((uint64_t) 1) << 1) +#define PKG_ABOUT (((uint64_t) 1) << 2) +#define PKG_HELP (((uint64_t) 1) << 3) + +static const char *spdx_version = "3.0.1"; +static const char *bom_license = "CC0-1.0"; +static const char *xsd_any_url_default_base = "https://github.com/pkgconf/pkgconf"; +static const char *xsd_any_uri_default_base = "github.com:pkgconf:pkgconf"; +static int maximum_traverse_depth = 2000; + +static pkgconf_client_t pkg_client; +static uint64_t want_flags; +static size_t maximum_package_count = 0; +// static int maximum_traverse_depth = 2000; +static FILE *error_msgout = NULL; +static FILE *sbom_out = NULL; + +static const char * +environ_lookup_handler(const pkgconf_client_t *client, const char *key) +{ + (void) client; + + return getenv(key); +} + +static bool +error_handler(const char *msg, const pkgconf_client_t *client, void *data) +{ + (void) client; + (void) data; + if (!pkgconf_output_file_fmt(error_msgout, "%s", msg)) + { + pkgconf_error(client, "spdxtool: Could not output error message: %s", strerror(errno)); + return false; + } + return true; +} + +// NOTE: this function is passed to pkgconf_pkg_traverse +static void +generate_spdx_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *ptr) +{ + spdxtool_core_spdx_document_t *document = (spdxtool_core_spdx_document_t *)ptr; + pkgconf_node_t *node = NULL; + spdxtool_software_sbom_t *sbom = NULL; + char *package_spdx = NULL; + char *spdx_id_string = NULL; + char sep = spdxtool_util_get_uri_separator(client); + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + spdx_id_string = spdxtool_util_get_spdx_id_string(client, "software_Sbom", pkg->id); + if (!spdx_id_string) + goto err; + + sbom = spdxtool_software_sbom_new(client, spdx_id_string, document->creation_info, "build"); + free(spdx_id_string); + spdx_id_string = NULL; + if (!sbom) + goto err; + + sbom->spdx_document = document; + sbom->rootElement = pkg; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Package", pkg->id); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "spdxId", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + pkgconf_tuple_add(client, &pkg->vars, "creationInfo", document->creation_info, false, 0); + pkgconf_tuple_add(client, &pkg->vars, "agent", document->agent, false, 0); + + if (pkg->license.head != NULL) + { + pkgconf_buffer_t spdx_id_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&spdx_id_buf, "%s%chasDeclaredLicense", pkg->id, sep); + char *spdx_id_name = pkgconf_buffer_freeze(&spdx_id_buf); + if (!spdx_id_name) + goto err; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); + free(spdx_id_name); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "hasDeclaredLicense", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + pkgconf_buffer_t concluded_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_append_fmt(&concluded_buf, "%s%chasConcludedLicense", pkg->id, sep); + spdx_id_name = pkgconf_buffer_freeze(&concluded_buf); + if (!spdx_id_name) + goto err; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); + free(spdx_id_name); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "hasConcludedLicense", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) + { + const pkgconf_license_t *license = node->data; + if (license->type == PKGCONF_LICENSE_EXPRESSION) + { + if (!spdxtool_core_spdx_document_add_license(client, document, license->data)) + goto err; + } + } + } + + node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + goto err; + + pkgconf_node_insert_tail(node, sbom, &document->rootElement); + return; + +err: + pkgconf_error(client, "generate_spdx_package: failed for %s", pkg->id); + free(package_spdx); + free(spdx_id_string); + spdxtool_software_sbom_free(sbom); +} + +static bool +generate_spdx(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *creation_time, const char *creation_id, const char *agent_name) +{ + const char *agent_name_string = agent_name ? agent_name : "Default"; + const char *creation_id_string = creation_id ? creation_id : "_:creationinfo_1"; + + spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, creation_id_string, agent_name_string); + if (!agent) + { + pkgconf_error(client, "Could not create agent struct"); + return false; + } + + spdxtool_core_creation_info_t *creation = spdxtool_core_creation_info_new(client, agent->spdx_id, creation_id_string, creation_time); + if (!creation) + { + pkgconf_error(client, "Could not create creation info struct"); + spdxtool_core_agent_free(agent); + return false; + } + + char *spdx_id_int = spdxtool_util_get_spdx_id_int(client, "spdxDocument"); + spdxtool_core_spdx_document_t *document = spdxtool_core_spdx_document_new(client, spdx_id_int, creation_id_string, agent->spdx_id); + free(spdx_id_int); + if (!document) + { + pkgconf_error(client, "Could not create document"); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + int eflag = pkgconf_pkg_traverse(client, world, generate_spdx_package, document, maximum_traverse_depth, 0); + if (eflag != PKGCONF_PKG_ERRF_OK) + { + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + spdxtool_serialize_value_t *root = spdxtool_serialize_sbom(client, agent, creation, document); + if (!root) + { + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + pkgconf_buffer_t buffer = PKGCONF_BUFFER_INITIALIZER; + spdxtool_serialize_value_to_buf(&buffer, root, 0); + spdxtool_serialize_value_free(root); + + bool ret = pkgconf_output_file_fmt(sbom_out, "%s\n", pkgconf_buffer_str(&buffer)); + pkgconf_buffer_finalize(&buffer); + + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + + if (!ret) + pkgconf_error(client, "spdxtool: Could not output to file: %s", strerror(errno)); + return ret; +} + +static int +version(void) +{ + printf("spdxtool %s\n", PACKAGE_VERSION); + return EXIT_SUCCESS; +} + +static int +about(void) +{ + printf("spdxtool (%s %s)\n\n", PACKAGE_NAME, PACKAGE_VERSION); + printf("SPDX-License-Identifier: BSD-2-Clause\n\n"); + printf("Copyright (c) 2025 The FreeBSD Foundation\n\n"); + printf("Portions of this software were developed by\n"); + printf("Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from\n"); + printf("the FreeBSD Foundation\n\n"); + printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT); + return EXIT_SUCCESS; +} + +static int +usage(void) +{ + printf("usage: spdxtool [modules]\n"); + + printf("\nOptions:\n"); + + printf(" --agent-name Set agent name [default: 'Default']\n"); + printf(" --creation-time Use string as creation time (Should be in ISO8601 format) [default: current time]\n"); + printf(" --creation-id Use string as creation id [default: '_:creationinfo_1']\n"); + printf(" --help this message\n"); + printf(" --about print bomtool version and license to stdout\n"); + printf(" --version print bomtool version to stdout\n"); + printf(" --output FILE output SBOM data to file\n"); + printf(" --spdx-base-id URL Uset string as base of SPDX ids [default: %s]\n", xsd_any_uri_default_base); + printf(" --use-uri Use URIs not URLs as SPDX id"); + printf(" --define-variable=varname=value define variable global 'varname' as 'value'\n"); + + return EXIT_SUCCESS; +} + +int +main(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS; + pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default(); + char *creation_time = NULL; + char *creation_id = NULL; + char *agent_name = NULL; + char world_id[] = "virtual:world"; + char world_realname[] = "virtual world package"; + const char *spdx_id_base = xsd_any_url_default_base; + bool colon_sep = false; + pkgconf_pkg_t world = + { + .id = world_id, + .realname = world_realname, + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + error_msgout = stderr; + sbom_out = stdout; + + struct pkg_option options[] = + { + { "agent-name", required_argument, NULL, 100, }, + { "creation-time", required_argument, NULL, 101, }, + { "creation-id", required_argument, NULL, 102, }, + { "version", no_argument, &want_flags, PKG_VERSION, }, + { "about", no_argument, &want_flags, PKG_ABOUT, }, + { "help", no_argument, &want_flags, PKG_HELP, }, + { "output", required_argument, NULL, 103, }, + { "spdx-base-id", required_argument, NULL, 104, }, + { "use-uri", no_argument, NULL, 105, }, + { "define-variable", required_argument, NULL, 106, }, + { NULL, 0, NULL, 0 } + }; + + while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1) + { + switch (ret) + { + case 100: + agent_name = pkg_optarg; + break; + case 101: + creation_time = pkg_optarg; + break; + case 102: + creation_id = pkg_optarg; + break; + case 103: + sbom_out = fopen(pkg_optarg, "w"); + if (sbom_out == NULL) + { + pkgconf_output_file_fmt(stderr, "unable to open %s: %s\n", pkg_optarg, strerror(errno)); + return EXIT_FAILURE; + } + break; + case 104: + spdx_id_base = pkg_optarg; + break; + case 105: + // If SPDX id base have not been altered use default + if (!strcmp(spdx_id_base, xsd_any_url_default_base)) + spdx_id_base = xsd_any_uri_default_base; + colon_sep = true; + break; + case 106: + pkgconf_tuple_define_global(&pkg_client, pkg_optarg); + break; + case '?': + case ':': + return EXIT_FAILURE; + default: + break; + } + } + + pkgconf_client_init(&pkg_client, error_handler, NULL, personality, NULL, environ_lookup_handler); + + /* we have determined what features we want most likely. in some cases, we override later. */ + pkgconf_client_set_flags(&pkg_client, want_client_flags); + + /* at this point, want_client_flags should be set, so build the dir list */ + pkgconf_client_dir_list_build(&pkg_client, personality); + + + if ((want_flags & PKG_ABOUT) == PKG_ABOUT) + return about(); + + if ((want_flags & PKG_VERSION) == PKG_VERSION) + return version(); + + if ((want_flags & PKG_HELP) == PKG_HELP) + return usage(); + + while (1) + { + const char *package = argv[pkg_optind]; + + if (package == NULL) + break; + + /* check if there is a limit to the number of packages allowed to be included, if so and we have hit + * the limit, stop adding packages to the queue. + */ + if (maximum_package_count > 0 && pkgq.length > maximum_package_count) + break; + + while (isspace((unsigned char)package[0])) + package++; + + /* skip empty packages */ + if (package[0] == '\0') + { + pkg_optind++; + continue; + } + + if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) + { + pkgconf_queue_push(&pkgq, package); + pkg_optind++; + } + else + { + char packagebuf[PKGCONF_BUFSIZE]; + + snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); + pkg_optind += 3; + + pkgconf_queue_push(&pkgq, packagebuf); + } + } + + if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) + { + ret = EXIT_FAILURE; + goto out; + } + + spdxtool_util_set_uri_root(&pkg_client, spdx_id_base); + spdxtool_util_set_uri_separator_colon(&pkg_client, colon_sep); + spdxtool_util_set_spdx_license(&pkg_client, bom_license); + spdxtool_util_set_spdx_version(&pkg_client, spdx_version); + + if (!generate_spdx(&pkg_client, &world, creation_time, creation_id, agent_name)) + { + ret = EXIT_FAILURE; + goto out; + } + + ret = EXIT_SUCCESS; + +out: + pkgconf_solution_free(&pkg_client, &world); + pkgconf_queue_free(&pkgq); + pkgconf_cross_personality_deinit(personality); + pkgconf_client_deinit(&pkg_client); + + return ret; +} diff --git a/cli/spdxtool/serialize.c b/cli/spdxtool/serialize.c new file mode 100644 index 000000000000..69ef3a70a3b1 --- /dev/null +++ b/cli/spdxtool/serialize.c @@ -0,0 +1,467 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + * + * Copyright (C) 2026 Elizabeth Ashford. + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "core.h" +#include "software.h" +#include "simplelicensing.h" +#include "serialize.h" + +static void +serialize_escape_string(pkgconf_buffer_t *buffer, const char *s) +{ + for (const char *p = s; *p; p++) + { + switch (*p) + { + case '\"': + pkgconf_buffer_append(buffer, "\\\""); + break; + case '\\': + pkgconf_buffer_append(buffer, "\\\\"); + break; + case '\b': + pkgconf_buffer_append(buffer, "\\b"); + break; + case '\f': + pkgconf_buffer_append(buffer, "\\f"); + break; + case '\n': + pkgconf_buffer_append(buffer, "\\n"); + break; + case '\r': + pkgconf_buffer_append(buffer, "\\r"); + break; + case '\t': + pkgconf_buffer_append(buffer, "\\t"); + break; + default: + if (*p < 0x20) + pkgconf_buffer_append_fmt(buffer, "\\u%04x", (unsigned int)*p); + else + pkgconf_buffer_push_byte(buffer, *p); + } + } +} + +static inline void +serialize_add_indent(pkgconf_buffer_t *buffer, unsigned int level) +{ + for (; level; level--) + pkgconf_buffer_append(buffer, " "); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent) + * + * Serialize the given JSON to the buffer + * + * :param pkgconf_buffer_t *buffer: Buffer to add to. + * :param spdxtool_serialize_value *value: Value to serialize. + * :param unsigned int indent: Indent level + * :return: true on success, false on failure + */ +bool +spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent) +{ + if (!buffer || !value) + return false; + + switch(value->type) { + case SPDXTOOL_SERIALIZE_TYPE_STRING: + pkgconf_buffer_push_byte(buffer, '"'); + serialize_escape_string(buffer, value->value.s ? value->value.s : ""); + pkgconf_buffer_push_byte(buffer, '"'); + break; + case SPDXTOOL_SERIALIZE_TYPE_INT: + pkgconf_buffer_append_fmt(buffer, "%d", value->value.i); + break; + case SPDXTOOL_SERIALIZE_TYPE_BOOL: + pkgconf_buffer_append(buffer, value->value.b ? "true" : "false"); + break; + case SPDXTOOL_SERIALIZE_TYPE_NULL: + pkgconf_buffer_append(buffer, "null"); + break; + case SPDXTOOL_SERIALIZE_TYPE_OBJECT: + { + pkgconf_node_t *iter; + pkgconf_buffer_push_byte(buffer, '{'); + pkgconf_buffer_push_byte(buffer, '\n'); + + PKGCONF_FOREACH_LIST_ENTRY(value->value.o->entries.head, iter) + { + spdxtool_serialize_object_t *entry = iter->data; + serialize_add_indent(buffer, indent + 1); + pkgconf_buffer_append_fmt(buffer, "\"%s\": ", entry->key); + spdxtool_serialize_value_to_buf(buffer, entry->value, indent + 1); + if (iter->next) + pkgconf_buffer_push_byte(buffer, ','); + pkgconf_buffer_push_byte(buffer, '\n'); + } + + serialize_add_indent(buffer, indent); + pkgconf_buffer_push_byte(buffer, '}'); + break; + } + case SPDXTOOL_SERIALIZE_TYPE_ARRAY: + { + pkgconf_node_t *iter; + pkgconf_buffer_push_byte(buffer, '['); + pkgconf_buffer_push_byte(buffer, '\n'); + + PKGCONF_FOREACH_LIST_ENTRY(value->value.a->items.head, iter) + { + spdxtool_serialize_value_t *entry = iter->data; + serialize_add_indent(buffer, indent + 1); + spdxtool_serialize_value_to_buf(buffer, entry, indent + 1); + if (iter->next) + pkgconf_buffer_push_byte(buffer, ','); + pkgconf_buffer_push_byte(buffer, '\n'); + } + serialize_add_indent(buffer, indent); + pkgconf_buffer_push_byte(buffer, ']'); + break; + } + } + + return true; +} + +/* + * !doc + * + * .. 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) + * + * Add a key-value pair to a JSON object list. The key is copied internally. + * The object list takes ownership of the value. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string, copied internally. + * :param spdxtool_serialize_value_t *value: Value to associate with the key. Ownership transfers to the object list. + * :return: The value added, not owned by the caller. + */ +spdxtool_serialize_value_t * +spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value) +{ + if (!object_list || !value) + { + spdxtool_serialize_value_free(value); + return NULL; + } + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + spdxtool_serialize_object_t *object = calloc(1, sizeof(spdxtool_serialize_object_t)); + char *keycopy = key ? strdup(key) : strdup(""); + if (!node || !object || !keycopy) + { + free(node); + free(keycopy); + spdxtool_serialize_object_free(object); + spdxtool_serialize_value_free(value); + return NULL; + } + + object->key = keycopy; + object->value = value; + pkgconf_node_insert_tail(node, object, &object_list->entries); + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t value) + * + * Add a value to a JSON array. The array takes ownership of the value. + * + * :param spdxtool_serialize_array_t *array: Array to add to. + * :param spdxtool_serialize_value_t value: Value to append. Ownership transfers to the array. + * :return: The value added, not owned by the caller. + */ +spdxtool_serialize_value_t * +spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t *value) +{ + if (!array) + { + // Taking value, so free + spdxtool_serialize_value_free(value); + return NULL; + } + + pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + { + spdxtool_serialize_value_free(value); + return NULL; + } + + pkgconf_node_insert_tail(node, value, &array->items); + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_object_list_t *spdxtool_serialize_object_list_new(void) + * + * Allocate and initialize a new empty JSON object list. + * + * :return: Pointer to a new spdxtool_serialize_object_list_t, or NULL on allocation failure. + */ +spdxtool_serialize_object_list_t * +spdxtool_serialize_object_list_new(void) +{ + return calloc(1, sizeof(spdxtool_serialize_object_list_t)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_array_t *spdxtool_serialize_array_new(void) + * + * Allocate and initialize a new empty JSON array. + * + * :return: Pointer to a new spdxtool_serialize_array_t, or NULL on allocation failure. + */ +spdxtool_serialize_array_t * +spdxtool_serialize_array_new(void) +{ + return calloc(1, sizeof(spdxtool_serialize_array_t)); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_value_free(spdxtool_serialize_value_t *value) + * + * Free all resources owned by a JSON value. For strings, frees the string. + * For objects and arrays, recursively frees all children. The value pointer + * itself is not freed as it is assumed to be stack-allocated. + * + * :param spdxtool_serialize_value_t *value: Value to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_value_free(spdxtool_serialize_value_t *value) +{ + if (!value) + return; + + switch (value->type) + { + case SPDXTOOL_SERIALIZE_TYPE_STRING: + free(value->value.s); + break; + case SPDXTOOL_SERIALIZE_TYPE_ARRAY: + spdxtool_serialize_array_free(value->value.a); + break; + case SPDXTOOL_SERIALIZE_TYPE_OBJECT: + spdxtool_serialize_object_list_free(value->value.o); + break; + default: + // Nothing to do + break; + } + + free(value); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_object_free(spdxtool_serialize_object_t *object) + * + * Free a JSON object entry, including its key string and owned value. + * The object pointer itself is not freed by this function. + * + * :param spdxtool_serialize_object_t *object: Object entry to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_object_free(spdxtool_serialize_object_t *object) +{ + if (!object) + return; + + free(object->key); + spdxtool_serialize_value_free(object->value); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list) + * + * Free a JSON object list and all of its entries, including their keys and values. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list) +{ + if (!object_list) + return; + + pkgconf_node_t *iter_next = NULL, *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY_SAFE(object_list->entries.head, iter_next, iter) + { + spdxtool_serialize_object_t *object = iter->data; + spdxtool_serialize_object_free(object); + free(object); + free(iter); + } + + free(object_list); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_array_free(spdxtool_serialize_array_t *array) + * + * Free a JSON array and all of its elements. + * + * :param spdxtool_serialize_array_t *array: Array to free. May be NULL. + * :return: nothing + */ +void +spdxtool_serialize_array_free(spdxtool_serialize_array_t *array) +{ + if (!array) + return; + + pkgconf_node_t *iter_next = NULL, *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY_SAFE(array->items.head, iter_next, iter) + { + spdxtool_serialize_value_t *value = iter->data; + spdxtool_serialize_value_free(value); + free(iter); + } + + free(array); +} + +/* + * !doc + * + * .. 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) + * + * Serialize a complete SPDX SBOM document to a JSON-LD value tree. Iterates + * all SBOMs, packages, relationships, and license expressions registered on + * the document. The SpdxDocument object is emitted last to ensure all element + * IDs have been populated by prior iteration. This function must be called + * after pkgconf_pkg_traverse has completed so that all packages and their + * dependencies are registered on spdx. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_core_agent_t *agent: Agent struct to include in the document. + * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to include in the document. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct containing all registered SBOMs, packages, relationships, and licenses. + * :return: spdxtool_serialize_value_t * representing the complete JSON-LD document, or a null string value on allocation failure. + */ +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) +{ + const char *errstr = "out of memory"; + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_array_t *graph = NULL; + spdxtool_serialize_object_list_t *root = spdxtool_serialize_object_list_new(); + if (!root) + goto err; + + if (!spdxtool_serialize_object_add_string(root, "@context", "https://spdx.org/rdf/3.0.1/spdx-context.jsonld")) + goto err; + + graph = spdxtool_serialize_array_new(); + if (!graph) + goto err; + + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_agent_to_object(client, agent))) + goto err; + + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_creation_info_to_object(client, creation))) + goto err; + + pkgconf_node_t *iter = NULL; + PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter) + { + spdxtool_simplelicensing_license_expression_t *expression = iter->data; + if (!expression) + { + errstr = "licenses list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_simplelicensing_licenseExpression_to_object(client, spdx->creation_info, expression))) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter) + { + spdxtool_software_sbom_t *current_sbom = iter->data; + if (!current_sbom) + { + errstr = "sbom list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_sbom_to_object(client, current_sbom))) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->packages.head, iter) + { + pkgconf_pkg_t *pkg = iter->data; + if (!pkg) + { + errstr = "pkg list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_package_to_object(client, pkg, spdx))) + goto err; + } + + PKGCONF_FOREACH_LIST_ENTRY(spdx->relationships.head, iter) + { + spdxtool_core_relationship_t *relationship = iter->data; + if (!relationship) + { + errstr = "relationship list corrupted"; + goto err; + } + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_relationship_to_object(client, relationship))) + goto err; + } + + // SpdxDocument last — spdx->element must be fully populated first + if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_spdx_document_to_object(client, spdx))) + goto err; + + bool ok = spdxtool_serialize_object_add_array(root, "@graph", graph); + graph = NULL; + if (!ok) + goto err; + + ret = spdxtool_serialize_value_object(root); + root = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_serialize_sbom: %s", errstr); + + spdxtool_serialize_object_list_free(root); + spdxtool_serialize_array_free(graph); + return ret; +} diff --git a/cli/spdxtool/serialize.h b/cli/spdxtool/serialize.h new file mode 100644 index 000000000000..e04b7f38b741 --- /dev/null +++ b/cli/spdxtool/serialize.h @@ -0,0 +1,539 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + * + * Copyright (C) 2026 Elizabeth Ashford. + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" + +#ifndef CLI__SPDXTOOL__SERIALIZE_H +#define CLI__SPDXTOOL__SERIALIZE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum spdxtool_serialize_type_ +{ + SPDXTOOL_SERIALIZE_TYPE_STRING, // JSON string type + SPDXTOOL_SERIALIZE_TYPE_INT, // JSON number type (int) + SPDXTOOL_SERIALIZE_TYPE_BOOL, // JSON bool type + SPDXTOOL_SERIALIZE_TYPE_NULL, // JSON null type + SPDXTOOL_SERIALIZE_TYPE_OBJECT, // JSON object type + SPDXTOOL_SERIALIZE_TYPE_ARRAY // JSON array type +} spdxtool_serialize_type_t; + +typedef struct spdxtool_serialize_value_ { + spdxtool_serialize_type_t type; + union { + char *s; + int i; + bool b; + struct spdxtool_serialize_object_list_ *o; + struct spdxtool_serialize_array_ *a; + } value; +} spdxtool_serialize_value_t; + +typedef struct spdxtool_serialize_object_ { + char *key; + spdxtool_serialize_value_t *value; +} spdxtool_serialize_object_t; + +typedef struct spdxtool_serialize_object_list_ { + pkgconf_list_t entries; +} spdxtool_serialize_object_list_t; + +typedef struct spdxtool_serialize_array_ { + pkgconf_list_t items; +} spdxtool_serialize_array_t; + +bool +spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent); + +spdxtool_serialize_value_t * +spdxtool_serialize_value_dup(const spdxtool_serialize_value_t *value); + +spdxtool_serialize_value_t * +spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t* value); + +spdxtool_serialize_object_list_t * +spdxtool_serialize_object_list_new(void); + +spdxtool_serialize_array_t * +spdxtool_serialize_array_new(void); + +spdxtool_serialize_value_t * +spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t* value); + +void +spdxtool_serialize_value_free(spdxtool_serialize_value_t *value); + +void +spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list); + +void +spdxtool_serialize_object_free(spdxtool_serialize_object_t *object); + +void +spdxtool_serialize_array_free(spdxtool_serialize_array_t *array); + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t * spdxtool_serialize_value_string(const char *s) + * + * Construct a JSON string value. The string is copied internally. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param const char *s: String to copy. May be NULL, in which case the value holds NULL. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_string(const char *s) +{ + if (!s) + return NULL; + + char *sv = strdup(s); + if (!sv) + return NULL; + + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + { + free(sv); + return NULL; + } + + value->type = SPDXTOOL_SERIALIZE_TYPE_STRING; + value->value.s = sv; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_int(int d) + * + * Construct a JSON integer value. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param int d: int value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_int(int i) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_INT; + value->value.i = i; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_bool(bool b) + * + * Construct a JSON boolean value. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param bool b: Boolean value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_bool(bool b) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_BOOL; + value->value.b = b; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_null(void) + * + * Construct a JSON null value. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_null(void) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_NULL; + return value; +} + + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_object(spdxtool_serialize_object_list_t *object_list) + * + * Construct a JSON object value wrapping an existing object list. + * The returned value takes ownership of the object list. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to wrap. Ownership transfers to the returned value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_object(spdxtool_serialize_object_list_t *object_list) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_OBJECT; + value->value.o = object_list; + return value; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_array(spdxtool_serialize_array_t *array) + * + * Construct a JSON array value wrapping an existing array. + * The returned value takes ownership of the array. + * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free(). + * + * :param spdxtool_serialize_array_t *array: Array to wrap. Ownership transfers to the returned value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_value_array(spdxtool_serialize_array_t *array) +{ + spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t)); + if (!value) + return NULL; + + value->type = SPDXTOOL_SERIALIZE_TYPE_ARRAY; + value->value.a = array; + return value; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_serialize_object_add_string(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) + * + * Add a string key-value pair to a JSON object. The string is copied internally. + * Unconditionally adds the key even if value is NULL. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param const char *value: String value to copy. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_string(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_string(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_string_opt(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) + * + * Add a string key-value pair to a JSON object only if value is non-NULL. + * Use this for optional fields that should be omitted entirely when absent. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param const char *value: String value to copy, or NULL to skip. + * :return: If value is set: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the object. + * This object is not owned by the caller. + * If value is not set: NULL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_string_opt(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value) +{ + if (value) + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_string(value)); + + return NULL; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_int(spdxtool_serialize_object_list_t *object_list, const char *key, int value) + * + * Add a int key-value pair to a JSON object. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param int value: Integer value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_int(spdxtool_serialize_object_list_t *object_list, const char *key, int value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_int(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_bool(spdxtool_serialize_object_list_t *object_list, const char *key, bool value) + * + * Add a boolean key-value pair to a JSON object. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param bool value: Boolean value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_bool(spdxtool_serialize_object_list_t *object_list, const char *key, bool value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_bool(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_null(spdxtool_serialize_object_list_t *object_list, const char *key) + * + * Add a null key-value pair to a JSON object. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_null(spdxtool_serialize_object_list_t *object_list, const char *key) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_null()); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_object(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_object_list_t *value) + * + * Add an object key-value pair to a JSON object. + * This takes ownership of the object in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param spdxtool_serialize_object_list_t *value: Object value to add. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_object(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_object_list_t *value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_object(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_array(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_array_t *value) + * + * Add an array key-value pair to a JSON object. + * This takes ownership of the array in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_object_list_t *object_list: Object list to add to. + * :param const char *key: Key string. + * :param spdxtool_serialize_array_t *value: Array value to add. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY, located in the object. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_object_add_array(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_array_t *value) +{ + return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_array(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_string(spdxtool_serialize_array_t *array, const char *value) + * + * Append a string value to a JSON array. The string is copied internally. + * Unconditionally appends even if value is NULL. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param const char *value: String value to copy. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_string(spdxtool_serialize_array_t *array, const char *value) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_string(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_string_opt(spdxtool_serialize_array_t *a, const char *value) + * + * Append a string value to a JSON array only if value is non-NULL. + * Use this for optional array entries that should be omitted when absent. + * + * :param spdxtool_serialize_array_t *a: Array to append to. + * :param const char *value: String value to copy, or NULL to skip. + * :return: If value is set: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the array. + * This object is not owned by the caller. + * If value is not set: NULL. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_string_opt(spdxtool_serialize_array_t *array, const char *value) +{ + if (value) + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_string(value)); + + return NULL; +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_int(spdxtool_serialize_array_t *array, int value) + * + * Append a int value to a JSON array. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param int value: integer value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_int(spdxtool_serialize_array_t *array, int value) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_int(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_bool(spdxtool_serialize_array_t *array, bool value) + * + * Append a boolean value to a JSON array. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param bool value: Boolean value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_bool(spdxtool_serialize_array_t *array, bool value) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_bool(value)); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_null(spdxtool_serialize_array_t *array) + * + * Append a null value to a JSON array. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_null(spdxtool_serialize_array_t *array) +{ + return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_null()); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_object(spdxtool_serialize_array_t *array, spdxtool_serialize_object_list_t *value) + * + * Append an object value to a JSON array. + * This takes ownership of the object in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param spdxtool_serialize_object_list_t *value: Object value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_object(spdxtool_serialize_array_t *array, spdxtool_serialize_object_list_t *value) +{ + if (!value) + return NULL; + + spdxtool_serialize_value_t *ret = spdxtool_serialize_value_object(value); + if (!ret) + { + // Since we take possession of the pointer unconditionally, clean up. + spdxtool_serialize_object_list_free(value); + return NULL; + } + + return spdxtool_serialize_array_add_take(array, ret); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_array(spdxtool_serialize_array_t *array, spdxtool_serialize_array_t *value) + * + * Append an array value to a JSON array. + * This takes ownership of the array in value unconditionally, freeing on failure. + * + * :param spdxtool_serialize_array_t *array: Array to append to. + * :param spdxtool_serialize_array_t *value: Array value. + * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY, located in the array. + * This object is not owned by the caller. + */ +static inline spdxtool_serialize_value_t * +spdxtool_serialize_array_add_array(spdxtool_serialize_array_t *array, spdxtool_serialize_array_t *value) +{ + if (!value) + return NULL; + + spdxtool_serialize_value_t *ret = spdxtool_serialize_value_array(value); + if (!ret) + { + // Since we take possession of the pointer unconditionally, clean up. + spdxtool_serialize_array_free(value); + return NULL; + } + + return spdxtool_serialize_array_add_take(array, ret); +} + +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); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/simplelicensing.c b/cli/spdxtool/simplelicensing.c new file mode 100644 index 000000000000..a9823b9b3b6c --- /dev/null +++ b/cli/spdxtool/simplelicensing.c @@ -0,0 +1,113 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" +#include "serialize.h" +#include "software.h" +#include "core.h" +#include "simplelicensing.h" + +/* + * !doc + * + * .. c:function:: spdxtool_simplelicensing_license_expression_t *spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, char *license) + * + * Create new /SimpleLicensing/SimpleLicensingText struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param char *license: SPDX name of license + * :return: NULL if some problem occurs and SimpleLicensingText struct if not + */ +spdxtool_simplelicensing_license_expression_t * +spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, const char *license) +{ + if (!client || !license) + return NULL; + + spdxtool_simplelicensing_license_expression_t *expression = calloc(1, sizeof(spdxtool_simplelicensing_license_expression_t)); + if (!expression) + goto err; + + expression->type = "simplelicensing_LicenseExpression"; + expression->license_expression = strdup(license); + expression->spdx_id = spdxtool_util_get_spdx_id_string(client, expression->type, license); + + if (!expression->license_expression || !expression->spdx_id) + goto err; + + return expression; + +err: + pkgconf_error(client, "spdxtool_simplelicensing_licenseExpression_new: out of memory"); + spdxtool_simplelicensing_licenseExpression_free(expression); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression) + * + * Free /SimpleLicensing/SimpleLicensingText struct + * + * :param spdxtool_simplelicensing_license_expression_t *expression: SimpleLicensingText struct to be freed. + * :return: nothing + */ +void +spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression) +{ + if(!expression) + return; + + free(expression->spdx_id); + free(expression->license_expression); + + free(expression); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_simplelicensing_licenseExpression_to_object(const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression) + * + * Serialize /SimpleLicensing/LicenseExpression struct to a JSON value tree. + * + * :param const char *creation_info: The creationInfo ID string to embed in the object. + * :param const spdxtool_simplelicensing_license_expression_t *expression: LicenseExpression struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the LicenseExpression object. + */ +spdxtool_serialize_value_t * +spdxtool_simplelicensing_licenseExpression_to_object(pkgconf_client_t *client, const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + if (!(spdxtool_serialize_object_add_string(object_list, "type", "simplelicensing_LicenseExpression") && + spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", expression->spdx_id) && + spdxtool_serialize_object_add_string(object_list, "simplelicensing_licenseExpression", expression->license_expression))) + { + goto err; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_simplelicensing_licenseExpression_to_object: out of memory"); + + spdxtool_serialize_object_list_free(object_list); + return ret; +} diff --git a/cli/spdxtool/simplelicensing.h b/cli/spdxtool/simplelicensing.h new file mode 100644 index 000000000000..328136c620c7 --- /dev/null +++ b/cli/spdxtool/simplelicensing.h @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include "util.h" + +#ifndef CLI__SPDXTOOL__SIMPLELICENSING_H +#define CLI__SPDXTOOL__SIMPLELICENSING_H + + +#ifdef __cplusplus +extern "C" { +#endif + +spdxtool_simplelicensing_license_expression_t * +spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, const char *license); + +void +spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression); + +spdxtool_serialize_value_t * +spdxtool_simplelicensing_licenseExpression_to_object(pkgconf_client_t *client, const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/software.c b/cli/spdxtool/software.c new file mode 100644 index 000000000000..4e9712ba6942 --- /dev/null +++ b/cli/spdxtool/software.c @@ -0,0 +1,447 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <stdlib.h> +#include <string.h> +#include <libpkgconf/libpkgconf.h> +#include "util.h" +#include "serialize.h" +#include "software.h" +#include "core.h" + +/* + * !doc + * + * .. 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) + * + * Create new /Software/Sbom struct + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param const char *spdx_id: spdxId for this SBOM element + * :param const char *creation_id: id for CreationInfo + * :param const char *sbom_type: Sbom types can be found SPDX documention + * :return: NULL if some problem occurs and Sbom struct if not + */ +spdxtool_software_sbom_t * +spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type) +{ + if (!client || !spdx_id || !creation_id || !sbom_type) + return NULL; + + spdxtool_software_sbom_t *sbom = calloc(1, sizeof(spdxtool_software_sbom_t)); + if (!sbom) + goto err; + + sbom->type = "software_Sbom"; + sbom->spdx_id = strdup(spdx_id); + sbom->creation_info = strdup(creation_id); + sbom->sbom_type = strdup(sbom_type); + + if (!sbom->spdx_id || !sbom->creation_info || !sbom->sbom_type) + goto err; + + return sbom; + +err: + pkgconf_error(client, "spdxtool_software_sbom_new: out of memory"); + spdxtool_software_sbom_free(sbom); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom) + * + * Free /Software/Sbom struct + * + * :param spdxtool_software_sbom_t *sbom: Sbom struct to be freed. + * :return: nothing + */ +void +spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom) +{ + if(!sbom) + return; + + free(sbom->spdx_id); + free(sbom->creation_info); + free(sbom->sbom_type); + + free(sbom); +} + +/* + * !doc + * + * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom) + * + * Serialize /Software/Sbom struct to a JSON value tree. As a side effect, + * the package associated with the SBOM's rootElement is registered on the + * document via spdxtool_core_spdx_document_add_package, and relationship + * element IDs are registered via spdxtool_core_spdx_document_add_element. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param spdxtool_software_sbom_t *sbom: Sbom struct to be serialized. + * :return: spdxtool_serialize_value_t * representing the Sbom object. + */ +spdxtool_serialize_value_t * +spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = NULL; + spdxtool_serialize_array_t *sbom_type_array = NULL; + spdxtool_serialize_array_t *root_element_array = NULL; + spdxtool_serialize_array_t *element_array = NULL; + char *spdx_id = NULL; + + char sep = spdxtool_util_get_uri_separator(client); + + spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId"); + if (!spdx_id) + goto err; + + object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + sbom_type_array = spdxtool_serialize_array_new(); + if (!sbom_type_array) + goto err; + + if (!spdxtool_serialize_array_add_string(sbom_type_array, sbom->sbom_type)) + goto err; + + root_element_array = spdxtool_serialize_array_new(); + if (!root_element_array) + goto err; + + if (!spdxtool_serialize_array_add_string(root_element_array, spdx_id)) + goto err; + + element_array = spdxtool_serialize_array_new(); + if (!element_array) + goto err; + + pkgconf_node_t *node = NULL; + PKGCONF_FOREACH_LIST_ENTRY(sbom->rootElement->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id); + char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); + if (!relationship_str) + goto err; + + char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); + free(relationship_str); + if (!spdx_id_relation) + goto err; + + if (!spdxtool_serialize_array_add_string(element_array, spdx_id_relation)) + { + free(spdx_id_relation); + goto err; + } + + if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, spdx_id_relation)) + { + free(spdx_id_relation); + goto err; + } + + free(spdx_id_relation); + } + + char *value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasDeclaredLicense"); + if (value) + { + if (!spdxtool_serialize_array_add_string(element_array, value)) + { + free(value); + goto err; + } + + if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value)) + { + free(value); + goto err; + } + + free(value); + } + + value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasConcludedLicense"); + if (value) + { + if (!spdxtool_serialize_array_add_string(element_array, value)) + { + free(value); + goto err; + } + + if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value)) + { + free(value); + goto err; + } + + free(value); + } + + if (!(spdxtool_serialize_object_add_string(object_list, "type", sbom->type) && + spdxtool_serialize_object_add_string(object_list, "creationInfo", sbom->creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", sbom->spdx_id))) + { + goto err; + } + + if (!spdxtool_serialize_object_add_array(object_list, "software_sbomType", sbom_type_array)) + goto err; + sbom_type_array = NULL; + + if (!spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array)) + goto err; + root_element_array = NULL; + + if (!spdxtool_serialize_object_add_array(object_list, "element", element_array)) + goto err; + element_array = NULL; + + if (!spdxtool_core_spdx_document_add_package(client, sbom->spdx_document, sbom->rootElement)) + goto err; + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_software_sbom_to_object: out of memory"); + + free(spdx_id); + spdxtool_serialize_object_list_free(object_list); + spdxtool_serialize_array_free(sbom_type_array); + spdxtool_serialize_array_free(root_element_array); + spdxtool_serialize_array_free(element_array); + return ret; +} + +static bool +serialize_copyright_lines_to_object(spdxtool_serialize_object_list_t *object_list, const pkgconf_list_t *copyright_lines) +{ + pkgconf_buffer_t copyright_buf = PKGCONF_BUFFER_INITIALIZER; + const pkgconf_node_t *node; + + if (copyright_lines->head == NULL) + return spdxtool_serialize_object_add_string(object_list, "software_copyrightText", "NOASSERTION") != NULL; + + PKGCONF_FOREACH_LIST_ENTRY(copyright_lines->head, node) + { + const pkgconf_bufferset_t *set = node->data; + pkgconf_buffer_join(©right_buf, '\n', pkgconf_buffer_str_or_empty(&set->buffer), NULL); + } + + bool ok = spdxtool_serialize_object_add_string(object_list, "software_copyrightText", pkgconf_buffer_str_or_empty(©right_buf)) != NULL; + pkgconf_buffer_finalize(©right_buf); + return ok; +} + +/* + * !doc + * + * .. 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) + * + * Serialize /Software/Package struct to a JSON value tree. As a side effect, + * any license and dependency relationships generated during serialization are + * added to the document via spdxtool_core_spdx_document_add_relationship. + * + * :param pkgconf_client_t *client: The pkgconf client being accessed. + * :param pkgconf_pkg_t *pkg: Package struct to be serialized. + * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument to which generated relationships are added. + * :return: spdxtool_serialize_value_t * representing the Package object. + */ +spdxtool_serialize_value_t * +spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx) +{ + spdxtool_serialize_value_t *ret = NULL; + spdxtool_serialize_object_list_t *object_list = NULL; + spdxtool_serialize_array_t *originated_by = NULL; + char *creation_info = NULL; + char *spdx_id = NULL; + char *agent = NULL; + char *spdx_id_license = NULL; + pkgconf_list_t relations = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t *cpy_relations = NULL; + pkgconf_node_t *node = NULL; + char sep = spdxtool_util_get_uri_separator(client); + + creation_info = spdxtool_util_tuple_lookup(client, &pkg->vars, "creationInfo"); + spdx_id = spdxtool_util_tuple_lookup(client, &pkg->vars, "spdxId"); + agent = spdxtool_util_tuple_lookup(client, &pkg->vars, "agent"); + + if (!creation_info || !spdx_id || !agent) + goto err; + + object_list = spdxtool_serialize_object_list_new(); + if (!object_list) + goto err; + + originated_by = spdxtool_serialize_array_new(); + if (!originated_by) + goto err; + + if (!spdxtool_serialize_array_add_string(originated_by, agent)) + goto err; + + if (!(spdxtool_serialize_object_add_string(object_list, "type", "software_Package") && + spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) && + spdxtool_serialize_object_add_string(object_list, "spdxId", spdx_id) && + spdxtool_serialize_object_add_string(object_list, "name", pkg->realname))) + { + goto err; + } + + if (!spdxtool_serialize_object_add_array(object_list, "originatedBy", originated_by)) + goto err; + originated_by = NULL; + + if (!serialize_copyright_lines_to_object(object_list, &pkg->copyright)) + goto err; + + if (!spdxtool_serialize_object_add_string(object_list, "software_homePage", + pkg->url ? pkg->url : "")) + { + goto err; + } + + if (!spdxtool_serialize_object_add_string(object_list, "software_downloadLocation", + pkg->source ? pkg->source : "")) + { + goto err; + } + + if (!spdxtool_serialize_object_add_string(object_list, "software_packageVersion", pkg->version)) + goto err; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) + { + const pkgconf_license_t *license = node->data; + if (license->type == PKGCONF_LICENSE_EXPRESSION) + { + spdx_id_license = spdxtool_util_get_spdx_id_string(client, "simplelicensing_LicenseExpression", license->data); + if (!spdx_id_license) + goto err; + + pkgconf_license_insert(client, &relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_license); + free(spdx_id_license); + spdx_id_license = NULL; + } + } + + char *tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasDeclaredLicense"); + if (tuple_license) + { + cpy_relations = calloc(1, sizeof(pkgconf_list_t)); + if (!cpy_relations) + { + free(tuple_license); + goto err; + } + + pkgconf_license_copy_list(client, cpy_relations, &relations); + spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasDeclaredLicense"); + free(tuple_license); + if (!relationship) + goto err; + if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) + goto err; + cpy_relations = NULL; + } + + tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasConcludedLicense"); + if (tuple_license) + { + cpy_relations = calloc(1, sizeof(pkgconf_list_t)); + if (!cpy_relations) + { + free(tuple_license); + goto err; + } + + pkgconf_license_copy_list(client, cpy_relations, &relations); + spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasConcludedLicense"); + free(tuple_license); + if (!relationship) + goto err; + if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) + goto err; + cpy_relations = NULL; + } + pkgconf_license_free(&relations); + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id); + char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); + if (!relationship_str) + goto err; + + char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str); + free(relationship_str); + if (!spdx_id_relation) + goto err; + + char *spdx_id_package = spdxtool_util_get_spdx_id_string(client, "Package", match->id); + if (!spdx_id_package) + { + free(spdx_id_relation); + goto err; + } + + cpy_relations = calloc(1, sizeof(pkgconf_list_t)); + if (!cpy_relations) + { + free(spdx_id_relation); + free(spdx_id_package); + goto err; + } + + pkgconf_license_insert(client, cpy_relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_package); + spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, spdx_id_relation, spdx_id, cpy_relations, "dependsOn"); + free(spdx_id_relation); + free(spdx_id_package); + if (!relationship) + goto err; + if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship)) + goto err; + cpy_relations = NULL; + } + + ret = spdxtool_serialize_value_object(object_list); + object_list = NULL; + +err: + if (!ret) + pkgconf_error(client, "spdxtool_software_package_to_object: out of memory"); + + free(creation_info); + free(spdx_id); + free(agent); + free(spdx_id_license); + spdxtool_serialize_object_list_free(object_list); + spdxtool_serialize_array_free(originated_by); + return ret; +} diff --git a/cli/spdxtool/software.h b/cli/spdxtool/software.h new file mode 100644 index 000000000000..d18710cb0d1b --- /dev/null +++ b/cli/spdxtool/software.h @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef CLI__SPDXTOOL_SOFTWARE_H +#define CLI__SPDXTOOL_SOFTWARE_H + +#include <stdlib.h> +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +spdxtool_software_sbom_t * +spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type); + +void +spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom); + +spdxtool_serialize_value_t * +spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *doc); + +spdxtool_serialize_value_t * +spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cli/spdxtool/util.c b/cli/spdxtool/util.c new file mode 100644 index 000000000000..e47bf0a1e61f --- /dev/null +++ b/cli/spdxtool/util.c @@ -0,0 +1,331 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <libpkgconf/libpkgconf.h> +#include <libpkgconf/stdinc.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <ctype.h> +#include "util.h" + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default) + * + * Set key wit default value. Default value is used if key is NULL + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char *key: Key to be preserved + * :param const char *key_value: Value for key + * :param const char *key_default: Default value if key_value is NULL + * :return: nothing + */ +void +spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default) +{ + PKGCONF_TRACE(client, "set uri_root to: %s", key_value != NULL ? key_value : key_default); + pkgconf_tuple_add_global(client, key, key_value != NULL ? key_value : key_default); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root) + * + * Set URI/URL root for spdxId. Type for this is 'xsd:anyURI' which means + * it can they may be absolute or relative. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char *uri_root: URI root. + * :return: nothing + */ +void +spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root) +{ + spdxtool_util_set_key(client, "spdx_uri_root", uri_root, "https://github.com/pkgconf/pkgconf"); +} + +/* + * !doc + * + * .. c:function:: const char *spdxtool_util_get_uri_root(pkgconf_client_t *client) + * + * Get current URI + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: URI or NULL + */ +const char * +spdxtool_util_get_uri_root(pkgconf_client_t *client) +{ + return pkgconf_tuple_find_global(client, "spdx_uri_root"); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool is_colon) + * + * when using URI rather than URL change separator from slash '/' to colon ':'. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param bool is_colon If true use colon ':' is separator and otherwise slash '/' + * :return: nothing + */ +void +spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool is_colon) +{ + if (is_colon == true) + client->flags |= SPDXTOOL_SEPARATOR_COLON; + else + client->flags = client->flags & ~SPDXTOOL_SEPARATOR_COLON; +} + +/* + * !doc + * + * .. c:function:: char spdxtool_util_get_uri_separator(pkgconf_client_t *client) + * + * Get separator char + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: Colon ':' or slash '/' + */ +char +spdxtool_util_get_uri_separator(pkgconf_client_t *client) +{ + if (client->flags & SPDXTOOL_SEPARATOR_COLON) + return ':'; + + return '/'; +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version) + * + * Set current SPDX SBOM Spec version. If not set it's 3.0.1 + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param char* spdx_version: SPDX specification version. + * :return: nothing + */ +void +spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version) +{ + spdxtool_util_set_key(client, "spdx_version", spdx_version, "3.0.1"); +} + +/* + * !doc + * + * .. c:function:: const char *spdxtool_util_get_spdx_version(pkgconf_client_t *client) + * + * Get SPDX SBOM specification version in use + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: Spec version + */ +const char * +spdxtool_util_get_spdx_version(pkgconf_client_t *client) +{ + return pkgconf_tuple_find_global(client, "spdx_version"); +} + +/* + * !doc + * + * .. c:function:: void spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license) + * + * Under which license SBOM is released. License string should be in + * SPDX style. Something like: FreeBSD-DOC, CC0-1.0 or MIT + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char *spdx_license: SPDX compatible license + * :return: nothing + */ +void +spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license) +{ + spdxtool_util_set_key(client, "spdx_license", spdx_license, "CC0-1.0"); +} + +/* + * !doc + * + * .. c:function:: const char *spdxtool_util_get_spdx_license(pkgconf_client_t *client) + * + * Get license which SBOM is release in + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :return: SPDX license + */ +const char * +spdxtool_util_get_spdx_license(pkgconf_client_t *client) +{ + return pkgconf_tuple_find_global(client, "spdx_license"); +} + +static size_t last_id = 0; + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, char *part) + * + * Get spdxId with current URI. + * URI is lookg like https://test/part/1 + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param char *part: Addition subdir part before number + * :return: URI + */ +char * +spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, const char *part) +{ + const char *global_xsd_any_uri = spdxtool_util_get_uri_root(client); + char sep = spdxtool_util_get_uri_separator(client); + pkgconf_buffer_t current_uri = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_join(¤t_uri, sep, global_xsd_any_uri, part, NULL); + pkgconf_buffer_append_fmt(¤t_uri, "%c" SIZE_FMT_SPECIFIER, sep, ++last_id); + + return pkgconf_buffer_freeze(¤t_uri); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id) + * + * Get string id URI + * looks something like: https://test/part/string_id + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param const char* part: subdir part of URI. + * :param const char* string_id: String ID. + * :return: URI + */ +char * +spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id) +{ + const char *global_xsd_any_uri = spdxtool_util_get_uri_root(client); + char sep = spdxtool_util_get_uri_separator(client); + pkgconf_buffer_t current_uri = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_join(¤t_uri, sep, global_xsd_any_uri, part, string_id, NULL); + + return pkgconf_buffer_freeze(¤t_uri); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_iso8601_time(time_t *wanted_time) + * + * Get ISO8601 time string + * + * :param time_t *wanted_time: Time to be converted + * :return: Time string in ISO8601 format + */ +char * +spdxtool_util_get_iso8601_time(time_t *wanted_time) +{ + char buf[PKGCONF_ITEM_SIZE]; + + if (!wanted_time) + return NULL; + + struct tm *tm_info = gmtime(wanted_time); + if (!tm_info) + return NULL; + + /* ISO8601 time with Z at the end */ + strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", tm_info); + return strdup(buf); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_get_current_iso8601_time(void) + * + * Get ISO8601 current timestamp + * + * :return: Time string in ISO8601 format + */ +char * +spdxtool_util_get_current_iso8601_time(void) +{ + time_t now; + time(&now); + return spdxtool_util_get_iso8601_time(&now); +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_string_correction(char *str) + * + * Lowercase string and change spaces to '_' + * + * :param char *str: String to be converted + * :return: Converted string + */ +char * +spdxtool_util_string_correction(char *str) +{ + char *ptr = str; + /* Lowecase string and make spaces '_' */ + for ( ; *ptr; ++ptr) + { + *ptr = (char) tolower(*ptr); + if(isspace((unsigned char) *ptr)) + { + *ptr = '_'; + } + } + + return str; +} + +/* + * !doc + * + * .. c:function:: char *spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key) + * + * Lowercase string and change spaces to '_' + * + * :param pkgconf_client_t *client: client to use for the lookup + * :param pkgconf_list_t *vars: locally-scoped vars to look through + * :param char *key: key to lookup + * :return: Converted string + */ +char * +spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key) +{ + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_eval_ctx_t ctx = { + .client = client, + .vars = vars, + }; + + pkgconf_variable_t *v = pkgconf_bytecode_eval_lookup_var(&ctx, key, strlen(key)); + if (v == NULL) + return NULL; + + if (!pkgconf_variable_eval(client, vars, v, &buf, NULL)) + return NULL; + + return pkgconf_buffer_freeze(&buf); +} diff --git a/cli/spdxtool/util.h b/cli/spdxtool/util.h new file mode 100644 index 000000000000..be86d6e154d7 --- /dev/null +++ b/cli/spdxtool/util.h @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef CLI__SPDXTOOL__UTIL_H +#define CLI__SPDXTOOL__UTIL_H + +#include <libpkgconf/libpkgconf.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPDXTOOL_SEPARATOR_COLON 0x80 + +typedef struct spdxtool_core_agent_ +{ + char *spdx_id; + const char *type; + char *creation_info; + char *name; +} spdxtool_core_agent_t; + +typedef struct spdxtool_core_creation_info_ +{ + char *id; + const char *type; + char *created; + char *created_by; + const char *created_using; + char *spec_version; +} spdxtool_core_creation_info_t; + +typedef struct spdxtool_core_spdx_document +{ + const char *type; + char *spdx_id; + char *creation_info; + char *agent; + pkgconf_list_t licenses; + pkgconf_list_t element; + pkgconf_list_t rootElement; + pkgconf_list_t relationships; + pkgconf_list_t packages; +} spdxtool_core_spdx_document_t; + +typedef struct spdxtool_software_sbom_ +{ + char *spdx_id; + const char *type; + char *creation_info; + char *sbom_type; + spdxtool_core_spdx_document_t *spdx_document; + pkgconf_pkg_t *rootElement; +} spdxtool_software_sbom_t; + +typedef struct spdxtool_simplelicensing_license_expression_ +{ + const char *type; + char *spdx_id; + char *license_expression; +} spdxtool_simplelicensing_license_expression_t; + +typedef struct spdxtool_core_relationship_ +{ + const char *type; + char *spdx_id; + char *creation_info; + char *from; + pkgconf_list_t *to; + char *relationship_type; +} spdxtool_core_relationship_t; + +void +spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default); + +void +spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root); + +void +spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool sep); + +char +spdxtool_util_get_uri_separator(pkgconf_client_t *client); + +const char * +spdxtool_util_get_uri_root(pkgconf_client_t *client); + +void +spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version); + +const char * +spdxtool_util_get_spdx_version(pkgconf_client_t *client); + +void +spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license); + +const char * +spdxtool_util_get_spdx_license(pkgconf_client_t *client); + +char * +spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, const char *part); + +char * +spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id); + +char * +spdxtool_util_get_iso8601_time(time_t *wanted_time); + +char * +spdxtool_util_get_current_iso8601_time(void); + +char * +spdxtool_util_string_correction(char *str); + +char * +spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key); + +#ifdef __cplusplus +} +#endif + +#endif |
