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

neomutt / neomutt / 18454331572

12 Oct 2025 05:03PM UTC coverage: 50.06% (-0.03%) from 50.093%
18454331572

push

github

flatcap
Use buf_string() to get the data from a Buffer

5 of 17 new or added lines in 3 files covered. (29.41%)

203 existing lines in 4 files now uncovered.

9127 of 18232 relevant lines covered (50.06%)

274.1 hits per line

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

90.68
/mutt/string.c
1
/**
2
 * @file
3
 * String manipulation functions
4
 *
5
 * @authors
6
 * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2018-2020 Pietro Cerutti <gahr@gahr.ch>
8
 * Copyright (C) 2021 Austin Ray <austin@austinray.io>
9
 * Copyright (C) 2022 Claes Nästén <pekdon@gmail.com>
10
 * Copyright (C) 2025 Dennis Schön <mail@dennis-schoen.de>
11
 *
12
 * @copyright
13
 * This program is free software: you can redistribute it and/or modify it under
14
 * the terms of the GNU General Public License as published by the Free Software
15
 * Foundation, either version 2 of the License, or (at your option) any later
16
 * version.
17
 *
18
 * This program is distributed in the hope that it will be useful, but WITHOUT
19
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21
 * details.
22
 *
23
 * You should have received a copy of the GNU General Public License along with
24
 * this program.  If not, see <http://www.gnu.org/licenses/>.
25
 */
26

27
/**
28
 * @page mutt_string String manipulation functions
29
 *
30
 * Lots of commonly-used string manipulation routines.
31
 */
32

33
#include "config.h"
34
#include <errno.h>
35
#include <stdarg.h> // IWYU pragma: keep
36
#include <stdbool.h>
37
#include <stdio.h>
38
#include <stdlib.h>
39
#include <string.h>
40
#include <strings.h>
41
#include "array.h"
42
#include "ctype2.h"
43
#include "exit.h"
44
#include "logging2.h"
45
#include "memory.h"
46
#include "string2.h"
47
#ifdef HAVE_SYSEXITS_H
48
#include <sysexits.h>
49
#endif
50

51
#ifndef HAVE_STRCASESTR
52
/**
53
 * strcasestr - Find the first occurrence of needle in haystack, ignoring case
54
 * @param haystack String to search
55
 * @param needle   String to find
56
 * @retval ptr Matched string, or NULL on failure
57
 */
58
static char *strcasestr(const char *haystack, const char *needle)
59
{
60
  size_t haystackn = strlen(haystack);
61
  size_t needlen = strlen(needle);
62

63
  const char *p = haystack;
64
  while (haystackn >= needlen)
65
  {
66
    if (strncasecmp(p, needle, needlen) == 0)
67
      return (char *) p;
68
    p++;
69
    haystackn--;
70
  }
71
  return NULL;
72
}
73
#endif /* HAVE_STRCASESTR */
74

75
#ifndef HAVE_STRSEP
76
/**
77
 * strsep - Extract a token from a string
78
 * @param stringp String to be split up
79
 * @param delim   Characters to split stringp at
80
 * @retval ptr Next token, or NULL if the no more tokens
81
 *
82
 * @note The pointer stringp will be moved and NULs inserted into it
83
 */
84
static char *strsep(char **stringp, const char *delim)
85
{
86
  if (!*stringp)
87
    return NULL;
88

89
  char *start = *stringp;
90
  for (char *p = *stringp; *p != '\0'; p++)
91
  {
92
    for (const char *s = delim; *s != '\0'; s++)
93
    {
94
      if (*p == *s)
95
      {
96
        *p = '\0';
97
        *stringp = p + 1;
98
        return start;
99
      }
100
    }
101
  }
102
  *stringp = NULL;
103
  return start;
104
}
105
#endif /* HAVE_STRSEP */
106

107
/**
108
 * struct SysExits - Lookup table of error messages
109
 */
110
struct SysExits
111
{
112
  int err_num;         ///< Error number, see errno(3)
113
  const char *err_str; ///< Human-readable string for error
114
};
115

