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