xref: /freebsd/contrib/pkgconf/libpkgconf/license.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
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