116
/// Lookup table of error messages
117
static const struct SysExits SysExits[] = {
118
#ifdef EX_USAGE
119
  { 0xff & EX_USAGE, "Bad usage." },
120
#endif
121
#ifdef EX_DATAERR
122
  { 0xff & EX_DATAERR, "Data format error." },
123
#endif
124
#ifdef EX_NOINPUT
125
  { 0xff & EX_NOINPUT, "Can't open input." },
126
#endif
127
#ifdef EX_NOUSER
128
  { 0xff & EX_NOUSER, "User unknown." },
129
#endif
130
#ifdef EX_NOHOST
131
  { 0xff & EX_NOHOST, "Host unknown." },
132
#endif
133
#ifdef EX_UNAVAILABLE
134
  { 0xff & EX_UNAVAILABLE, "Service unavailable." },
135
#endif
136
#ifdef EX_SOFTWARE
137
  { 0xff & EX_SOFTWARE, "Internal error." },
138
#endif
139
#ifdef EX_OSERR
140
  { 0xff & EX_OSERR, "Operating system error." },
141
#endif
142
#ifdef EX_OSFILE
143
  { 0xff & EX_OSFILE, "System file missing." },
144
#endif
145
#ifdef EX_CANTCREAT
146
  { 0xff & EX_CANTCREAT, "Can't create output." },
147
#endif
148
#ifdef EX_IOERR
149
  { 0xff & EX_IOERR, "I/O error." },
150
#endif
151
#ifdef EX_TEMPFAIL
152
  { 0xff & EX_TEMPFAIL, "Deferred." },
153
#endif
154
#ifdef EX_PROTOCOL
155
  { 0xff & EX_PROTOCOL, "Remote protocol error." },
156
#endif
157
#ifdef EX_NOPERM
158
  { 0xff & EX_NOPERM, "Insufficient permission." },
159
#endif
160
#ifdef EX_CONFIG
161
  { 0xff & EX_NOPERM, "Local configuration error." },
162
#endif
163
  { S_ERR, "Exec error." },
164
};
165

166
/**
167
 * mutt_str_sysexit - Return a string matching an error code
168
 * @param err_num Error code, e.g. EX_NOPERM
169
 * @retval ptr string representing the error code
170
 */
171
const char *mutt_str_sysexit(int err_num)
7✔
172
{
173
  for (size_t i = 0; i < countof(SysExits); i++)
74✔
174
  {
175
    if (err_num == SysExits[i].err_num)
72✔
176
      return SysExits[i].err_str;
5✔
177
  }
178

179
  return NULL;
180
}
181

182
/**
183
 * mutt_str_sep - Find first occurrence of any of delim characters in *stringp
184
 * @param stringp Pointer to string to search for delim, updated with position of after delim if found else NULL
185
 * @param delim   String with characters to search for in *stringp
186
 * @retval ptr Input value of *stringp
187
 */
188
char *mutt_str_sep(char **stringp, const char *delim)
7✔
189
{
190
  if (!stringp || !*stringp || !delim)
7✔
191
    return NULL;
192
  return strsep(stringp, delim);
4✔
193
}
194

195
/**
196
 * startswith - Check whether a string starts with a prefix
197
 * @param str String to check
198
 * @param prefix Prefix to match
199
 * @param match_case True if case needs to match
200
 * @retval num Length of prefix if str starts with prefix
201
 * @retval 0   str does not start with prefix
202
 */
203
static size_t startswith(const char *str, const char *prefix, bool match_case)
15,032✔
204
{
205
  if (!str || (str[0] == '\0') || !prefix || (prefix[0] == '\0'))
15,032✔
206
  {
207
    return 0;
208
  }
209

210
  const char *saved_prefix = prefix;
211
  for (; *str && *prefix; str++, prefix++)
67,016✔
212
  {
213
    if (*str == *prefix)
59,839✔
214
      continue;
50,697✔
215

216
    if (!match_case && mutt_tolower(*str) == mutt_tolower(*prefix))
9,142✔
217
      continue;
1,306✔
218

219
    return 0;
220
  }
221

222
  return (*prefix == '\0') ? (prefix - saved_prefix) : 0;
7,177✔
223
}
224

