Coverage Report

Created: 2025-07-12 06:56

/src/libssh/src/token.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * token.c - Token list handling functions
3
 *
4
 * This file is part of the SSH Library
5
 *
6
 * Copyright (c) 2003-2008 by Aris Adamantiadis
7
 * Copyright (c) 2019 by Anderson Toshiyuki Sasaki - Red Hat, Inc.
8
 *
9
 * The SSH Library is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU Lesser General Public License as published by
11
 * the Free Software Foundation; either version 2.1 of the License, or (at your
12
 * option) any later version.
13
 *
14
 * The SSH Library is distributed in the hope that it will be useful, but
15
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
17
 * License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with the SSH Library; see the file COPYING.  If not, write to
21
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
22
 * MA 02111-1307, USA.
23
 */
24
25
#include "config.h"
26
27
#include <stdio.h>
28
#include <string.h>
29
#include <stdbool.h>
30
31
#include "libssh/priv.h"
32
#include "libssh/token.h"
33
34
/**
35
 * @internal
36
 *
37
 * @brief Free the given tokens list structure. The used buffer is overwritten
38
 * with zeroes before freed.
39
 *
40
 * @param[in] tokens    The pointer to a structure to be freed;
41
 */
42
void ssh_tokens_free(struct ssh_tokens_st *tokens)
43
0
{
44
0
    int i;
45
0
    if (tokens == NULL) {
46
0
        return;
47
0
    }
48
49
0
    if (tokens->tokens != NULL) {
50
0
        for (i = 0; tokens->tokens[i] != NULL; i++) {
51
0
            explicit_bzero(tokens->tokens[i], strlen(tokens->tokens[i]));
52
0
        }
53
0
    }
54
55
0
    SAFE_FREE(tokens->buffer);
56
0
    SAFE_FREE(tokens->tokens);
57
0
    SAFE_FREE(tokens);
58
0
}
59
60
/**
61
 * @internal
62
 *
63
 * @brief Split a given string on the given separator character. The returned
64
 * structure holds an array of pointers (tokens) pointing to the obtained
65
 * parts and a buffer where all the content of the list is stored. The last
66
 * element of the array will always be set as NULL.
67
 *
68
 * @param[in] chain         The string to split
69
 * @param[in] separator     The character used to separate the tokens.
70
 *
71
 * @return  A newly allocated tokens list structure; NULL in case of error.
72
 */
73
struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator)
74
0
{
75
76
0
    struct ssh_tokens_st *tokens = NULL;
77
0
    size_t num_tokens = 1, i = 1;
78
79
0
    char *found, *c;
80
81
0
    if (chain == NULL) {
82
0
        return NULL;
83
0
    }
84
85
0
    tokens = calloc(1, sizeof(struct ssh_tokens_st));
86
0
    if (tokens == NULL) {
87
0
        return NULL;
88
0
    }
89
90
0
    tokens->buffer = strdup(chain);
91
0
    if (tokens->buffer == NULL) {
92
0
        goto error;
93
0
    }
94
95
0
    c = tokens->buffer;
96
0
    do {
97
0
        found = strchr(c, separator);
98
0
        if (found != NULL) {
99
0
            c = found + 1;
100
0
            num_tokens++;
101
0
        }
102
0
    } while(found != NULL);
103
104
    /* Allocate tokens list */
105
0
    tokens->tokens = calloc(num_tokens + 1, sizeof(char *));
106
0
    if (tokens->tokens == NULL) {
107
0
        goto error;
108
0
    }
109
110
    /* First token starts in the beginning of the chain */
111
0
    tokens->tokens[0] = tokens->buffer;
112
0
    c = tokens->buffer;
113
114
0
    for (i = 1; i < num_tokens; i++) {
115
        /* Find next separator */
116
0
        found = strchr(c, separator);
117
0
        if (found == NULL) {
118
0
            break;
119
0
        }
120
121
        /* Replace it with a string terminator */
122
0
        *found = '\0';
123
124
        /* The next token starts in the next byte */
125
0
        c = found + 1;
126
127
        /* If we did not reach the end of the chain yet, set the next token */
128
0
        if (*c != '\0') {
129
0
            tokens->tokens[i] = c;
130
0
        } else {
131
0
            break;
132
0
        }
133
0
    }
134
135
0
    return tokens;
136
137
0
error:
138
0
    ssh_tokens_free(tokens);
139
0
    return NULL;
140
0
}
141
142
/**
143
 * @internal
144
 *
145
 * @brief Given two strings, the first containing a list of available tokens and
146
 * the second containing a list of tokens to be searched ordered by preference,
147
 * returns a copy of the first preferred token present in the available list.
148
 *
149
 * @param[in] available_list    The list of available tokens
150
 * @param[in] preferred_list    The list of tokens to search, ordered by
151
 * preference
152
 *
153
 * @return  A newly allocated copy of the token if found; NULL otherwise
154
 */
