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

neomutt / neomutt / 22837812302

07 Mar 2026 01:58PM UTC coverage: 42.377% (+0.009%) from 42.368%
22837812302

push

github

flatcap
rename MT_COLOR_SIDEBAR_SPOOL_FILE

Rename MT_COLOR_SIDEBAR_SPOOLFILE to MT_COLOR_SIDEBAR_SPOOL_FILE
to match the colour name.

0 of 2 new or added lines in 1 file covered. (0.0%)

2351 existing lines in 28 files now uncovered.

12119 of 28598 relevant lines covered (42.38%)

2821.95 hits per line

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

90.74
/mutt/string.c
1
/**
2
 * @file
3
 * String manipulation functions
4
 *
5
 * @authors
6
 * Copyright (C) 2017-2025 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
 * Copyright (C) 2025 Alejandro Colomar <alx@kernel.org>
12
 * Copyright (C) 2025 Thomas Klausner <wiz@gatalith.at>
13
 *
14
 * @copyright
15
 * This program is free software: you can redistribute it and/or modify it under
16
 * the terms of the GNU General Public License as published by the Free Software
17
 * Foundation, either version 2 of the License, or (at your option) any later
18
 * version.
19
 *
20
 * This program is distributed in the hope that it will be useful, but WITHOUT
21
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
23
 * details.
24
 *
25
 * You should have received a copy of the GNU General Public License along with
26
 * this program.  If not, see <http://www.gnu.org/licenses/>.
27
 */
28

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

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

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

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

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

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

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

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

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

181
  return NULL;
182
}
183

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

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

212
  const char *saved_prefix = prefix;
213
  for (; *str && *prefix; str++, prefix++)
75,402✔
214
  {
215
    if (*str == *prefix)
68,211✔
216
      continue;
51,168✔
217

218
    if (!match_case && mutt_tolower(*str) == mutt_tolower(*prefix))
17,043✔
219
      continue;
1,310✔
220

221
    return 0;
222
  }
223

224
  return (*prefix == '\0') ? (prefix - saved_prefix) : 0;
7,191✔
225
}
226

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

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

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

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

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

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

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

322
  char *p = str;
323

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

330
  return str;
331
}
332

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

345
  char *p = str;
346

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

353
  return str;
354
}
355

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

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

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

389
  char *p = MUTT_MEM_MALLOC(len + 1, char);
2,854✔
390
  memcpy(p, begin, len);
391
  p[len] = '\0';
2,854✔
392
  return p;
2,854✔
393
}
394

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

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

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

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

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

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

478
  size_t needle_length = strlen(needle);
35✔
479
  if (needle_length > haystack_length)
35✔
480
    return NULL;
481

482
  const char *haystack_end = haystack + haystack_length - needle_length;
32✔
483

484
  for (const char *p = haystack_end; p >= haystack; p--)
234✔
485
  {
486
    for (size_t i = 0; i < needle_length; i++)
539✔
487
    {
488
      if ((mutt_tolower(p[i]) != mutt_tolower(needle[i])))
517✔
489
        goto next;
202✔
490
    }
491
    return p;
492

493
  next:;
494
  }
495
  return NULL;
496
}
497

498
/**
499
 * mutt_str_len - Calculate the length of a string, safely
500
 * @param a String to measure
501
 * @retval num Length in bytes
502
 */
503
size_t mutt_str_len(const char *a)
953,746✔
504
{
505
  return a ? strlen(a) : 0;
953,746✔
506
}
507

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

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

535
  const char *p = NULL, *q = NULL;
536

537
  while (*(p = haystack))
35✔
538
  {
539
    for (q = needle; *p && *q && (mutt_tolower(*p) == mutt_tolower(*q)); p++, q++)
79✔
540
    {
541
    }
542
    if ((*q == '\0'))
34✔
543
      return haystack;
9✔
544
    haystack++;
25✔
545
  }
546
  return NULL;
547
}
548

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

564
/**
565
 * mutt_str_remove_trailing_ws - Trim trailing whitespace from a string
566
 * @param s String to trim
567
 *
568
 * The string is modified in place.
569
 */
570
void mutt_str_remove_trailing_ws(char *s)
11✔
571
{
572
  if (!s)
11✔
573
    return;
574

575
  for (char *p = s + mutt_str_len(s) - 1; (p >= s) && mutt_isspace(*p); p--)
31✔
576
    *p = '\0';
21✔
577
}
578

579
/**
580
 * mutt_str_copy - Copy a string into a buffer (guaranteeing NUL-termination)
581
 * @param dest  Buffer for the result
582
 * @param src   String to copy
583
 * @param dsize Destination buffer size
584
 * @retval num Destination string length
585
 */
586
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
11,325✔
587
{
588
  if (!dest || (dsize == 0))
11,325✔
589
    return 0;
590
  if (!src)
11,323✔
591
  {
592
    dest[0] = '\0';
4✔
593
    return 0;
4✔
594
  }
595

596
  char *dest0 = dest;
597
  while ((--dsize > 0) && (*src != '\0'))
85,033✔
598
    *dest++ = *src++;
73,714✔
599

600
  *dest = '\0';
11,319✔
601
  return dest - dest0;
11,319✔
602
}
603

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