225
/**
226
 * mutt_str_startswith - Check whether a string starts with a prefix
227
 * @param str String to check
228
 * @param prefix Prefix to match
229
 * @retval num Length of prefix if str starts with prefix
230
 * @retval 0   str does not start with prefix
231
 */
232
size_t mutt_str_startswith(const char *str, const char *prefix)
13,112✔
233
{
234
  return startswith(str, prefix, true);
13,112✔
235
}
236

237
/**
238
 * mutt_istr_startswith - Check whether a string starts with a prefix, ignoring case
239
 * @param str String to check
240
 * @param prefix Prefix to match
241
 * @retval num Length of prefix if str starts with prefix
242
 * @retval 0   str does not start with prefix
243
 */
244
size_t mutt_istr_startswith(const char *str, const char *prefix)
1,920✔
245
{
246
  return startswith(str, prefix, false);
1,920✔
247
}
248

249
/**
250
 * mutt_str_dup - Copy a string, safely
251
 * @param str String to copy
252
 * @retval ptr  Copy of the string
253
 * @retval NULL str was NULL or empty
254
 */
255
char *mutt_str_dup(const char *str)
6,913✔
256
{
257
  if (!str || (*str == '\0'))
6,913✔
258
    return NULL;
259

260
  char *p = strdup(str);
6,683✔
261
  if (!p)
6,683✔
262
  {
263
    mutt_error("%s", strerror(errno)); // LCOV_EXCL_LINE
264
    mutt_exit(1);                      // LCOV_EXCL_LINE
265
  }
266
  return p;
267
}
268

269
/**
270
 * mutt_str_replace - Replace one string with another
271
 * @param[out] p String to replace
272
 * @param[in]  s New string
273
 * @retval ptr Replaced string
274
 *
275
 * This function free()s the original string, strdup()s the new string and
276
 * overwrites the pointer to the first string.
277
 *
278
 * This function alters the pointer of the caller.
279
 *
280
 * @note Free *p afterwards to handle the case that *p and s reference the same memory
281
 */
282
char *mutt_str_replace(char **p, const char *s)
45✔
283
{
284
  if (!p)
45✔
285
    return NULL;
286
  const char *tmp = *p;
43✔
287
  *p = mutt_str_dup(s);
43✔
288
  FREE(&tmp);
43✔
289
  return *p;
43✔
290
}
291

292
/**
293
 * mutt_str_adjust - Shrink-to-fit a string
294
 * @param[out] ptr String to alter
295
 *
296
 * Take a string which is allocated on the heap, find its length and reallocate
297
 * the memory to be exactly the right size.
298
 *
299
 * This function alters the pointer of the caller.
300
 */
301
void mutt_str_adjust(char **ptr)
56✔
302
{
303
  if (!ptr || !*ptr)
56✔
304
    return;
305
  MUTT_MEM_REALLOC(ptr, strlen(*ptr) + 1, char);
54✔
306
}
307

308
/**
309
 * mutt_str_lower - Convert all characters in the string to lowercase
310
 * @param str String to lowercase
311
 * @retval ptr Lowercase string
312
 *
313
 * The string is transformed in place.
314
 */
315
char *mutt_str_lower(char *str)
37✔
316
{
317
  if (!str)
37✔
318
    return NULL;
319

320
  char *p = str;
321

322
  while (*p)
271✔
323
  {
324
    *p = mutt_tolower(*p);
235✔
325
    p++;
235✔
326
  }
327

328
  return str;
329
}
330

331
/**
332
 * mutt_str_upper - Convert all characters in the string to uppercase
333
 * @param str String to uppercase
334
 * @retval ptr Uppercase string
335
 *
336
 * The string is transformed in place.
337
 */
338
char *mutt_str_upper(char *str)
4✔
339
{
340
  if (!str)
4✔
341
    return NULL;
342

343
  char *p = str;
344

345
  while (*p)
13✔
346
  {
347
    *p = mutt_toupper(*p);
10✔
348
    p++;
10✔
349
  }
350

351
  return str;
352
}
353

354
/**
355
 * mutt_strn_copy - Copy a sub-string into a buffer
356
 * @param dest   Buffer for the result
357
 * @param src    Start of the string to copy
358
 * @param len    Length of the string to copy
359
 * @param dsize  Destination buffer size
360
 * @retval ptr Destination buffer
361
 */
