mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
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
374 lines
10 KiB
C
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;
|
|
}
|