• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 26127302613

19 May 2026 09:51PM UTC coverage: 93.024% (+0.4%) from 92.635%
26127302613

push

github

51329 of 55178 relevant lines covered (93.02%)

215.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.4
/src/str.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2008-2026 The ProFTPD Project team
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
19
 * and other respective copyright holders give permission to link this program
20
 * with OpenSSL, and distribute the resulting executable, without including
21
 * the source code for OpenSSL in the source distribution.
22
 */
23

24
/* String manipulation functions. */
25

26
#include "conf.h"
27

28
/* Maximum number of matches that we will do in a given string. */
29
#define PR_STR_MAX_MATCHES                        128
30

31
static const char *str_vreplace(pool *p, unsigned int max_replaces,
32
    const char *s, va_list args) {
95✔
33
  const char *src;
34
  char *m, *r, *cp;
95✔
35
  char *matches[PR_STR_MAX_MATCHES+1], *replaces[PR_STR_MAX_MATCHES+1];
95✔
36
  char buf[PR_TUNABLE_PATH_MAX] = {'\0'}, *pbuf = NULL;
95✔
37
  size_t nmatches = 0, rlen = 0;
95✔
38
  int blen = 0;
95✔
39

95✔
40
  src = s;
41
  cp = buf;
95✔
42
  *cp = '\0';
95✔
43

95✔
44
  memset(matches, 0, sizeof(matches));
45
  memset(replaces, 0, sizeof(replaces));
95✔
46

95✔
47
  blen = strlen(src) + 1;
48

95✔
49
  while ((m = va_arg(args, char *)) != NULL &&
50
         nmatches < PR_STR_MAX_MATCHES) {
406✔
51
    char *tmp = NULL;
52
    unsigned int count = 0;
254✔
53

254✔
54
    r = va_arg(args, char *);
55
    if (r == NULL) {
254✔
56
      break;
254✔
57
    }
58

59
    /* Increase the length of the needed buffer by the difference between
60
     * the given match and replacement strings, multiplied by the number
61
     * of times the match string occurs in the source string.
62
     */
63
    tmp = strstr(s, m);
64
    while (tmp) {
218✔
65
      pr_signals_handle();
436✔
66
      count++;
220✔
67
      if (count > max_replaces) {
220✔
68
        errno = E2BIG;
220✔
69
        return NULL;
2✔
70
      }
2✔
71

72
      /* Be sure to increment the pointer returned by strstr(3), to
73
       * advance past the beginning of the substring for which we are
74
       * looking.  Otherwise, we just loop endlessly, seeing the same
75
       * value for tmp over and over.
76
       */
77
      tmp += strlen(m);
78
      tmp = strstr(tmp, m);
218✔
79
    }
218✔
80

81
    /* We are only concerned about match/replacement strings that actually
82
     * occur in the given string.
83
     */
84
    if (count) {
85
      blen += count * (strlen(r) - strlen(m));
216✔
86
      if (blen < 0) {
182✔
87
        /* Integer overflow. In order to overflow this, somebody must be
182✔
88
         * doing something very strange. The possibility still exists that
89
         * we might not catch this overflow in extreme corner cases, but
90
         * massive amounts of data (gigabytes) would need to be in s to
91
         * trigger this, easily larger than any buffer we might use.
92
         */
93
        return s;
94
      }
95
      matches[nmatches] = m;
96
      replaces[nmatches++] = r;
182✔
97
    }
182✔
98
  }
99

100
  /* If there are no matches, then there is nothing to replace. */
101
  if (nmatches == 0) {
102
    return s;
93✔
103
  }
104

105
  /* Try to handle large buffer situations (i.e. escaping of PR_TUNABLE_PATH_MAX
106
   * (>2048) correctly, but do not allow very big buffer sizes, that may
107
   * be dangerous (BUFSIZ may be defined in stdio.h) in some library
108
   * functions.
109
   */
110
#ifndef BUFSIZ
111
# define BUFSIZ 8192
112
#endif
113

114
  if (blen >= BUFSIZ) {
115
    errno = ENOSPC;
55✔
116
    return NULL;
1✔
117
  }
1✔
118

119
  cp = pbuf = (char *) pcalloc(p, ++blen);
120

54✔
121
  while (*src) {
122
    char **mptr, **rptr;
1,259✔
123

124
    for (mptr = matches, rptr = replaces; *mptr; mptr++, rptr++) {
125
      size_t mlen;
35,502✔
126

34,552✔
127
      pr_signals_handle();
128

34,552✔
129
      mlen = strlen(*mptr);
130
      rlen = strlen(*rptr);
34,552✔
131

34,552✔
132
      if (strncmp(src, *mptr, mlen) == 0) {
133
        sstrncpy(cp, *rptr, blen - strlen(pbuf));
34,552✔
134

201✔
135
        if (((cp + rlen) - pbuf + 1) > blen) {
136
          pr_log_pri(PR_LOG_ERR,
201✔
137
            "WARNING: attempt to overflow internal ProFTPD buffers");
×
138
          cp = pbuf;
139

×
140
          cp += (blen - 1);
141
          goto done;
×
142

×
143
        } else {
144
          cp += rlen;
145
        }
201✔
146
        
147
        src += mlen;
148
        break;
201✔
149
      }
201✔
150
    }
151

152
    if (!*mptr) {
153
      if ((cp - pbuf + 1) >= blen) {
1,151✔
154
        pr_log_pri(PR_LOG_ERR,
950✔
155
          "WARNING: attempt to overflow internal ProFTPD buffers");
×
156
        cp = pbuf;
157

×
158
        cp += (blen - 1);
159
        goto done;
×
160
      }
×
161

162
      *cp++ = *src++;
163
    }
950✔
164
  }
165

166
 done:
167
  *cp = '\0';
54✔
168

54✔
169
  return pbuf;
170
}
54✔
171

172
const char *pr_str_quote(pool *p, const char *str) {
173
  if (p == NULL ||
8✔
174
      str == NULL) {
8✔
175
    errno = EINVAL;
8✔
176
    return NULL;
4✔
177
  }
4✔
178

179
  return sreplace(p, str, "\"", "\"\"", NULL);
180
}
4✔
181