362
char *mutt_strn_copy(char *dest, const char *src, size_t len, size_t dsize)
7✔
363
{
364
  if (!src || !dest || (len == 0) || (dsize == 0))
7✔
365
    return dest;
366

367
  if (len > (dsize - 1))
2✔
368
    len = dsize - 1;
369
  memcpy(dest, src, len);
370
  dest[len] = '\0';
2✔
371
  return dest;
2✔
372
}
373

374
/**
375
 * mutt_strn_dup - Duplicate a sub-string
376
 * @param begin Start of the string to copy
377
 * @param len   Length of string to copy
378
 * @retval ptr New string
379
 *
380
 * The caller must free the returned string.
381
 */
382
char *mutt_strn_dup(const char *begin, size_t len)
234✔
383
{
384
  if (!begin)
234✔
385
    return NULL;
386

387
  char *p = MUTT_MEM_MALLOC(len + 1, char);
232✔
388
  memcpy(p, begin, len);
389
  p[len] = '\0';
232✔
390
  return p;
232✔
391
}
392

393
/**
394
 * mutt_str_cmp - Compare two strings, safely
395
 * @param a First string to compare
396
 * @param b Second string to compare
397
 * @retval -1 a precedes b
398
 * @retval  0 a and b are identical
399
 * @retval  1 b precedes a
400
 */
401
int mutt_str_cmp(const char *a, const char *b)
14,307✔
402
{
403
  return strcmp(NONULL(a), NONULL(b));
16,290✔
404
}
405

406
/**
407
 * mutt_istr_cmp - Compare two strings ignoring case, safely
408
 * @param a First string to compare
409
 * @param b Second string to compare
410
 * @retval -1 a precedes b
411
 * @retval  0 a and b are identical
412
 * @retval  1 b precedes a
413
 */
414
int mutt_istr_cmp(const char *a, const char *b)
5,257✔
415
{
416
  return strcasecmp(NONULL(a), NONULL(b));
5,269✔
417
}
418

419
/**
420
 * mutt_strn_equal - Check for equality of two strings (to a maximum), safely
421
 * @param a   First string to compare
422
 * @param b   Second string to compare
423
 * @param num Maximum number of bytes to compare
424
 * @retval true First num chars of both strings are equal
425
 * @retval false First num chars of both strings not equal
426
 */
427
bool mutt_strn_equal(const char *a, const char *b, size_t num)
4,927✔
428
{
429
  return strncmp(NONULL(a), NONULL(b), num) == 0;
4,929✔
430
}
431

432
/**
433
 * mutt_istrn_cmp - Compare two strings ignoring case (to a maximum), safely
434
 * @param a   First string to compare
435
 * @param b   Second string to compare
436
 * @param num Maximum number of bytes to compare
437
 * @retval -1 a precedes b
438
 * @retval  0 a and b are identical
439
 * @retval  1 b precedes a
440
 */
441
int mutt_istrn_cmp(const char *a, const char *b, size_t num)
16✔
442
{
443
  return strncasecmp(NONULL(a), NONULL(b), num);
18✔
444
}
445

446
/**
447
 * mutt_istrn_equal - Check for equality of two strings ignoring case (to a maximum), safely
448
 * @param a   First string to compare
449
 * @param b   Second string to compare
450
 * @param num Maximum number of bytes to compare
451
 * @retval -1 a precedes b
452
 * @retval true First num chars of both strings are equal, ignoring case
453
 * @retval false First num chars of both strings not equal, ignoring case
454
 */
455
bool mutt_istrn_equal(const char *a, const char *b, size_t num)
2,310✔
456
{
457
  return strncasecmp(NONULL(a), NONULL(b), num) == 0;
2,312✔
458
}
459

460
/**
461
 * mutt_istrn_rfind - Find last instance of a substring, ignoring case
462
 * @param haystack        String to search through
463
 * @param haystack_length Length of the string
464
 * @param needle          String to find
465
 * @retval NULL String not found
466
 * @retval ptr  Location of string
467
 *
468
 * Return the last instance of needle in the haystack, or NULL.
469
 * Like strcasestr(), only backwards, and for a limited haystack length.
470
 */