618
  for (; mutt_str_is_email_wsp(*s); s++)
1,312✔
619
    ; // Do nothing
620

621
  return (char *) s;
622
}
623

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

638
  const char *p = s;
639
  size_t len = n;
640

641
  if (n == 0)
15✔
642
    return 0;
643

644
  for (; p < (s + n); p++)
46✔
645
  {
646
    if (!strchr(" \t\r\n", *p))
40✔
647
    {
648
      len = p - s;
8✔
649
      break;
8✔
650
    }
651
  }
652

653
  if ((len != 0) && strchr("\r\n", *(p - 1))) /* LWS doesn't end with CRLF */
14✔
654
    len = 0;
655
  return len;
656
}
657

658
/**
659
 * mutt_str_equal - Compare two strings
660
 * @param a First string
661
 * @param b Second string
662
 * @retval true The strings are equal
663
 * @retval false The strings are not equal
664
 */
665
bool mutt_str_equal(const char *a, const char *b)
85,201✔
666
{
667
  return (a == b) || (mutt_str_cmp(a, b) == 0);
85,201✔
668
}
669

670
/**
671
 * mutt_istr_equal - Compare two strings, ignoring case
672
 * @param a First string
673
 * @param b Second string
674
 * @retval true The strings are equal
675
 * @retval false The strings are not equal
676
 */
677
bool mutt_istr_equal(const char *a, const char *b)
10,892✔
678
{
679
  return (a == b) || (mutt_istr_cmp(a, b) == 0);
10,892✔
680
}
681

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

693
  for (; (*str != '\0') && (len > 0); str++, len--)
24✔
694
    if ((*str & 0x80) != 0)
20✔
695
      return false;
696

697
  return true;
698
}
699

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

716
  while (*src && strchr(" \t\n", *src))
21✔
717
    src++;
11✔
718
  while (*src && !strchr(" \t\n", *src))
58✔
719
    src++;
48✔
720
  return src;
721
}
722

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

736
  const char *val = getenv(name);
5,328✔
737
  if (val && (val[0] != '\0'))
5,328✔
738
    return val;
739

740
  return NULL;
741
}
742

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

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

764
  return rc;
765
}
766

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

781
  va_list ap;
782
  int n;
783

784
  va_start(ap, fmt);
42✔
785
  n = vasprintf(strp, fmt, ap);
786
  va_end(ap);
42✔
787

788
  /* GNU libc man page for vasprintf(3) states that the value of *strp
789
   * is undefined when the return code is -1.  */
790
  if (n < 0)
42✔
791
  {
792
    mutt_error("%s", strerror(errno)); /* LCOV_EXCL_LINE */
793
    mutt_exit(1);                      /* LCOV_EXCL_LINE */
794
  }
795

796
  if (n == 0)
42✔
797
  {
798
    /* NeoMutt convention is to use NULL for 0-length strings */
799
    FREE(strp); /* LCOV_EXCL_LINE */
800
  }
801

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

813
  int rlen = 256;
814

815
  *strp = MUTT_MEM_MALLOC(rlen, char);
816
  while (true)
817
  {
818
    va_list ap;
819
    va_start(ap, fmt);
820
    const int n = vsnprintf(*strp, rlen, fmt, ap);
821
    va_end(ap);
822
    if (n < 0)
823
    {
824
      FREE(strp);
825
      return n;
826
    }
827

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

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

859
  mutt_str_copy(buf, str, buflen);
12✔
860
  for (; *buf != '\0'; buf++)
75✔
861
  {
862
    if (*buf == '_')
63✔
863
      *buf = '-';
16✔
864
  }
865
}
866

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

897
  /* fast-track in case the paths have been mutt_pretty_mailbox'ified */
898
  if ((a[0] == '+') && (b[0] == '+'))
11✔
899
  {
900
    return CMP_INBOX(a + 1, b + 1);
28✔
901
  }
902

UNCOV
903
  const char *a_end = strrchr(a, '/');
×
904
  const char *b_end = strrchr(b, '/');
×
905

906
  /* If one path contains a '/', but not the other */
UNCOV
907
  if ((!a_end) ^ (!b_end))
×
908
    return 0;
909

910
  /* If neither path contains a '/' */
UNCOV
911
  if (!a_end)
×
912
    return 0;
913

914
  /* Compare the subpaths */
915
  size_t a_len = a_end - a;
×
916
  size_t b_len = b_end - b;
×
UNCOV
917
  size_t min = MIN(a_len, b_len);
×
UNCOV
918
  int same = (a[min] == '/') && (b[min] == '/') && (a[min + 1] != '\0') &&
×
UNCOV
919
             (b[min + 1] != '\0') && mutt_istrn_equal(a, b, min);
×
920

921
  if (!same)
922
    return 0;
923

UNCOV
924
  return CMP_INBOX(a + 1 + min, b + 1 + min);
×
925

926
#undef CMP_INBOX
927
#undef IS_INBOX
928
}
929

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

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