182
const char *quote_dir(pool *p, char *path) {
183
  return pr_str_quote(p, path);
4✔
184
}
4✔
185

186
const char *pr_str_replace(pool *p, unsigned int max_replaces,
187
    const char *s, ...) {
9✔
188
  va_list args;
189
  const char *res = NULL;
9✔
190

9✔
191
  if (p == NULL ||
192
      s == NULL ||
9✔
193
      max_replaces == 0) {
9✔
194
    errno = EINVAL;
195
    return NULL;
3✔
196
  }
3✔
197

198
  va_start(args, s);
199
  res = str_vreplace(p, max_replaces, s, args);
6✔
200
  va_end(args);
6✔
201

6✔
202
  return res;
203
}
6✔
204

205
const char *sreplace(pool *p, const char *s, ...) {
206
  va_list args;
92✔
207
  const char *res = NULL;
92✔
208

92✔
209
  if (p == NULL ||
210
      s == NULL) {
92✔
211
    errno = EINVAL;
92✔
212
    return NULL;
3✔
213
  }
3✔
214

215
  va_start(args, s);
216
  res = str_vreplace(p, PR_STR_MAX_REPLACEMENTS, s, args);
89✔
217
  va_end(args);
89✔
218

89✔
219
  if (res == NULL &&
220
      errno == E2BIG) {
89✔
221
    /* For backward compatible behavior. */
2✔
222
    return s;
223
  }
1✔
224

225
  return res;
226
}
227

228
/* "safe" strcat, saves room for NUL at end of dst, and refuses to copy more
229
 * than "n" bytes.
230
 */
231
char *sstrcat(char *dst, const char *src, size_t n) {
232
  register char *d = dst;
14,447✔
233

14,447✔
234
  if (dst == NULL ||
235
      src == NULL ||
14,447✔
236
      n == 0) {
14,447✔
237
    errno = EINVAL;
238
    return NULL;
1✔
239
  }
1✔
240

241
  /* Edge case short circuit; strlcat(3) doesn't do what I think it should
242
   * do for this particular case.
243
   */
244
  if (n > 1) {
245
#if defined(HAVE_STRLCAT)
14,446✔
246
    strlcat(dst, src, n);
247

248
#else
249
    for (; *d && n > 1; d++, n--) {
250
    }
103,118✔
251

88,676✔
252
    while (n-- > 1 && *src) {
253
      *d++ = *src++;
68,201✔
254
    }
53,759✔
255

256
    *d = '\0';
257
#endif /* HAVE_STRLCAT */
14,442✔
258

259
  } else {
260
    *d = '\0';
261
  }
4✔
262

263
  return dst;
264
}
265

266
char *pstrdup(pool *p, const char *str) {
267
  char *res;
6,136✔
268
  size_t len;
6,136✔
269

6,136✔
270
  if (p == NULL ||
271
      str == NULL) {
6,136✔
272
    errno = EINVAL;
6,136✔
273
    return NULL;
26✔
274
  }
26✔
275

276
  len = strlen(str) + 1;
277

6,110✔
278
  res = palloc(p, len);
279
  if (res != NULL) {
6,110✔
280
    sstrncpy(res, str, len);
6,110✔
281
  }
6,110✔
282

283
  return res;
284
}
285

286
char *pstrndup(pool *p, const char *str, size_t n) {
287
  char *res;
650✔
288

650✔
289
  if (!p || !str) {
290
    errno = EINVAL;
650✔
291
    return NULL;
248✔
292
  }
248✔
293

294
  res = palloc(p, n + 1);
295
  sstrncpy(res, str, n + 1);
402✔
296
  return res;
402✔
297
}
402✔
298

299
char *pdircat(pool *p, ...) {
300
  char *argp, *ptr, *res;
47✔
301
  char last;
47✔
302
  int count = 0;
47✔
303
  size_t len = 0, res_len = 0;
47✔
304
  va_list ap;
47✔
305

47✔
306
  if (p == NULL) {
307
    errno = EINVAL;
47✔
308
    return NULL;
1✔
309
  }
1✔
310

311
  va_start(ap, p);
312

46✔
313
  last = 0;
314

46✔
315
  while ((res = va_arg(ap, char *)) != NULL) {
316
    /* If the first argument is "", we have to account for a leading /
139✔
317
     * which must be added.
318
     */
319
    if (!count++ && !*res) {
320
      len++;
93✔
321

2✔
322
    } else if (last && last != '/' && *res != '/') {
323
      len++;
91✔
324

39✔
325
    } else if (last && last == '/' && *res == '/') {
326
      len--;
52✔
327
    }
5✔
328

329
    res_len = strlen(res);
330
    len += res_len;
93✔
331
    last = (*res ? res[res_len-1] : 0);
93✔
332
  }
93✔
333

334
  va_end(ap);
335
  ptr = res = (char *) pcalloc(p, len + 1);
46✔
336

46✔
337
  va_start(ap, p);
338

46✔
339
  last = res_len = 0;
340

46✔
341
  while ((argp = va_arg(ap, char *)) != NULL) {
342
    size_t arglen;
139✔
343

93✔
344
    if (last && last == '/' && *argp == '/') {
345
      argp++;
93✔
346

5✔
347
    } else if (last && last != '/' && *argp != '/') {
348
      sstrcat(ptr, "/", len + 1);
88✔
349
      ptr += 1;
39✔
350
      res_len += 1;
39✔
351
    }
39✔
352

353
    arglen = strlen(argp);
354
    sstrcat(ptr, argp, len + 1);
93✔
355
    ptr += arglen;
93✔
356
    res_len += arglen;
93✔
357

93✔
358
    last = (*res ? res[res_len-1] : 0);
359
  }
93✔
360

361
  va_end(ap);
362

46✔
363
  return res;
364
}
46✔
365