471
const char *mutt_istrn_rfind(const char *haystack, size_t haystack_length, const char *needle)
42✔
472
{
473
  if (!haystack || (haystack_length == 0) || !needle)
42✔
474
    return NULL;
475

476
  int needle_length = strlen(needle);
35✔
477
  const char *haystack_end = haystack + haystack_length - needle_length;
35✔
478

479
  for (const char *p = haystack_end; p >= haystack; p--)
237✔
480
  {
481
    for (size_t i = 0; i < needle_length; i++)
539✔
482
    {
483
      if ((mutt_tolower(p[i]) != mutt_tolower(needle[i])))
517✔
484
        goto next;
202✔
485
    }
486
    return p;
487

488
  next:;
489
  }
490
  return NULL;
491
}
492

493
/**
494
 * mutt_str_len - Calculate the length of a string, safely
495
 * @param a String to measure
496
 * @retval num Length in bytes
497
 */
498
size_t mutt_str_len(const char *a)
13,180✔
499
{
500
  return a ? strlen(a) : 0;
13,180✔
501
}
502

503
/**
504
 * mutt_str_coll - Collate two strings (compare using locale), safely
505
 * @param a First string to compare
506
 * @param b Second string to compare
507
 * @retval <0 a precedes b
508
 * @retval  0 a and b are identical
509
 * @retval >0 b precedes a
510
 */
511
int mutt_str_coll(const char *a, const char *b)
22✔
512
{
513
  return strcoll(NONULL(a), NONULL(b));
24✔
514
}
515

516
/**
517
 * mutt_istr_find - Find first occurrence of string (ignoring case)
518
 * @param haystack String to search through
519
 * @param needle   String to find
520
 * @retval ptr  First match of the search string
521
 * @retval NULL No match, or an error
522
 */
523
const char *mutt_istr_find(const char *haystack, const char *needle)
12✔
524
{
525
  if (!haystack)
12✔
526
    return NULL;
527
  if (!needle)
11✔
528
    return haystack;
529

530
  const char *p = NULL, *q = NULL;
531

532
  while (*(p = haystack))
35✔
533
  {
534
    for (q = needle; *p && *q && (mutt_tolower(*p) == mutt_tolower(*q)); p++, q++)
79✔
535
    {
536
    }
537
    if ((*q == '\0'))
34✔
538
      return haystack;
9✔
539
    haystack++;
25✔
540
  }
541
  return NULL;
542
}
543

544
/**
545
 * mutt_str_skip_whitespace - Find the first non-whitespace character in a string
546
 * @param p String to search
547
 * @retval ptr
548
 * - First non-whitespace character
549
 * - Terminating NUL character, if the string was entirely whitespace
550
 */
551
char *mutt_str_skip_whitespace(const char *p)
12✔
552
{
553
  if (!p)
12✔
554
    return NULL;
555
  SKIPWS(p);
26✔
556
  return (char *) p;
557
}
558

559
/**
560
 * mutt_str_remove_trailing_ws - Trim trailing whitespace from a string
561
 * @param s String to trim
562
 *
563
 * The string is modified in place.
564
 */
565
void mutt_str_remove_trailing_ws(char *s)
11✔
566
{
567
  if (!s)
11✔
568
    return;
569

570
  for (char *p = s + mutt_str_len(s) - 1; (p >= s) && mutt_isspace(*p); p--)
31✔
571
    *p = '\0';
21✔
572
}
573

574
/**
575
 * mutt_str_copy - Copy a string into a buffer (guaranteeing NUL-termination)
576
 * @param dest  Buffer for the result
577
 * @param src   String to copy
578
 * @param dsize Destination buffer size
579
 * @retval num Destination string length
580
 */
581
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
2,184✔
582
{
583
  if (!dest || (dsize == 0))
2,184✔
584
    return 0;
585
  if (!src)
2,182✔
586
  {
587
    dest[0] = '\0';
4✔
588
    return 0;
4✔
589
  }
590

591
  char *dest0 = dest;
592
  while ((--dsize > 0) && (*src != '\0'))
17,913✔
593
    *dest++ = *src++;
15,735✔
594

595
  *dest = '\0';
2,178✔
596
  return dest - dest0;
2,178✔
597
}
598