155
char *ssh_find_matching(const char *available_list,
156
                        const char *preferred_list)
157
0
{
158
0
    struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL;
159
160
0
    int i, j;
161
0
    char *ret = NULL;
162
163
0
    if ((available_list == NULL) || (preferred_list == NULL)) {
164
0
        return NULL;
165
0
    }
166
167
0
    a_tok = ssh_tokenize(available_list, ',');
168
0
    if (a_tok == NULL) {
169
0
        return NULL;
170
0
    }
171
172
0
    p_tok = ssh_tokenize(preferred_list, ',');
173
0
    if (p_tok == NULL) {
174
0
        goto out;
175
0
    }
176
177
0
    for (i = 0; p_tok->tokens[i]; i++) {
178
0
        for (j = 0; a_tok->tokens[j]; j++) {
179
0
            if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0) {
180
0
                ret = strdup(a_tok->tokens[j]);
181
0
                goto out;
182
0
            }
183
0
        }
184
0
    }
185
186
0
out:
187
0
    ssh_tokens_free(a_tok);
188
0
    ssh_tokens_free(p_tok);
189
0
    return ret;
190
0
}
191
192
/**
193
 * @internal
194
 *
195
 * @brief Given two strings, the first containing a list of available tokens and
196
 * the second containing a list of tokens to be searched ordered by preference,
197
 * returns a list of all matching tokens ordered by preference.
198
 *
199
 * @param[in] available_list    The list of available tokens
200
 * @param[in] preferred_list    The list of tokens to search, ordered by
201
 * preference
202
 *
203
 * @return  A newly allocated string containing the list of all matching tokens;
204
 * NULL otherwise
205
 */
206
char *ssh_find_all_matching(const char *available_list,
207
                            const char *preferred_list)
208
0
{
209
0
    struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL;
210
0
    int i, j;
211
0
    char *ret = NULL;
212
0
    size_t max, len, pos = 0;
213
0
    int match;
214
215
0
    if ((available_list == NULL) || (preferred_list == NULL)) {
216
0
        return NULL;
217
0
    }
218
219
0
    max = MAX(strlen(available_list), strlen(preferred_list));
220
221
0
    ret = calloc(1, max + 1);
222
0
    if (ret == NULL) {
223
0
        return NULL;
224
0
    }
225
226
0
    a_tok = ssh_tokenize(available_list, ',');
227
0
    if (a_tok == NULL) {
228
0
        SAFE_FREE(ret);
229
0
        goto out;
230
0
    }
231
232
0
    p_tok = ssh_tokenize(preferred_list, ',');
233
0
    if (p_tok == NULL) {
234
0
        SAFE_FREE(ret);
235
0
        goto out;
236
0
    }
237
238
0
    for (i = 0; p_tok->tokens[i] ; i++) {
239
0
        for (j = 0; a_tok->tokens[j]; j++) {
240
0
            match = !strcmp(a_tok->tokens[j], p_tok->tokens[i]);
241
0
            if (match) {
242
0
                if (pos != 0) {
243
0
                    ret[pos] = ',';
244
0
                    pos++;
245
0
                }
246
247
0
                len = strlen(a_tok->tokens[j]);
248
0
                memcpy(&ret[pos], a_tok->tokens[j], len);
249
0
                pos += len;
250
0
                ret[pos] = '\0';
251
0
            }
252
0
        }
253
0
    }
254
255
0
    if (ret[0] == '\0') {
256
0
        SAFE_FREE(ret);
257
0
    }
258
259
0
out:
260
0
    ssh_tokens_free(a_tok);
261
0
    ssh_tokens_free(p_tok);
262
0
    return ret;
263
0
}
264
265
/**
266
 * @internal
267
 *
268
 * @brief Given a string containing a list of elements, remove all duplicates
269
 * and return in a newly allocated string.
270
 *
271
 * @param[in] list  The list to be freed of duplicates
272
 *
273
 * @return  A newly allocated copy of the string free of duplicates; NULL in
274
 * case of error.
275
 */