366
char *pstrcat(pool *p, ...) {
367
  char *argp, *ptr, *res;
422✔
368
  size_t len = 0;
422✔
369
  va_list ap;
422✔
370

422✔
371
  if (p == NULL) {
372
    errno = EINVAL;
422✔
373
    return NULL;
1✔
374
  }
1✔
375

376
  va_start(ap, p);
377

421✔
378
  while ((res = va_arg(ap, char *)) != NULL) {
379
    len += strlen(res);
1,791✔
380
  }
1,370✔
381

382
  va_end(ap);
383

421✔
384
  ptr = res = pcalloc(p, len + 1);
385

421✔
386
  va_start(ap, p);
387

421✔
388
  while ((argp = va_arg(ap, char *)) != NULL) {
389
    size_t arglen;
1,791✔
390

1,370✔
391
    arglen = strlen(argp);
392
    sstrcat(ptr, argp, len + 1);
1,370✔
393
    ptr += arglen;
1,370✔
394
  }
1,370✔
395

396
  va_end(ap);
397

421✔
398
  return res;
399
}
421✔
400

401
int pr_strnrstr(const char *s, size_t slen, const char *suffix,
402
    size_t suffixlen, int flags) {
96✔
403
  int res = FALSE;
404

96✔
405
  if (s == NULL ||
406
      suffix == NULL) {
96✔
407
    errno = EINVAL;
96✔
408
    return -1;
3✔
409
  }
3✔
410

411
  if (slen == 0) {
412
    slen = strlen(s);
93✔
413
  }
3✔
414

415
  if (suffixlen == 0) {
416
    suffixlen = strlen(suffix);
93✔
417
  }
3✔
418

419
  if (slen == 0 &&
420
      suffixlen == 0) {
93✔
421
    return TRUE;
422
  }
423

424
  if (slen == 0 ||
425
      suffixlen == 0) {
92✔
426
    return FALSE;
92✔
427
  }
428

429
  if (suffixlen > slen) {
430
    return FALSE;
91✔
431
  }
432

433
  if (flags & PR_STR_FL_IGNORE_CASE) {
434
    if (strncasecmp(s + (slen - suffixlen), suffix, suffixlen) == 0) {
84✔
435
      res = TRUE;
81✔
436
    }
15✔
437

438
  } else {
439
    if (strncmp(s + (slen - suffixlen), suffix, suffixlen) == 0) {
440
      res = TRUE;
3✔
441
    }
2✔
442
  }
443

444
  return res;
445
}
446

447
const char *pr_str_strip(pool *p, const char *str) {
448
  const char *dup_str, *start, *finish;
7✔
449
  size_t len = 0;
7✔
450

7✔
451
  if (p == NULL ||
452
      str == NULL) {
7✔
453
    errno = EINVAL;
7✔
454
    return NULL;
3✔
455
  }
3✔
456

457
  /* First, find the non-whitespace start of the given string */
458
  for (start = str; PR_ISSPACE(*start); start++) {
459
  }
18✔
460

14✔
461
  /* Now, find the non-whitespace end of the given string */
462
  for (finish = &str[strlen(str)-1]; PR_ISSPACE(*finish); finish--) {
463
  }
18✔
464

14✔
465
  /* Include for the last byte, of course. */
466
  len = finish - start + 1;
467

4✔
468
  /* The space-stripped string is, then, everything from start to finish. */
469
  dup_str = pstrndup(p, start, len);
470

4✔
471
  return dup_str;
472
}
4✔
473

474
char *pr_str_strip_end(char *s, const char *ch) {
475
  size_t len;
21✔
476

21✔
477
  if (s == NULL ||
478
      ch == NULL) {
21✔
479
    errno = EINVAL;
21✔
480
    return NULL;
3✔
481
  }
3✔
482

483
  len = strlen(s);
484

18✔
485
  while (len && strchr(ch, *(s+len - 1))) {
486
    pr_signals_handle();
41✔
487

23✔
488
    *(s+len - 1) = '\0';
489
    len--;
23✔
490
  }
23✔
491

492
  return s;
493
}
494

495
#if defined(HAVE_STRTOULL) && \
496
  (SIZEOF_UID_T == SIZEOF_LONG_LONG && SIZEOF_GID_T == SIZEOF_LONG_LONG)
497
static int parse_ull(const char *val, unsigned long long *num) {
498
  char *endp = NULL;
499
  unsigned long long res;
500

501
  res = strtoull(val, &endp, 10);
502
  if (endp && *endp) {
503
    errno = EINVAL;
504
    return -1;
505
  }
506

507
  *num = res;
508
  return 0;
509
}
510
#endif /* HAVE_STRTOULL */
511

512
static int parse_ul(const char *val, unsigned long *num) {
513
  char *endp = NULL;
×
514
  unsigned long res;
×
515

×
516
  res = strtoul(val, &endp, 10);
517
  if (endp && *endp) {
×
518
    errno = EINVAL;
×
519
    return -1;
×
520
  }
×
521

522
  *num = res;
523
  return 0;
×
524
}
×
525

526
char *pr_str_bin2hex(pool *p, const unsigned char *buf, size_t len, int flags) {
527
  static const char *hex_lc = "0123456789abcdef", *hex_uc = "0123456789ABCDEF";
6✔
528
  register unsigned int i;
6✔
529
  const char *hex_vals;
6✔
530
  char *hex, *ptr;
6✔
531
  size_t hex_len;
6✔
532

6✔
533
  if (p == NULL ||
534
      buf == NULL) {
6✔
535
    errno = EINVAL;
6✔
536
    return NULL;
2✔
537
  }
2✔
538

539
  if (len == 0) {
540
    return pstrdup(p, "");
4✔
541
  }
1✔
542

543
  /* By default, we use lowercase hex values. */
544
  hex_vals = hex_lc;
545
  if (flags & PR_STR_FL_HEX_USE_UC) {
3✔
546
    hex_vals = hex_uc;
3✔
547
  }
1✔
548

549
  hex_len = (len * 2) + 1;
550
  hex = palloc(p, hex_len);
3✔
551

3✔
552
  ptr = hex;
553
  for (i = 0; i < len; i++) {
3✔
554
    *ptr++ = hex_vals[buf[i] >> 4];
24✔
555
    *ptr++ = hex_vals[buf[i] % 16];
18✔
556
  }
18✔
557
  *ptr = '\0';
558

3✔
559
  return hex;
560
}
3✔
561