599
/**
600
 * mutt_str_skip_email_wsp - Skip over whitespace as defined by RFC5322
601
 * @param s String to search
602
 * @retval ptr
603
 * - First non-whitespace character
604
 * - Terminating NUL character, if the string was entirely whitespace
605
 *
606
 * This is used primarily for parsing header fields.
607
 */
608
char *mutt_str_skip_email_wsp(const char *s)
1,006✔
609
{
610
  if (!s)
1,006✔
611
    return NULL;
612

613
  for (; mutt_str_is_email_wsp(*s); s++)
1,124✔
614
    ; // Do nothing
615

616
  return (char *) s;
617
}
618

619
/**
620
 * mutt_str_lws_len - Measure the linear-white-space at the beginning of a string
621
 * @param s String to check
622
 * @param n Maximum number of characters to check
623
 * @retval num Count of whitespace characters
624
 *
625
 * Count the number of whitespace characters at the beginning of a string.
626
 * They can be `<space>`, `<tab>`, `<cr>` or `<lf>`.
627
 */
628
size_t mutt_str_lws_len(const char *s, size_t n)
16✔
629
{
630
  if (!s)
16✔
631
    return 0;
632

633
  const char *p = s;
634
  size_t len = n;
635

636
  if (n == 0)
15✔
637
    return 0;
638

639
  for (; p < (s + n); p++)
46✔
640
  {
641
    if (!strchr(" \t\r\n", *p))
40✔
642
    {
643
      len = p - s;
8✔
644
      break;
8✔
645
    }
646
  }
647

648
  if ((len != 0) && strchr("\r\n", *(p - 1))) /* LWS doesn't end with CRLF */
14✔
649
    len = 0;
650
  return len;
651
}
652

653
/**
654
 * mutt_str_equal - Compare two strings
655
 * @param a First string
656
 * @param b Second string
657
 * @retval true The strings are equal
658
 * @retval false The strings are not equal
659
 */
660
bool mutt_str_equal(const char *a, const char *b)
8,081✔
661
{
662
  return (a == b) || (mutt_str_cmp(a, b) == 0);
8,081✔
663
}
664

665
/**
666
 * mutt_istr_equal - Compare two strings, ignoring case
667
 * @param a First string
668
 * @param b Second string
669
 * @retval true The strings are equal
670
 * @retval false The strings are not equal
671
 */
672
bool mutt_istr_equal(const char *a, const char *b)
4,032✔
673
{
674
  return (a == b) || (mutt_istr_cmp(a, b) == 0);
4,032✔
675
}
676

677
/**
678
 * mutt_str_is_ascii - Is a string ASCII (7-bit)?
679
 * @param str String to examine
680
 * @param len Length of string to examine
681
 * @retval true There are no 8-bit chars
682
 */
683
bool mutt_str_is_ascii(const char *str, size_t len)
8✔
684
{
685
  if (!str)
8✔
686
    return true;
687

688
  for (; (*str != '\0') && (len > 0); str++, len--)
24✔
689
    if ((*str & 0x80) != 0)
20✔
690
      return false;
691

692
  return true;
693
}
694

695
/**
696
 * mutt_str_find_word - Find the end of a word (non-space)
697
 * @param src String to search
698
 * @retval ptr End of the word
699
 *
700
 * Skip to the end of the current word.
701
 * Skip past any whitespace characters.
702
 *
703
 * @note If there aren't any more words, this will return a pointer to the
704
 *       final NUL character.
705
 */
706
const char *mutt_str_find_word(const char *src)
11✔
707
{
708
  if (!src)
11✔
709
    return NULL;
710

711
  while (*src && strchr(" \t\n", *src))
21✔
712
    src++;
11✔
713
  while (*src && !strchr(" \t\n", *src))
58✔
714
    src++;
48✔
715
  return src;
716
}
717

718
/**
719
 * mutt_str_getenv - Get an environment variable
720
 * @param name Environment variable to get
721
 * @retval ptr Value of variable
722
 * @retval NULL Variable isn't set, or is empty
723
 *
724
 * @warning The caller must not free the returned pointer.
725
 */
