Files
freebsd-src/libpkgconf/license.c
T
Pierre Pronchery fe9da42ef1 Vendor import of pkgconf 3.0.9
Obtained from https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-3.0.9.tar.gz

SHA1: a390f016a7a5ff419c9eef9e2b2c01fe86c11971  -
SHA256: 32310a33bb97425ebc0707cb5af59f9dc05d10282dcd4db628a45ed904993dc6  -
SHA512: 30776d31f860910286b96fbf60a19b5a6d1e73cb6a1077a019d763ae6e5ed1e8d81bc3649978c83f40e5e90ae779f7e530c513b700c8e73bb6399883dd6ef7ab  -

Sponsored by:	The FreeBSD Foundation
2026-11-02 00:28:01 +01:00

374 lines
10 KiB
C

/*
* license.c
* Evaluate SPDX license expressions
*
* SPDX-License-Identifier: pkgconf
*
* Copyright (c) 2026 pkgconf authors (see AUTHORS).
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* This software is provided 'as is' and without any warranty, express or
* implied. In no event shall the authors be liable for any damages arising
* from the use of this software.
*/
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
static bool license_sanitize_string(const char *s, pkgconf_buffer_t *buf)
{
unsigned int i = 0;
/*
* Allowed chars are:
* - a-z
* - 0-9
* - chars '-', '+', '.', '(' and ')'
*/
for (i = 0; i < strlen(s); i ++)
{
if (isalnum((unsigned char) s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '(' || s[i] == ')' || s[i] == '.' || s[i] == ':')
{
if (!pkgconf_buffer_push_byte(buf, s[i]))
return false;
}
}
return true;
}
/*
* !doc
*
* .. c:function:: bool pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
*
* Copies a `license list` to another `license list`
*
* :param pkgconf_client_t* client: The pkgconf client being accessed.
* :param pkgconf_list_t* list: The list the fragments are being added to.
* :param pkgconf_list_t* base: The list the fragments are being copied from.
* :return: true on success, false on allocation failure
*/
bool
pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
{
pkgconf_node_t *node;
(void) client;
PKGCONF_FOREACH_LIST_ENTRY(base->head, node)
{
pkgconf_license_t *license = node->data;
pkgconf_license_t *cpy_license = calloc(1, sizeof(pkgconf_license_t));
if (cpy_license == NULL)
return false;
cpy_license->type = license->type;
if (license->data != NULL)
{
cpy_license->data = strdup(license->data);
if (cpy_license->data == NULL)
{
free(cpy_license);
return false;
}
}
pkgconf_node_insert_tail(&cpy_license->iter, cpy_license, list);
}
return true;
}
/*
* !doc
*
* .. c:function:: void pkgconf_license_free(pkgconf_list_t *list)
*
* Delete an entire `license list`.
*
* :param pkgconf_list_t* list: The `license list` to delete.
* :return: nothing
*/
void
pkgconf_license_free(pkgconf_list_t *list)
{
if (!list)
return;
pkgconf_node_t *node, *next;
PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
{
pkgconf_license_t *license = node->data;
free(license->data);
free(license);
}
pkgconf_list_zero(list);
}
/*
* !doc
*
* .. c:function:: bool pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
*
* Adds a `license` of text to a `license list` directly without interpreting it.
*
* :param pkgconf_client_t* client: The pkgconf client being accessed.
* :param pkgconf_list_t* list: The fragment list.
* :param char type: The type of the license.
* :param char* data: The data of the license
* :return: true on success, false on allocation failure
*/
bool
pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
{
pkgconf_license_t *license;
if (data == NULL)
return false;
license = calloc(1, sizeof(pkgconf_license_t));
if (!license)
{
pkgconf_error(client, "pkgconf_license_insert: out of memory");
return false;
}
license->type = type;
license->data = strdup(data);
if (license->data == NULL)
{
pkgconf_error(client, "pkgconf_license_insert: out of memory");
free(license);
return false;
}
pkgconf_node_insert_tail(&license->iter, license, list);
return true;
}
/*
* !doc
*
* .. c:function:: bool pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
*
* Evaluates SPDX expression strings like:
* - BSD-3-Clause
* - LGPL-2.1-only OR MIT
* - LGPL-2.1-only OR MIT OR BSD-3-Clause
* - ISC AND (BSD-3-Clause AND BSD-2-Clause)
*
* Function parses and sanitizes license strings. Also adding multiple
* 'License:'-keys is supported like:
* License: BSD-3-Clause
* License: BSD-2-Clause
*
* Which will evaluate to: BSD-3-Clause AND BSD-2-Clause
*
* :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
* :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes.
* :param char* expression: The dependency data to parse.
* :param uint flags: Any flags to attach to the dependency nodes.
* :return: true on success, false on allocation failure
*/
bool
pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
{
pkgconf_buffer_t out_buffer = PKGCONF_BUFFER_INITIALIZER;
size_t buf_size = 0;
char *cur_word = NULL;
int i, ret, argc;
char **argv;
size_t string_len = 0;
(void)flags;
if (expression == NULL)
return true;
buf_size = strlen(expression) + 1;
ret = pkgconf_argv_split(expression, &argc, &argv);
if (ret)
return true;
/* This is not the first License:
* so add AND
*/
if (license_list->head)
{
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND"))
goto fail;
}
i = 0;
while (i < argc)
{
string_len = strnlen(argv[i], buf_size);
cur_word = argv[i];
if (string_len >= 1)
{
if (!license_sanitize_string(cur_word, &out_buffer))
goto fail;
if (!pkgconf_buffer_len(&out_buffer))
{
i ++;
pkgconf_buffer_finalize(&out_buffer);
continue;
}
cur_word = (char *)pkgconf_buffer_str(&out_buffer);
size_t cur_len = strnlen(cur_word, buf_size);
if (!cur_len)
{
i ++;
pkgconf_buffer_finalize(&out_buffer);
continue;
}
if (cur_word[0] == '(')
{
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_OPEN, "("))
goto fail;
/* If there is more after '(' like '(BSD-2-Clause'
* Then append rest to fragments as license.
* This is expression like GPL-2.0-only OR (BSD-2-Clause AND ISC)
*/
if (cur_len >= 2)
{
cur_word ++;
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word))
goto fail;
}
}
else if (cur_word[cur_len - 1] == ')')
{
if (cur_len >= 2)
{
cur_word[cur_len - 1] = 0x00;
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word))
goto fail;
}
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_CLOSE, ")"))
goto fail;
}
else if (!strcasecmp(cur_word, "and"))
{
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND"))
goto fail;
}
else if (!strcasecmp(cur_word, "or"))
{
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_OR, "OR"))
goto fail;
}
else if (!strcasecmp(cur_word, "with"))
{
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_WITH, "WITH"))
goto fail;
}
else
{
if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word))
goto fail;
}
pkgconf_buffer_finalize(&out_buffer);
}
i++;
}
pkgconf_argv_free(argv);
return true;
fail:
pkgconf_buffer_finalize(&out_buffer);
pkgconf_argv_free(argv);
return false;
}
/*
* !doc
*
* .. c:function:: bool pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags)
*
* Evaluates SPDX expression strings like:
* - BSD-3-Clause
* - LGPL-2.1-only OR MIT
* - LGPL-2.1-only OR MIT OR BSD-3-Clause
* - ISC AND (BSD-3-Clause AND BSD-2-Clause)
*
* Function parses and sanitizes license strings. Also adding multiple
* 'License:'-keys is supported like:
* License: BSD-3-Clause
* License: BSD-2-Clause
*
* Which will evaluate to: BSD-3-Clause AND BSD-2-Clause
*
* :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
* :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes.
* :param char* expression: The dependency data to parse.
* :param uint flags: Any flags to attach to the dependency nodes.
* :return: true on success, false on allocation failure
*/
bool
pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags)
{
char *license_expression = pkgconf_bytecode_eval_str(client, &pkg->vars, license_str, NULL);
if (license_expression == NULL)
return false;
bool ret = pkgconf_license_evaluate_str(client, license_list, license_expression, flags);
free(license_expression);
return ret;
}
/*
* !doc
*
* .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends)
*
* Renders license fragments back to SPDX expression string. Tries
* to keep output as close as possible to original input
*
* :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
* :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes.
* :param char* depends: The dependency data to parse.
* :param uint flags: Any flags to attach to the dependency nodes.
* :return: nothing
*/
bool
pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkgconf_buffer_t *buf)
{
const pkgconf_buffer_t *frag_string = NULL;
pkgconf_node_t *node;
bool is_delim = true;
(void)client;
PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
{
pkgconf_license_t *license = node->data;
is_delim = true;
frag_string = PKGCONF_BUFFER_FROM_STR(license->data);
if (!pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(frag_string)))
return false;
if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next->data)->type == PKGCONF_LICENSE_BRACKET_CLOSE))
{
is_delim = false;
}
if (node->next != NULL && is_delim)
{
if (!pkgconf_buffer_push_byte(buf, ' '))
return false;
}
}
return true;
}