562
static int c2h(char c, unsigned char *h) {
563
  if (c >= '0' &&
31✔
564
      c <= '9') {
31✔
565
    *h = c - '0';
566
    return TRUE;
26✔
567
  }
26✔
568

569
  if (c >= 'a' &&
570
      c <= 'f') {
5✔
571
    *h = c - 'a' + 10;
572
    return TRUE;
2✔
573
  }
2✔
574

575
  if (c >= 'A' &&
576
      c <= 'F') {
3✔
577
    *h = c - 'A' + 10;
578
    return TRUE;
2✔
579
  }
2✔
580

581
  return FALSE;
582
}
583

584
unsigned char *pr_str_hex2bin(pool *p, const unsigned char *hex, size_t hex_len,
585
    size_t *len) {
7✔
586
  register unsigned int i, j;
587
  unsigned char *data;
7✔
588
  size_t data_len;
7✔
589

7✔
590
  if (p == NULL ||
591
      hex == NULL) {
7✔
592
    errno = EINVAL;
7✔
593
    return NULL;
2✔
594
  }
2✔
595

596
  if (hex_len == 0) {
597
    hex_len = strlen((char *) hex);
5✔
598
  }
1✔
599

600
  if (hex_len == 0) {
601
    data = (unsigned char *) pstrdup(p, "");
5✔
602
    return data;
1✔
603
  }
1✔
604

605
  data_len = hex_len / 2;
606
  data = palloc(p, data_len);
4✔
607

4✔
608
  for (i = 0, j = 0; i < hex_len; i += 2) {
609
    unsigned char v1, v2;
23✔
610

16✔
611
    if (c2h(hex[i], &v1) == FALSE) {
612
      errno = ERANGE;
16✔
613
      return NULL;
1✔
614
    }
1✔
615

616
    if (c2h(hex[i+1], &v2) == FALSE) {
617
      errno = ERANGE;
15✔
618
      return NULL;
×
619
    }
×
620

621
    data[j++] = ((v1 << 4) | v2);
622
  }
15✔
623

624
  if (len != NULL) {
625
    *len = data_len;
3✔
626
  }
3✔
627

628
  return data;
629
}
630

631
/* Calculate the Damerau-Levenshtein distance between strings `a' and `b'.
632
 * This implementation borrows from the git implementation; see
633
 * git/src/levenshtein.c.
634
 */
635
int pr_str_levenshtein(pool *p, const char *a, const char *b, int swap_cost,
636
    int subst_cost, int insert_cost, int del_cost, int flags) {
13✔
637
  size_t alen, blen;
638
  unsigned int i, j;
13✔
639
  int *row0, *row1, *row2, res;
13✔
640
  pool *tmp_pool;
13✔
641

13✔
642
  if (p == NULL ||
643
      a == NULL ||
13✔
644
      b == NULL) {
13✔
645
    errno = EINVAL;
646
    return -1;
3✔
647
  }
3✔
648

649
  alen = strlen(a);
650
  blen = strlen(b);
10✔
651

10✔
652
  tmp_pool = make_sub_pool(p);
653
  pr_pool_tag(tmp_pool, "Levenshtein Distance pool");
10✔
654

10✔
655
  if (flags & PR_STR_FL_IGNORE_CASE) {
656
    char *a2, *b2;
10✔
657

5✔
658
    a2 = pstrdup(tmp_pool, a);
659
    for (i = 0; i < alen; i++) {
5✔
660
      a2[i] = tolower((int) a[i]);
33✔
661
    }
23✔
662

663
    b2 = pstrdup(tmp_pool, b);
664
    for (i = 0; i < blen; i++) {
5✔
665
      b2[i] = tolower((int) b[i]);
50✔
666
    }
40✔
667

668
    a = a2;
669
    b = b2;
670
  }
671

672
  row0 = pcalloc(tmp_pool, sizeof(int) * (blen + 1));
673
  row1 = pcalloc(tmp_pool, sizeof(int) * (blen + 1));
10✔
674
  row2 = pcalloc(tmp_pool, sizeof(int) * (blen + 1));
10✔
675

10✔
676
  for (j = 0; j <= blen; j++) {
677
    row1[j] = j * insert_cost;
85✔
678
  }
65✔
679

680
  for (i = 0; i < alen; i++) {
681
    int *ptr;
48✔
682

38✔
683
    row2[0] = (i + 1) * del_cost;
684
    for (j = 0; j < blen; j++) {
38✔
685
      /* Substitution */
327✔
686
      row2[j + 1] = row1[j] + (subst_cost * (a[i] != b[j]));
687

289✔
688
      /* Swap */
689
      if (i > 0 &&
690
          j > 0 &&
289✔
691
          a[i-1] == b[j] &&
289✔
692
          a[i] == b[j-1] &&
206✔
693
          row2[j+1] > (row0[j-1] + swap_cost)) {
16✔
694
        row2[j+1] = row0[j-1] + swap_cost;
4✔
695
      }
1✔
696

697
      /* Deletion */
698
      if (row2[j+1] > (row1[j+1] + del_cost)) {
699
        row2[j+1] = row1[j+1] + del_cost;
289✔
700
      }
2✔
701

702
      /* Insertion */
703
      if (row2[j+1] > (row2[j] + insert_cost)) {
704
        row2[j+1] = row2[j] + insert_cost;
289✔
705
      }
44✔
706
    }
707

708
    ptr = row0;
709
    row0 = row1;
710
    row1 = row2;
711
    row2 = ptr;
712
  }
713

714
  res = row2[blen];
715

10✔
716
  destroy_pool(tmp_pool);
717
  return res;
10✔
718
}
10✔
719

720
/* For tracking the Levenshtein distance for a string. */
721
struct candidate {
722
  const char *s;
723
  int distance;
724
  int flags;
725
};
726