726
const char *mutt_str_getenv(const char *name)
1,259✔
727
{
728
  if (!name)
1,259✔
729
    return NULL;
730

731
  const char *val = getenv(name);
1,258✔
732
  if (val && (val[0] != '\0'))
1,258✔
733
    return val;
734

735
  return NULL;
736
}
737

738
/**
739
 * mutt_istr_remall - Remove all occurrences of substring, ignoring case
740
 * @param str     String containing the substring
741
 * @param target  Target substring for removal
742
 * @retval 0 String contained substring and substring was removed successfully
743
 * @retval 1 String did not contain substring
744
 */
745
int mutt_istr_remall(char *str, const char *target)
92✔
746
{
747
  int rc = 1;
748
  if (!str || !target)
92✔
749
    return rc;
750

751
  // Look through an ensure all instances of the substring are gone.
752
  while ((str = (char *) strcasestr(str, target)))
134✔
753
  {
754
    size_t target_len = mutt_str_len(target);
755
    memmove(str, str + target_len, 1 + strlen(str + target_len));
45✔
756
    rc = 0; // If we got here, then a substring existed and has been removed.
757
  }
758

759
  return rc;
760
}
761

762
#ifdef HAVE_VASPRINTF
763
/**
764
 * mutt_str_asprintf - Format a string, allocating space as necessary
765
 * @param[out] strp New string saved here
766
 * @param[in]  fmt  Format string
767
 * @param[in]  ...  Format arguments
768
 * @retval num Characters written
769
 * @retval -1  Error
770
 */
771
int mutt_str_asprintf(char **strp, const char *fmt, ...)
33✔
772
{
773
  if (!strp || !fmt)
33✔
774
    return -1;
775

776
  va_list ap;
777
  int n;
778

779
  va_start(ap, fmt);
31✔
780
  n = vasprintf(strp, fmt, ap);
781
  va_end(ap);
31✔
782

783
  /* GNU libc man page for vasprintf(3) states that the value of *strp
784
   * is undefined when the return code is -1.  */
785
  if (n < 0)
31✔
786
  {
787
    mutt_error("%s", strerror(errno)); /* LCOV_EXCL_LINE */
788
    mutt_exit(1);                      /* LCOV_EXCL_LINE */
789
  }
790

791
  if (n == 0)
31✔
792
  {
793
    /* NeoMutt convention is to use NULL for 0-length strings */
794
    FREE(strp); /* LCOV_EXCL_LINE */
795
  }
796

797
  return n;
798
}
799
#else
800
/* Allocate a C-string large enough to contain the formatted string.
801
 * This is essentially malloc+sprintf in one.
802
 */
803
int mutt_str_asprintf(char **strp, const char *fmt, ...)
804
{
805
  if (!strp || !fmt)
806
    return -1;
807

808
  int rlen = 256;
809

810
  *strp = MUTT_MEM_MALLOC(rlen, char);
811
  while (true)
812
  {
813
    va_list ap;
814
    va_start(ap, fmt);
815
    const int n = vsnprintf(*strp, rlen, fmt, ap);
816
    va_end(ap);
817
    if (n < 0)
818
    {
819
      FREE(strp);
820
      return n;
821
    }
822

823
    if (n < rlen)
824
    {
825
      /* reduce space to just that which was used.  note that 'n' does not
826
       * include the terminal nul char.  */
827
      if (n == 0) /* convention is to use NULL for zero-length strings. */
828
        FREE(strp);
829
      else if (n != rlen - 1)
830
        MUTT_MEM_REALLOC(strp, n + 1, char);
831
      return n;
832
    }
833
    /* increase size and try again */
834
    rlen = n + 1;
835
    MUTT_MEM_REALLOC(strp, rlen, char);
836
  }
837
  /* not reached */
838
}
839
#endif /* HAVE_ASPRINTF */
840

841
/**
842
 * mutt_str_hyphenate - Hyphenate a snake-case string
843
 * @param buf    Buffer for the result
844
 * @param buflen Length of the buffer
845
 * @param str    String to convert
846
 *
847
 * Replace underscores (`_`) with hyphens -`).
848
 */
