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 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 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 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 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 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 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 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