727
static int distance_cmp(const void *a, const void *b) {
728
  const struct candidate *cand1, *cand2;
11✔
729
  const char *s1, *s2;
11✔
730
  int distance1, distance2;
11✔
731

11✔
732
  cand1 = *((const struct candidate **) a);
733
  s1 = cand1->s;
11✔
734
  distance1 = cand1->distance;
11✔
735

11✔
736
  cand2 = *((const struct candidate **) b);
737
  s2 = cand2->s;
11✔
738
  distance2 = cand2->distance;
11✔
739

11✔
740
  if (distance1 != distance2) {
741
    return distance1 - distance2;
11✔
742
  }
6✔
743

744
  if (cand1->flags & PR_STR_FL_IGNORE_CASE) {
745
    return strcasecmp(s1, s2);
5✔
746
  }
2✔
747

748
  return strcmp(s1, s2);
749
}
3✔
750

751
array_header *pr_str_get_similars(pool *p, const char *s,
752
    array_header *candidates, int max_distance, int flags) {
7✔
753
  register unsigned int i;
754
  size_t len;
7✔
755
  array_header *similars;
7✔
756
  struct candidate **distances;
7✔
757
  pool *tmp_pool;
7✔
758

7✔
759
  if (p == NULL ||
760
      s == NULL ||
7✔
761
      candidates == NULL) {
7✔
762
    errno = EINVAL;
763
    return NULL;
3✔
764
  }
3✔
765

766
  if (candidates->nelts == 0) {
767
    errno = ENOENT;
4✔
768
    return NULL;
1✔
769
  }
1✔
770

771
  if (max_distance <= 0) {
772
    max_distance = PR_STR_DEFAULT_MAX_EDIT_DISTANCE;
3✔
773
  }
3✔
774

775
  tmp_pool = make_sub_pool(p);
776
  pr_pool_tag(tmp_pool, "Similar Strings pool");
3✔
777

3✔
778
  /* In order to use qsort(3), we need a contiguous block of memory, not
779
   * one of our array_headers.
780
   */
781

782
  distances = pcalloc(tmp_pool, candidates->nelts * sizeof(struct candidate *));
783

3✔
784
  len = strlen(s);
785
  for (i = 0; i < candidates->nelts; i++) {
3✔
786
    const char *c;
13✔
787
    struct candidate *cand;
10✔
788
    int prefix_match = FALSE;
10✔
789

10✔
790
    c = ((const char **) candidates->elts)[i];
791
    cand = pcalloc(tmp_pool, sizeof(struct candidate));
10✔
792
    cand->s = c;
10✔
793
    cand->flags = flags;
10✔
794

10✔
795
    /* Give prefix matches a higher score */
796
    if (flags & PR_STR_FL_IGNORE_CASE) {
797
      if (strncasecmp(c, s, len) == 0) {
10✔
798
        prefix_match = TRUE;
6✔
799
      }
800

801
    } else {
802
      if (strncmp(c, s, len) == 0) {
803
        prefix_match = TRUE;
4✔
804
      }
805
    }
806

807
    if (prefix_match == TRUE) {
808
      cand->distance = 0;
809

3✔
810
    } else {
811
      /* Note: We arbitrarily add one to the edit distance, in order to
812
       * distinguish a distance of zero from our prefix match "distances" of
813
       * zero above.
814
       */
815
      cand->distance = pr_str_levenshtein(tmp_pool, s, c, 0, 2, 1, 3,
816
        flags) + 1;
7✔
817
    }
7✔
818

819
    distances[i] = cand;
820
  }
10✔
821

822
  qsort(distances, candidates->nelts, sizeof(struct candidate *), distance_cmp);
823

3✔
824
  similars = make_array(p, candidates->nelts, sizeof(const char *));
825
  for (i = 0; i < candidates->nelts; i++) {
3✔
826
    struct candidate *cand;
16✔
827

10✔
828
    cand = distances[i];
829
    if (cand->distance <= max_distance) {
10✔
830
      *((const char **) push_array(similars)) = cand->s;
10✔
831
    }
8✔
832
  }
833

834
  destroy_pool(tmp_pool);
835
  return similars;
3✔
836
}
3✔
837

838
array_header *pr_str_text_to_array(pool *p, const char *text, char delimiter) {
839
  char *ptr;
78✔
840
  array_header *items;
78✔
841
  size_t text_len;
78✔
842

78✔
843
  if (p == NULL ||
844
      text == NULL) {
78✔
845
    errno = EINVAL;
78✔
846
    return NULL;
2✔
847
  }
2✔
848

849
  text_len = strlen(text);
850
  items = make_array(p, 1, sizeof(char *));
76✔
851

76✔
852
  if (text_len == 0) {
853
    return items;
76✔
854
  }
855

856
  ptr = memchr(text, delimiter, text_len);
857
  while (ptr != NULL) {
75✔
858
    size_t item_len;
130✔
859

60✔
860
    pr_signals_handle();
861

60✔
862
    item_len = ptr - text;
863
    if (item_len > 0) {
60✔
864
      char *item;
60✔
865

50✔
866
      item = palloc(p, item_len + 1);
867
      memcpy(item, text, item_len);
50✔
868
      item[item_len] = '\0';
50✔
869
      *((char **) push_array(items)) = item;
50✔
870
    }
50✔
871

872
    text = ++ptr;
873

60✔
874
    /* Include one byte for the delimiter character being skipped over. */
875
    text_len = text_len - item_len - 1;
876

60✔
877
    if (text_len == 0) {
878
      break;
60✔
879
    }
880

881
    ptr = memchr(text, delimiter, text_len);
882
  }
55✔
883

884
  if (text_len > 0) {
885
    *((char **) push_array(items)) = pstrdup(p, text);
75✔
886
  }
70✔
887

888
  return items;
889
}
890