276
char *ssh_remove_duplicates(const char *list)
277
0
{
278
0
    struct ssh_tokens_st *tok = NULL;
279
280
0
    size_t i, j, num_tokens, max_len;
281
0
    char *ret = NULL;
282
0
    bool *should_copy = NULL, need_comma = false;
283
284
0
    if (list == NULL) {
285
0
        return NULL;
286
0
    }
287
288
    /* The maximum number of tokens is the size of the list */
289
0
    max_len = strlen(list);
290
0
    if (max_len == 0) {
291
0
        return NULL;
292
0
    }
293
294
    /* Add space for ending '\0' */
295
0
    max_len++;
296
297
0
    tok = ssh_tokenize(list, ',');
298
0
    if ((tok == NULL) || (tok->tokens == NULL) || (tok->tokens[0] == NULL)) {
299
0
        goto out;
300
0
    }
301
302
0
    should_copy = calloc(1, max_len);
303
0
    if (should_copy == NULL) {
304
0
        goto out;
305
0
    }
306
307
0
    if (strlen(tok->tokens[0]) > 0) {
308
0
        should_copy[0] = true;
309
0
    }
310
311
0
    for (i = 1; tok->tokens[i]; i++) {
312
0
        for (j = 0; j < i; j++) {
313
0
            if (strcmp(tok->tokens[i], tok->tokens[j]) == 0) {
314
                /* Found a duplicate; do not copy */
315
0
                should_copy[i] = false;
316
0
                break;
317
0
            }
318
0
        }
319
320
        /* No matching token before */
321
0
        if (j == i) {
322
            /* Only copy if it is not an empty string */
323
0
            if (strlen(tok->tokens[i]) > 0) {
324
0
                should_copy[i] = true;
325
0
            } else {
326
0
                should_copy[i] = false;
327
0
            }
328
0
        }
329
0
    }
330
331
0
    num_tokens = i;
332
333
0
    ret = calloc(1, max_len);
334
0
    if (ret == NULL) {
335
0
        goto out;
336
0
    }
337
338
0
    for (i = 0; i < num_tokens; i++) {
339
0
        if (should_copy[i]) {
340
0
            if (need_comma) {
341
0
                strncat(ret, ",", (max_len - strlen(ret) - 1));
342
0
            }
343
0
            strncat(ret, tok->tokens[i], (max_len - strlen(ret) - 1));
344
0
            need_comma = true;
345
0
        }
346
0
    }
347
348
    /* If no comma is needed, nothing was copied */
349
0
    if (!need_comma) {
350
0
        SAFE_FREE(ret);
351
0
    }
352
353
0
out:
354
0
    SAFE_FREE(should_copy);
355
0
    ssh_tokens_free(tok);
356
0
    return ret;
357
0
}
358
359
/**
360
 * @internal
361
 *
362
 * @brief Given two strings containing lists of tokens, return a newly
363
 * allocated string containing all the elements of the first list appended with
364
 * all the elements of the second list, without duplicates. The order of the
365
 * elements will be preserved.
366
 *
367
 * @param[in] list             The first list
368
 * @param[in] appended_list    The list to be appended
369
 *
370
 * @return  A newly allocated copy list containing all the elements of the
371
 * kept_list appended with the elements of the appended_list without duplicates;
372
 * NULL in case of error.
373
 */
374
char *ssh_append_without_duplicates(const char *list,
375
                                    const char *appended_list)
376
0
{
377
0
    size_t concat_len = 0;
378
0
    char *ret = NULL, *concat = NULL;
379
0
    int rc = 0;
380
381
0
    if (list != NULL) {
382
0
        concat_len = strlen(list);
383
0
    }
384
385
0
    if (appended_list != NULL) {
386
0
        concat_len += strlen(appended_list);
387
0
    }
388
389
0
    if (concat_len == 0) {
390
0
        return NULL;
391
0
    }
392
393
    /* Add room for ending '\0' and for middle ',' */
394
0
    concat_len += 2;
395
0
    concat = calloc(1, concat_len);
396
0
    if (concat == NULL) {
397
0
        return NULL;
398
0
    }
399
400
0
    rc = snprintf(concat, concat_len, "%s%s%s",
401
0
                  list == NULL ? "" : list,
402
0
                  list == NULL ? "" : ",",
403
0
                  appended_list == NULL ? "" : appended_list);
404
0
    if (rc < 0) {
405
0
        SAFE_FREE(concat);
406
0
        return NULL;
407
0
    }
408
409
0
    ret = ssh_remove_duplicates(concat);
410
411
0
    SAFE_FREE(concat);
412
413
0
    return ret;
414
0
}
415
416
/**
417
 * @internal
418
 *
419
 * @brief Given two strings containing lists of tokens, return a newly
420
 * allocated string containing the elements of the first list without the
421
 * elements of the second list. The order of the elements will be preserved.
422
 *
423
 * @param[in] list             The first list
424
 * @param[in] remove_list      The list to be removed
425
 *
426
 * @return  A newly allocated copy list containing elements of the
427
 * list without the elements of remove_list; NULL in case of error.
428
 */
