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

proftpd / proftpd / 14526507026

17 Apr 2025 11:25PM UTC coverage: 93.03% (+0.4%) from 92.667%
14526507026

push

github

51358 of 55206 relevant lines covered (93.03%)

234.02 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-2025 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, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18
 *
19
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
20
 * and other respective copyright holders give permission to link this program
21
 * with OpenSSL, and distribute the resulting executable, without including
22
 * the source code for OpenSSL in the source distribution.
23
 */
24

25
/* String manipulation functions. */
26

27
#include "conf.h"
28

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

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

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

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

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

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

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

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

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

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

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

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

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

120
  cp = pbuf = (char *) pcalloc(p, ++blen);
54✔
121

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

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

128
      pr_signals_handle();
34,552✔
129

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

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

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

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

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

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

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

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

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

170
  return pbuf;
54✔
171
}
172

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

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

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

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

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

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

203
  return res;
6✔
204
}
205

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

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

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

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

226
  return res;
227
}
228

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

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

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

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

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

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

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

264
  return dst;
265
}
266

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

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

277
  len = strlen(str) + 1;
6,116✔
278

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

284
  return res;
285
}
286

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

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

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

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

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

312
  va_start(ap, p);
46✔
313

314
  last = 0;
46✔
315

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

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

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

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

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

338
  va_start(ap, p);
46✔
339

340
  last = res_len = 0;
46✔
341

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

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

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

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

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

362
  va_end(ap);
46✔
363

364
  return res;
46✔
365
}
366

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

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

377
  va_start(ap, p);
421✔
378

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

383
  va_end(ap);
421✔
384

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

387
  va_start(ap, p);
421✔
388

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

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

397
  va_end(ap);
421✔
398

399
  return res;
421✔
400
}
401

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

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

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

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

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

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

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

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

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

445
  return res;
446
}
447

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

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

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

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

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

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

472
  return dup_str;
4✔
473
}
474

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

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

484
  len = strlen(s);
18✔
485

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

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

493
  return s;
494
}
495

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

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

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

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

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

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

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

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

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

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

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

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

560
  return hex;
3✔
561
}
562

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

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

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

582
  return FALSE;
583
}
584

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

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

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

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

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

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

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

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

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

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

629
  return data;
630
}
631

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

715
  res = row2[blen];
10✔
716

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

808
    if (prefix_match == TRUE) {
809
      cand->distance = 0;
3✔
810

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

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

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

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

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

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

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

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

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

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

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

861
    pr_signals_handle();
60✔
862

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

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

873
    text = ++ptr;
60✔
874

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

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

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

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

889
  return items;
890
}
891

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

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

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

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

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

916
  return text;
917
}
918

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

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

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

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

952
  return 0;
×
953
}
954

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

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

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

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

988
  return 0;
×
989
}
990

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

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

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

1009
  return buf;
1010
}
1011

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

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

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

1030
  return buf;
1031
}
1032

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

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

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

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

1057
    return 0;
3✔
1058
  }
1059

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

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

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

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

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

1092
      return 0;
4✔
1093
    }
1094

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

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

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

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

1116
      return 0;
4✔
1117
    }
1118

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

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

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

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

1140
      return 0;
4✔
1141
    }
1142

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

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

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

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

1164
  return 0;
1165
}
1166

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

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

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

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

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

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

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

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

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

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

1207
  errno = 0;
10✔
1208

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

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

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

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

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

1239
  return 0;
1240
}
1241

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

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

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

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

1264
  res = dst = *cp;
49✔
1265

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

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

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

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

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

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

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

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

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

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

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

1326
  token = *src;
1327

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

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

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

1341
  return token;
1342
}
1343

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1424
      default:
1425
        break;
1426
    }
1427

1428
    str++;
187✔
1429
  }
1430

1431
  return FALSE;
1432
}
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