891
char *pr_str_array_to_text(pool *p, const array_header *items,
892
    const char *delimiter) {
6✔
893
  register unsigned int i;
894
  char **elts, *text = "";
6✔
895

6✔
896
  if (p == NULL ||
897
      items == NULL ||
6✔
898
      delimiter == NULL) {
6✔
899
    errno = EINVAL;
900
    return NULL;
3✔
901
  }
3✔
902

903
  if (items->nelts == 0) {
904
    return pstrdup(p, "");
3✔
905
  }
1✔
906

907
  elts = items->elts;
908
  for (i = 0; i < items->nelts; i++) {
2✔
909
    char *elt;
5✔
910

3✔
911
    elt = elts[i];
912
    text = pstrcat(p, text, *text ? delimiter : "", elt, NULL);
3✔
913
  }
5✔
914

915
  return text;
916
}
917

918
int pr_str2uid(const char *val, uid_t *uid) {
919
#ifdef HAVE_STRTOULL
1✔
920
  unsigned long long ull = 0ULL;
921
#endif /* HAVE_STRTOULL */
1✔
922
  unsigned long ul = 0UL;
923

1✔
924
  if (val == NULL ||
925
      uid == NULL) {
1✔
926
    errno = EINVAL;
1✔
927
    return -1;
1✔
928
  }
1✔
929

930
#if SIZEOF_UID_T == SIZEOF_LONG_LONG
931
# ifdef HAVE_STRTOULL
932
  if (parse_ull(val, &ull) < 0) {
933
    return -1;
934
  }
935
  *uid = ull;
936

937
# else
938
  if (parse_ul(val, &ul) < 0) {
939
    return -1;
940
  }
941
  *uid = ul;
942
# endif /* HAVE_STRTOULL */
943
#else
944
  (void) ull;
945
  if (parse_ul(val, &ul) < 0) {
×
946
    return -1;
×
947
  }
948
  *uid = ul;
949
#endif /* sizeof(uid_t) != sizeof(long long) */
×
950

951
  return 0;
952
}
×
953

954
int pr_str2gid(const char *val, gid_t *gid) {
955
#ifdef HAVE_STRTOULL
1✔
956
  unsigned long long ull = 0ULL;
957
#endif /* HAVE_STRTOULL */
1✔
958
  unsigned long ul = 0UL;
959

1✔
960
  if (val == NULL ||
961
      gid == NULL) {
1✔
962
    errno = EINVAL;
1✔
963
    return -1;
1✔
964
  }
1✔
965

966
#if SIZEOF_GID_T == SIZEOF_LONG_LONG
967
# ifdef HAVE_STRTOULL
968
  if (parse_ull(val, &ull) < 0) {
969
    return -1;
970
  }
971
  *gid = ull;
972

973
# else
974
  if (parse_ul(val, &ul) < 0) {
975
    return -1;
976
  }
977
  *gid = ul;
978
# endif /* HAVE_STRTOULL */
979
#else
980
  (void) ull;
981
  if (parse_ul(val, &ul) < 0) {
×
982
    return -1;
×
983
  }
984
  *gid = ul;
985
#endif /* sizeof(gid_t) != sizeof(long long) */
×
986

987
  return 0;
988
}
×
989

990
const char *pr_uid2str(pool *p, uid_t uid) {
991
  static char buf[64];
24✔
992

24✔
993
  memset(&buf, 0, sizeof(buf));
994
  if (uid != (uid_t) -1) {
24✔
995
#if SIZEOF_UID_T == SIZEOF_LONG_LONG
24✔
996
    pr_snprintf(buf, sizeof(buf)-1, "%llu", (unsigned long long) uid);
997
#else
998
    pr_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long) uid);
999
#endif /* sizeof(uid_t) != sizeof(long long) */
21✔
1000
  } else {
1001
    pr_snprintf(buf, sizeof(buf)-1, "%d", -1);
1002
  }
3✔
1003

1004
  if (p != NULL) {
1005
    return pstrdup(p, buf);
24✔
1006
  }
4✔
1007

1008
  return buf;
1009
}
1010

1011
const char *pr_gid2str(pool *p, gid_t gid) {
1012
  static char buf[64];
24✔
1013

24✔
1014
  memset(&buf, 0, sizeof(buf));
1015
  if (gid != (gid_t) -1) {
24✔
1016
#if SIZEOF_GID_T == SIZEOF_LONG_LONG
24✔
1017
    pr_snprintf(buf, sizeof(buf)-1, "%llu", (unsigned long long) gid);
1018
#else
1019
    pr_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long) gid);
1020
#endif /* sizeof(gid_t) != sizeof(long long) */
20✔
1021
  } else {
1022
    pr_snprintf(buf, sizeof(buf)-1, "%d", -1);
1023
  }
4✔
1024

1025
  if (p != NULL) {
1026
    return pstrdup(p, buf);
24✔
1027
  }
4✔
1028

1029
  return buf;
1030
}
1031