429
char *ssh_remove_all_matching(const char *list,
430
                              const char *remove_list)
431
0
{
432
0
    struct ssh_tokens_st *l_tok = NULL, *r_tok = NULL;
433
0
    int i, j, cmp;
434
0
    char *ret = NULL;
435
0
    size_t len, pos = 0;
436
0
    bool exclude;
437
438
0
    if (list == NULL) {
439
0
        return NULL;
440
0
    }
441
0
    if (remove_list == NULL) {
442
0
        return strdup (list);
443
0
    }
444
445
0
    l_tok = ssh_tokenize(list, ',');
446
0
    if (l_tok == NULL) {
447
0
        goto out;
448
0
    }
449
450
0
    r_tok = ssh_tokenize(remove_list, ',');
451
0
    if (r_tok == NULL) {
452
0
        goto out;
453
0
    }
454
455
0
    ret = calloc(1, strlen(list) + 1);
456
0
    if (ret == NULL) {
457
0
        goto out;
458
0
    }
459
460
0
    for (i = 0; l_tok->tokens[i]; i++) {
461
0
        exclude = false;
462
0
        for (j = 0; r_tok->tokens[j]; j++) {
463
0
            cmp = strcmp(l_tok->tokens[i], r_tok->tokens[j]);
464
0
            if (cmp == 0) {
465
0
                exclude = true;
466
0
                break;
467
0
            }
468
0
        }
469
0
        if (exclude == false) {
470
0
            if (pos != 0) {
471
0
                ret[pos] = ',';
472
0
                pos++;
473
0
            }
474
475
0
            len = strlen(l_tok->tokens[i]);
476
0
            memcpy(&ret[pos], l_tok->tokens[i], len);
477
0
            pos += len;
478
0
        }
479
0
    }
480
481
0
    if (ret[0] == '\0') {
482
0
        SAFE_FREE(ret);
483
0
    }
484
485
0
out:
486
0
    ssh_tokens_free(l_tok);
487
0
    ssh_tokens_free(r_tok);
488
0
    return ret;
489
0
}
490
491
/**
492
 * @internal
493
 *
494
 * @brief Given two strings containing lists of tokens, return a newly
495
 * allocated string containing all the elements of the first list prefixed at
496
 * the beginning of the second list, without duplicates.
497
 *
498
 * @param[in] list             The first list
499
 * @param[in] prefixed_list    The list to use as a prefix
500
 *
501
 * @return  A newly allocated list containing all the elements
502
 * of the list prefixed with the elements of the prefixed_list without
503
 * duplicates; NULL in case of error.
504
 */
505
char *ssh_prefix_without_duplicates(const char *list,
506
                                    const char *prefixed_list)
507
0
{
508
0
    size_t concat_len = 0;
509
0
    char *ret = NULL, *concat = NULL;
510
0
    int rc = 0;
511
512
0
    if (list != NULL) {
513
0
        concat_len = strlen(list);
514
0
    }
515
516
0
    if (prefixed_list != NULL) {
517
0
        concat_len += strlen(prefixed_list);
518
0
    }
519
520
0
    if (concat_len == 0) {
521
0
        return NULL;
522
0
    }
523
524
    /* Add room for ending '\0' and for middle ',' */
525
0
    concat_len += 2;
526
0
    concat = calloc(concat_len, 1);
527
0
    if (concat == NULL) {
528
0
        return NULL;
529
0
    }
530
531
0
    rc = snprintf(concat, concat_len, "%s%s%s",
532
0
                  prefixed_list == NULL ? "" : prefixed_list,
533
0
                  prefixed_list == NULL ? "" : ",",
534
0
                  list == NULL ? "" : list);
535
0
    if (rc < 0) {
536
0
        SAFE_FREE(concat);
537
0
        return NULL;
538
0
    }
539
540
0
    ret = ssh_remove_duplicates(concat);
541
542
0
    SAFE_FREE(concat);
543
544
0
    return ret;
545
0
}