849
void mutt_str_hyphenate(char *buf, size_t buflen, const char *str)
15✔
850
{
851
  if (!buf || (buflen == 0) || !str)
15✔
852
    return;
853

854
  mutt_str_copy(buf, str, buflen);
12✔
855
  for (; *buf != '\0'; buf++)
75✔
856
  {
857
    if (*buf == '_')
63✔
858
      *buf = '-';
16✔
859
  }
860
}
861

862
/**
863
 * mutt_str_inbox_cmp - Do two folders share the same path and one is an inbox - @ingroup sort_api
864
 * @param a First path
865
 * @param b Second path
866
 * @retval -1 a is INBOX of b
867
 * @retval  0 None is INBOX
868
 * @retval  1 b is INBOX for a
869
 *
870
 * This function compares two folder paths. It first looks for the position of
871
 * the last common '/' character. If a valid position is found and it's not the
872
 * last character in any of the two paths, the remaining parts of the paths are
873
 * compared (case insensitively) with the string "INBOX" followed by a non
874
 * alpha character, e.g., '.' or '/'. If only one of the two paths matches,
875
 * it's reported as being less than the other and the function returns -1 (a <
876
 * b) or 1 (a > b).  If both or no paths match the requirements, the two paths
877
 * are considered equivalent and this function returns 0.
878
 *
879
 * Examples:
880
 * * mutt_str_inbox_cmp("/foo/bar",      "/foo/baz") --> 0
881
 * * mutt_str_inbox_cmp("/foo/bar/",     "/foo/bar/inbox") --> 0
882
 * * mutt_str_inbox_cmp("/foo/bar/sent", "/foo/bar/inbox") --> 1
883
 * * mutt_str_inbox_cmp("=INBOX",        "=Drafts") --> -1
884
 * * mutt_str_inbox_cmp("=INBOX",        "=INBOX.Foo") --> 0
885
 * * mutt_str_inbox_cmp("=INBOX.Foo",    "=Drafts") --> -1
886
 */
887
int mutt_str_inbox_cmp(const char *a, const char *b)
11✔
888
{
889
#define IS_INBOX(s) (mutt_istrn_equal(s, "inbox", 5) && !mutt_isalnum((s)[5]))
890
#define CMP_INBOX(a, b) (IS_INBOX(b) - IS_INBOX(a))
891

892
  /* fast-track in case the paths have been mutt_pretty_mailbox'ified */
893
  if ((a[0] == '+') && (b[0] == '+'))
11✔
894
  {
895
    return CMP_INBOX(a + 1, b + 1);
28✔
896
  }
897

898
  const char *a_end = strrchr(a, '/');
×
UNCOV
899
  const char *b_end = strrchr(b, '/');
×
900

901
  /* If one path contains a '/', but not the other */
UNCOV
902
  if ((!a_end) ^ (!b_end))
×
903
    return 0;
904

905
  /* If neither path contains a '/' */
UNCOV
906
  if (!a_end)
×
907
    return 0;
908

909
  /* Compare the subpaths */
910
  size_t a_len = a_end - a;
×
911
  size_t b_len = b_end - b;
×
912
  size_t min = MIN(a_len, b_len);
×
913
  int same = (a[min] == '/') && (b[min] == '/') && (a[min + 1] != '\0') &&
×
UNCOV
914
             (b[min + 1] != '\0') && mutt_istrn_equal(a, b, min);
×
915

916
  if (!same)
917
    return 0;
918

UNCOV
919
  return CMP_INBOX(a + 1 + min, b + 1 + min);
×
920

921
#undef CMP_INBOX
922
#undef IS_INBOX
923
}
924

925
/**
926
 * string_array_clear - Free all memory of a StringArray
927
 * @param carr Array of text to clear
928
 *
929
 * @note Array is emptied, but not freed
930
 */
UNCOV
931
void string_array_clear(struct StringArray *arr)
×
932
{
933
  const char **str = NULL;
UNCOV
934
  ARRAY_FOREACH(str, arr)
×
935
  {
UNCOV
936
    FREE(str);
×
937
  }
938

UNCOV
939
  ARRAY_FREE(arr);
×
UNCOV
940
}
×
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