1032
/* NOTE: Update mod_ban's ban_parse_timestr() to use this function. */
1033
int pr_str_get_duration(const char *str, int *duration) {
1034
  int hours, mins, secs;
26✔
1035
  int flags = PR_STR_FL_IGNORE_CASE, has_suffix = FALSE;
26✔
1036
  size_t len;
26✔
1037
  char *ptr = NULL;
26✔
1038

26✔
1039
  if (str == NULL) {
1040
    errno = EINVAL;
26✔
1041
    return -1;
1✔
1042
  }
1✔
1043

1044
  if (sscanf(str, "%2d:%2d:%2d", &hours, &mins, &secs) == 3) {
1045
    if (hours < 0 ||
25✔
1046
        mins < 0 ||
4✔
1047
        secs < 0) {
3✔
1048
      errno = ERANGE;
3✔
1049
      return -1;
1✔
1050
    }
1✔
1051

1052
    if (duration != NULL) {
1053
      *duration = (hours * 60 * 60) + (mins * 60) + secs;
3✔
1054
    }
3✔
1055

1056
    return 0;
1057
  }
3✔
1058

1059
  len = strlen(str);
1060
  if (len == 0) {
21✔
1061
    errno = EINVAL;
21✔
1062
    return -1;
1✔
1063
  }
1✔
1064

1065
  /* Handle the "single component" formats:
1066
   *
1067
   * If ends with "S", "s", or "sec": parse secs
1068
   * If ends with "M", "m", or "min": parse minutes
1069
   * If ends with "H", "h", or "hr": parse hours
1070
   *
1071
   * Otherwise, try to parse as just a number of seconds.
1072
   */
1073

1074
  has_suffix = pr_strnrstr(str, len, "s", 1, flags);
1075
  if (has_suffix == FALSE) {
20✔
1076
    has_suffix = pr_strnrstr(str, len, "sec", 3, flags);
20✔
1077
  }
18✔
1078
  if (has_suffix == TRUE) {
1079
    /* Parse seconds */
20✔
1080

1081
    if (sscanf(str, "%d", &secs) == 1) {
1082
      if (secs < 0) {
4✔
1083
        errno = ERANGE;
4✔
1084
        return -1;
×
1085
      }
×
1086

1087
      if (duration != NULL) {
1088
        *duration = secs;
4✔
1089
      }
4✔
1090

1091
      return 0;
1092
    }
4✔
1093

1094
    errno = EINVAL;
1095
    return -1;
×
1096
  }
×
1097

1098
  has_suffix = pr_strnrstr(str, len, "m", 1, flags);
1099
  if (has_suffix == FALSE) {
16✔
1100
    has_suffix = pr_strnrstr(str, len, "min", 3, flags);
16✔
1101
  }
13✔
1102
  if (has_suffix == TRUE) {
1103
    /* Parse minutes */
16✔
1104

1105
    if (sscanf(str, "%d", &mins) == 1) {
1106
      if (mins < 0) {
4✔
1107
        errno = ERANGE;
4✔
1108
        return -1;
×
1109
      }
×
1110

1111
      if (duration != NULL) {
1112
        *duration = (mins * 60);
4✔
1113
      }
4✔
1114

1115
      return 0;
1116
    }
4✔
1117

1118
    errno = EINVAL;
1119
    return -1;
×
1120
  }
×
1121

1122
  has_suffix = pr_strnrstr(str, len, "h", 1, flags);
1123
  if (has_suffix == FALSE) {
12✔
1124
    has_suffix = pr_strnrstr(str, len, "hr", 2, flags);
12✔
1125
  }
8✔
1126
  if (has_suffix == TRUE) {
1127
    /* Parse hours */
12✔
1128

1129
    if (sscanf(str, "%d", &hours) == 1) {
1130
      if (hours < 0) {
6✔
1131
        errno = ERANGE;
6✔
1132
        return -1;
2✔
1133
      }
2✔
1134

1135
      if (duration != NULL) {
1136
        *duration = (hours * 60 * 60);
4✔
1137
      }
4✔
1138

1139
      return 0;
1140
    }
4✔
1141

1142
    errno = EINVAL;
1143
    return -1;
×
1144
  }
×
1145

1146
  /* Use strtol(3) here, check for trailing garbage, etc. */
1147
  secs = (int) strtol(str, &ptr, 10);
1148
  if (ptr && *ptr) {
6✔
1149
    /* Not a bare number, but a string with non-numeric characters. */
6✔
1150
    errno = EINVAL;
1151
    return -1;
4✔
1152
  }
4✔
1153

1154
  if (secs < 0) {
1155
    errno = ERANGE;
2✔
1156
    return -1;
1✔
1157
  }
1✔
1158

1159
  if (duration != NULL) {
1160
    *duration = secs;
1✔
1161
  }
1✔
1162

1163
  return 0;
1164
}
1165

