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

neomutt / neomutt / 17538805566

06 Sep 2025 08:16PM UTC coverage: 50.104% (+0.005%) from 50.099%
17538805566

push

github

web-flow
Revamp the S/MIME docs (#4665)

* the smime.rc have been re-imported from the neomutt-contrib repo, since smime-notes.txt was referring to it
* all S/MIME related files have been placed in a new smime directory, to improve discoverability
* I have verified / modernized / clarified smime-notes.txt

9132 of 18226 relevant lines covered (50.1%)

272.39 hits per line

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

93.59
/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 "ctype2.h"
42
#include "exit.h"
43
#include "logging2.h"
44
#include "memory.h"
45
#include "string2.h"
46
#ifdef HAVE_SYSEXITS_H
47
#include <sysexits.h>
48
#endif
49

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

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

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

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

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

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

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

178
  return NULL;
179
}
180

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

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

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

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

218
    return 0;
219
  }
220

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

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

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

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

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

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

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

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

319
  char *p = str;
320

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

327
  return str;
328
}
329

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

342
  char *p = str;
343

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

350
  return str;
351
}
352

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

615
  return (char *) s;
616
}
617

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

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

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

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

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

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

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

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

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

691
  return true;
692
}
693

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

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

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

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

734
  return NULL;
735
}
736

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

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

758
  return rc;
759
}
760

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

775
  va_list ap;
776
  int n;
777

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

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

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

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

807
  int rlen = 256;
808

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

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

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

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

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

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

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

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

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

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

915
  if (!same)
916
    return 0;
917

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

920
#undef CMP_INBOX
921
#undef IS_INBOX
922
}
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