1 /*
2 * license.c
3 * Evaluate SPDX license expressions
4 *
5 * SPDX-License-Identifier: pkgconf
6 *
7 * Copyright (c) 2026 pkgconf authors (see AUTHORS).
8 *
9 * Permission to use, copy, modify, and/or distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * This software is provided 'as is' and without any warranty, express or
14 * implied. In no event shall the authors be liable for any damages arising
15 * from the use of this software.
16 */
17
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20
license_sanitize_string(const char * s,pkgconf_buffer_t * buf)21 static void license_sanitize_string(const char *s, pkgconf_buffer_t *buf)
22 {
23 unsigned int i = 0;
24 /*
25 * Allowed chars are:
26 * - a-z
27 * - 0-9
28 * - chars '-', '+', '.', '(' and ')'
29 */
30 for (i = 0; i < strlen(s); i ++)
31 {
32 if (isalnum((unsigned char) s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '(' || s[i] == ')' || s[i] == '.' || s[i] == ':')
33 {
34 pkgconf_buffer_push_byte(buf, s[i]);
35 }
36 }
37 }
38
39 /*
40 * !doc
41 *
42 * .. c:function:: void pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
43 *
44 * Copies a `license list` to another `license list`
45 *
46 * :param pkgconf_client_t* client: The pkgconf client being accessed.
47 * :param pkgconf_list_t* list: The list the fragments are being added to.
48 * :param pkgconf_list_t* base: The list the fragments are being copied from.
49 * :return: nothing
50 */
51 void
pkgconf_license_copy_list(const pkgconf_client_t * client,pkgconf_list_t * list,const pkgconf_list_t * base)52 pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
53 {
54 pkgconf_node_t *node;
55 (void) client;
56
57 PKGCONF_FOREACH_LIST_ENTRY(base->head, node)
58 {
59 pkgconf_license_t *license = node->data;
60 pkgconf_license_t *cpy_license = calloc(1, sizeof(pkgconf_license_t));
61
62 if (cpy_license == NULL)
63 continue;
64
65 cpy_license->type = license->type;
66
67 if (license->data != NULL)
68 {
69 cpy_license->data = strdup(license->data);
70 }
71
72 pkgconf_node_insert_tail(&cpy_license->iter, cpy_license, list);
73 }
74 }
75
76 /*
77 * !doc
78 *
79 * .. c:function:: void pkgconf_license_free(pkgconf_list_t *list)
80 *
81 * Delete an entire `license list`.
82 *
83 * :param pkgconf_list_t* list: The `license list` to delete.
84 * :return: nothing
85 */
86 void
pkgconf_license_free(pkgconf_list_t * list)87 pkgconf_license_free(pkgconf_list_t *list)
88 {
89 if (!list)
90 return;
91
92 pkgconf_node_t *node, *next;
93
94 PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
95 {
96 pkgconf_license_t *license = node->data;
97 free(license->data);
98 free(license);
99 }
100 }
101
102 /*
103 * !doc
104 *
105 * .. c:function:: void pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
106 *
107 * Adds a `license` of text to a `license list` directly without interpreting it.
108 *
109 * :param pkgconf_client_t* client: The pkgconf client being accessed.
110 * :param pkgconf_list_t* list: The fragment list.
111 * :param char type: The type of the license.
112 * :param char* data: The data of the license
113 * :return: nothing
114 */
115 void
pkgconf_license_insert(pkgconf_client_t * client,pkgconf_list_t * list,unsigned char type,const char * data)116 pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
117 {
118 (void) client;
119
120 pkgconf_license_t *license;
121
122 license = calloc(1, sizeof(pkgconf_license_t));
123 if (!license)
124 {
125 pkgconf_error(client, "pkgconf_license_insert: out of memory");
126 return;
127 }
128 license->type = type;
129 license->data = strdup(data);
130
131 pkgconf_node_insert_tail(&license->iter, license, list);
132 }
133
134 /*
135 * !doc
136 *
137 * .. c:function:: pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
138 *
139 * Evaluates SPDX expression strings like:
140 * - BSD-3-Clause
141 * - LGPL-2.1-only OR MIT
142 * - LGPL-2.1-only OR MIT OR BSD-3-Clause
143 * - ISC AND (BSD-3-Clause AND BSD-2-Clause)
144 *
145 * Function parses and sanitizes license strings. Also adding multiple
146 * 'License:'-keys is supported like:
147 * License: BSD-3-Clause
148 * License: BSD-2-Clause
149 *
150 * Which will evaluate to: BSD-3-Clause AND BSD-2-Clause
151 *
152 * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
153 * :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes.
154 * :param char* expression: The dependency data to parse.
155 * :param uint flags: Any flags to attach to the dependency nodes.
156 * :return: nothing
157 */
158 void
pkgconf_license_evaluate_str(pkgconf_client_t * client,pkgconf_list_t * license_list,const char * expression,unsigned int flags)159 pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
160 {
161 pkgconf_buffer_t out_buffer = PKGCONF_BUFFER_INITIALIZER;
162 size_t buf_size = 0;
163 char *cur_word = NULL;
164 int i, ret, argc;
165 char **argv;
166 size_t string_len = 0;
167
168 (void)flags;
169
170 if (expression == NULL)
171 return;
172
173 buf_size = strlen(expression) + 1;
174 ret = pkgconf_argv_split(expression, &argc, &argv);
175 if (!ret)
176 {
177 /* This is not the first License:
178 * so add AND
179 */
180 if (license_list->head)
181 {
182 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND");
183 }
184
185 i = 0;
186 while (i < argc)
187 {
188 string_len = strnlen(argv[i], buf_size);
189 cur_word = argv[i];
190 if (string_len >= 1)
191 {
192 license_sanitize_string(cur_word, &out_buffer);
193 if (!pkgconf_buffer_len(&out_buffer))
194 {
195 i ++;
196 pkgconf_buffer_finalize(&out_buffer);
197 continue;
198 }
199
200 cur_word = (char *)pkgconf_buffer_str(&out_buffer);
201 size_t cur_len = strnlen(cur_word, buf_size);
202 if (!cur_len)
203 {
204 i ++;
205 pkgconf_buffer_finalize(&out_buffer);
206 continue;
207 }
208
209 if (cur_word[0] == '(')
210 {
211 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_OPEN, "(");
212 /* If there is more after '(' like '(BSD-2-Clause'
213 * Then append rest to fragments as license.
214 * This is expression like GPL-2.0-only OR (BSD-2-Clause AND ISC)
215 */
216 if (cur_len >= 2)
217 {
218 cur_word ++;
219 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word);
220 }
221 }
222 else if (cur_word[cur_len - 1] == ')')
223 {
224 if (cur_len >= 2)
225 {
226 cur_word[cur_len - 1] = 0x00;
227 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word);
228 }
229 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_CLOSE, ")");
230 }
231 else if (!strcasecmp(cur_word, "and"))
232 {
233 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND");
234 }
235 else if (!strcasecmp(cur_word, "or"))
236 {
237 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_OR, "OR");
238 }
239 else if (!strcasecmp(cur_word, "with"))
240 {
241 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_WITH, "WITH");
242 }
243 else
244 {
245 pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word);
246 }
247 pkgconf_buffer_finalize(&out_buffer);
248 }
249 i++;
250 }
251
252 pkgconf_argv_free(argv);
253 }
254 }
255
256 /*
257 * !doc
258 *
259 * .. c:function:: pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
260 *
261 * Evaluates SPDX expression strings like:
262 * - BSD-3-Clause
263 * - LGPL-2.1-only OR MIT
264 * - LGPL-2.1-only OR MIT OR BSD-3-Clause
265 * - ISC AND (BSD-3-Clause AND BSD-2-Clause)
266 *
267 * Function parses and sanitizes license strings. Also adding multiple
268 * 'License:'-keys is supported like:
269 * License: BSD-3-Clause
270 * License: BSD-2-Clause
271 *
272 * Which will evaluate to: BSD-3-Clause AND BSD-2-Clause
273 *
274 * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
275 * :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes.
276 * :param char* expression: The dependency data to parse.
277 * :param uint flags: Any flags to attach to the dependency nodes.
278 * :return: nothing
279 */
280 void
pkgconf_license_evaluate(pkgconf_client_t * client,pkgconf_pkg_t * pkg,pkgconf_list_t * license_list,const char * license_str,unsigned int flags)281 pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags)
282 {
283 char *license_expression = pkgconf_bytecode_eval_str(client, &pkg->vars, license_str, NULL);
284
285 pkgconf_license_evaluate_str(client, license_list, license_expression, flags);
286 free(license_expression);
287 }
288
289 /*
290 * !doc
291 *
292 * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends)
293 *
294 * Renders license fragments back to SPDX expression string. Tries
295 * to keep output as close as possible to original input
296 *
297 * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
298 * :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes.
299 * :param char* depends: The dependency data to parse.
300 * :param uint flags: Any flags to attach to the dependency nodes.
301 * :return: nothing
302 */
303 void
pkgconf_license_render(pkgconf_client_t * client,const pkgconf_list_t * list,pkgconf_buffer_t * buf)304 pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkgconf_buffer_t *buf)
305 {
306 const pkgconf_buffer_t *frag_string = NULL;
307 pkgconf_node_t *node;
308 bool is_delim = true;
309
310 (void)client;
311
312 PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
313 {
314 pkgconf_license_t *license = node->data;
315 is_delim = true;
316 frag_string = PKGCONF_BUFFER_FROM_STR(license->data);
317 pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(frag_string));
318
319 if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next->data)->type == PKGCONF_LICENSE_BRACKET_CLOSE))
320 {
321 is_delim = false;
322 }
323
324 if (node->next != NULL && is_delim)
325 {
326 pkgconf_buffer_push_byte(buf, ' ');
327 }
328 }
329 }
330