1166
int pr_str_get_nbytes(const char *str, const char *units, off_t *nbytes) {
1167
  off_t sz;
14✔
1168
  char *ptr = NULL;
14✔
1169
  float factor = 0.0;
14✔
1170

14✔
1171
  if (str == NULL) {
1172
    errno = EINVAL;
14✔
1173
    return -1;
2✔
1174
  }
2✔
1175

1176
  /* No negative numbers. */
1177
  if (*str == '-') {
1178
    errno = EINVAL;
12✔
1179
    return -1;
1✔
1180
  }
1✔
1181

1182
  if (units == NULL ||
1183
      *units == '\0') {
11✔
1184
    factor = 1.0;
11✔
1185

1186
  } else if (strncasecmp(units, "KB", 3) == 0) {
1187
    factor = 1024.0;
7✔
1188

1189
  } else if (strncasecmp(units, "MB", 3) == 0) {
1190
    factor = 1024.0 * 1024.0;
6✔
1191

1192
  } else if (strncasecmp(units, "GB", 3) == 0) {
1193
    factor = 1024.0 * 1024.0 * 1024.0;
5✔
1194

1195
  } else if (strncasecmp(units, "TB", 3) == 0) {
1196
    factor = 1024.0 * 1024.0 * 1024.0 * 1024.0;
4✔
1197

1198
  } else if (strncasecmp(units, "B", 2) == 0) {
1199
    factor = 1.0;
2✔
1200

1201
  } else {
1202
    errno = EINVAL;
1203
    return -1;
1✔
1204
  }
1✔
1205

1206
  errno = 0;
1207

10✔
1208
#ifdef HAVE_STRTOULL
1209
  sz = strtoull(str, &ptr, 10);
1210
#else
10✔
1211
  sz = strtoul(str, &ptr, 10);
1212
#endif /* !HAVE_STRTOULL */
1213

1214
  if (errno == ERANGE) {
1215
    return -1;
10✔
1216
  }
1217

1218
  if (ptr != NULL && *ptr) {
1219
    /* Error parsing the given string */
10✔
1220
    errno = EINVAL;
1221
    return -1;
3✔
1222
  }
3✔
1223

1224
  /* Don't bother applying the factor if the result will overflow the result. */
1225
#ifdef ULLONG_MAX
1226
  if (sz > (ULLONG_MAX / factor)) {
1227
#else
7✔
1228
  if (sz > (ULONG_MAX / factor)) {
1229
#endif /* !ULLONG_MAX */
1230
    errno = ERANGE;
1231
    return -1;
1✔
1232
  }
1✔
1233

1234
  if (nbytes != NULL) {
1235
    *nbytes = (off_t) (sz * factor);
6✔
1236
  }
6✔
1237

1238
  return 0;
1239
}
1240

1241
char *pr_str_get_word(char **cp, int flags) {
1242
  char *res, *dst;
70✔
1243
  int quote_mode = FALSE;
70✔
1244

70✔
1245
  if (cp == NULL ||
1246
     !*cp ||
70✔
1247
     !**cp) {
69✔
1248
    errno = EINVAL;
68✔
1249
    return NULL;
20✔
1250
  }
20✔
1251

1252
  if (!(flags & PR_STR_FL_PRESERVE_WHITESPACE)) {
1253
    while (**cp && PR_ISSPACE(**cp)) {
50✔
1254
      pr_signals_handle();
53✔
1255
      (*cp)++;
5✔
1256
    }
5✔
1257
  }
1258

1259
  if (!**cp) {
1260
    return NULL;
50✔
1261
  }
1262

1263
  res = dst = *cp;
1264

49✔
1265
  if (!(flags & PR_STR_FL_PRESERVE_COMMENTS)) {
1266
    /* Stop processing at start of an inline comment. */
49✔
1267
    if (**cp == '#') {
1268
      return NULL;
47✔
1269
    }
1270
  }
1271

1272
  if (!(flags & PR_STR_FL_IGNORE_QUOTES)) {
1273
    if (**cp == '\"') {
48✔
1274
      quote_mode = TRUE;
46✔
1275
      (*cp)++;
4✔
1276
    }
4✔
1277
  }
1278

1279
  while (**cp && (quote_mode ? (**cp != '\"') : !PR_ISSPACE(**cp))) {
1280
    pr_signals_handle();
472✔
1281

403✔
1282
    if (**cp == '\\' &&
1283
        quote_mode == TRUE) {
403✔
1284
      /* Escaped char */
1285
      if (*((*cp)+1)) {
1286
        *dst++ = *(++(*cp));
1✔
1287
        (*cp)++;
1✔
1288
        continue;
1✔
1289
      }
1✔
1290
    }
1291

1292
    *dst++ = **cp;
1293
    (*cp)++;
402✔
1294
  }
402✔
1295

1296
  if (**cp) {
1297
    (*cp)++;
48✔
1298
  }
25✔
1299

1300
  *dst = '\0';
1301
  return res;
48✔
1302
}
48✔
1303

1304
/* get_token tokenizes a string, increments the src pointer to the next
1305
 * non-separator in the string.  If the src string is empty or NULL, the next
1306
 * token returned is NULL.
1307
 */
1308
char *pr_str_get_token2(char **src, char *sep, size_t *token_len) {
1309
  char *token;
22✔
1310
  size_t len = 0;
22✔
1311

22✔
1312
  if (src == NULL ||
1313
      *src == NULL ||
22✔
1314
      **src == '\0' ||
20✔
1315
      sep == NULL) {
18✔
1316

1317
    if (token_len != NULL) {
1318
      *token_len = len;
10✔
1319
    }
1✔
1320

1321
    errno = EINVAL;
1322
    return NULL;
10✔
1323
  }
10✔
1324

1325
  token = *src;
1326

1327
  while (**src && !strchr(sep, **src)) {
1328
    (*src)++;
51✔
1329
    len++;
39✔
1330
  }
39✔
1331

1332
  if (**src) {
1333
    *(*src)++ = '\0';
12✔
1334
  }
8✔
1335

1336
  if (token_len != NULL) {
1337
    *token_len = len;
12✔
1338
  }
3✔
1339

1340
  return token;
1341
}
1342

1343
char *pr_str_get_token(char **src, char *sep) {
1344
  return pr_str_get_token2(src, sep, NULL);
15✔
1345
}
15✔
1346

1347
int pr_str_is_boolean(const char *str) {
1348
  if (str == NULL) {
43✔
1349
    errno = EINVAL;
43✔
1350
    return -1;
1✔
1351
  }
1✔
1352

1353
  if (strncasecmp(str, "on", 3) == 0) {
1354
    return TRUE;
42✔
1355
  }
1356

1357
  if (strncasecmp(str, "off", 4) == 0) {
1358
    return FALSE;
41✔
1359
  }
1360

1361
  if (strncasecmp(str, "yes", 4) == 0) {
1362
    return TRUE;
39✔
1363
  }
1364

1365
  if (strncasecmp(str, "no", 3) == 0) {
1366
    return FALSE;
38✔
1367
  }
1368

1369
  if (strncasecmp(str, "true", 5) == 0) {
1370
    return TRUE;
37✔
1371
  }
1372

1373
  if (strncasecmp(str, "false", 6) == 0) {
1374
    return FALSE;
36✔
1375
  }
1376

1377
  if (strncasecmp(str, "1", 2) == 0) {
1378
    return TRUE;
35✔
1379
  }
1380

1381
  if (strncasecmp(str, "0", 2) == 0) {
1382
    return FALSE;
34✔
1383
  }
1384

1385
  errno = EINVAL;
1386
  return -1;
33✔
1387
}
33✔
1388

1389
/* Return true if str contains any of the glob(7) characters. */
1390
int pr_str_is_fnmatch(const char *str) {
1391
  int have_bracket = 0;
33✔
1392

33✔
1393
  if (str == NULL) {
1394
    return FALSE;
33✔
1395
  }
1396

1397
  while (*str) {
1398
    switch (*str) {
219✔
1399
      case '?':
206✔
1400
      case '*':
1401
        return TRUE;
1402

1403
      case '\\':
1404
        /* If the next character is NUL, we've reached the end of the string. */
4✔
1405
        if (*(str+1) == '\0') {
1406
          return FALSE;
4✔
1407
        }
1408

1409
        /* Skip past the escaped character, i.e. the next character. */
1410
        str++;
1411
        break;
3✔
1412

3✔
1413
      case '[':
1414
        have_bracket++;
2✔
1415
        break;
2✔
1416

2✔
1417
      case ']':
1418
        if (have_bracket) {
2✔
1419
          return TRUE;
2✔
1420
        }
1421
        break;
1422

1423
      default:
1424
        break;
1425
    }
1426

1427
    str++;
1428
  }
187✔
1429

1430
  return FALSE;
1431
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc