Coverage Report

Created: 2023-09-25 07:17

/src/neomutt/mutt/string.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * String manipulation functions
4
 *
5
 * @authors
6
 * Copyright (C) 2017 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8
 *
9
 * @copyright
10
 * This program is free software: you can redistribute it and/or modify it under
11
 * the terms of the GNU General Public License as published by the Free Software
12
 * Foundation, either version 2 of the License, or (at your option) any later
13
 * version.
14
 *
15
 * This program is distributed in the hope that it will be useful, but WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18
 * details.
19
 *
20
 * You should have received a copy of the GNU General Public License along with
21
 * this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
/**
25
 * @page mutt_string String manipulation functions
26
 *
27
 * Lots of commonly-used string manipulation routines.
28
 */
29
30
#include "config.h"
31
#include <ctype.h>
32
#include <stdarg.h>
33
#include <stdbool.h>
34
#include <stdio.h>
35
#include <stdlib.h>
36
#include <string.h>
37
#include <strings.h>
38
#include "exit.h"
39
#include "logging2.h"
40
#include "memory.h"
41
#include "message.h"
42
#include "string2.h"
43
#ifdef HAVE_SYSEXITS_H
44
#include <sysexits.h>
45
#endif
46
47
#ifndef HAVE_STRCASESTR
48
/**
49
 * strcasestr - Find the first occurrence of needle in haystack, ignoring case
50
 * @param haystack String to search
51
 * @param needle   String to find
52
 * @retval ptr Matched string, or NULL on failure
53
 */
54
static char *strcasestr(const char *haystack, const char *needle)
55
{
56
  size_t haystackn = strlen(haystack);
57
  size_t needlen = strlen(needle);
58
59
  const char *p = haystack;
60
  while (haystackn >= needlen)
61
  {
62
    if (strncasecmp(p, needle, needlen) == 0)
63
      return (char *) p;
64
    p++;
65
    haystackn--;
66
  }
67
  return NULL;
68
}
69
#endif /* HAVE_STRCASESTR */
70
71
#ifndef HAVE_STRSEP
72
/**
73
 * strsep - Extract a token from a string
74
 * @param stringp String to be split up
75
 * @param delim   Characters to split stringp at
76
 * @retval ptr Next token, or NULL if the no more tokens
77
 *
78
 * @note The pointer stringp will be moved and NULs inserted into it
79
 */
80
static char *strsep(char **stringp, const char *delim)
81
{
82
  if (!*stringp)
83
    return NULL;
84
85
  char *start = *stringp;
86
  for (char *p = *stringp; *p != '\0'; p++)
87
  {
88
    for (const char *s = delim; *s != '\0'; s++)
89
    {
90
      if (*p == *s)
91
      {
92
        *p = '\0';
93
        *stringp = p + 1;
94
        return start;
95
      }
96
    }
97
  }
98
  *stringp = NULL;
99
  return start;
100
}
101
#endif /* HAVE_STRSEP */
102
103
/**
104
 * struct SysExits - Lookup table of error messages
105
 */
106
struct SysExits
107
{
108
  int err_num;         ///< Error number, see errno(3)
109
  const char *err_str; ///< Human-readable string for error
110
};
111
112
/// Lookup table of error messages
113
static const struct SysExits SysExits[] = {
114
#ifdef EX_USAGE
115
  { 0xff & EX_USAGE, "Bad usage." },
116
#endif
117
#ifdef EX_DATAERR
118
  { 0xff & EX_DATAERR, "Data format error." },
119
#endif
120
#ifdef EX_NOINPUT
121
  { 0xff & EX_NOINPUT, "Can't open input." },
122
#endif
123
#ifdef EX_NOUSER
124
  { 0xff & EX_NOUSER, "User unknown." },
125
#endif
126
#ifdef EX_NOHOST
127
  { 0xff & EX_NOHOST, "Host unknown." },
128
#endif
129
#ifdef EX_UNAVAILABLE
130
  { 0xff & EX_UNAVAILABLE, "Service unavailable." },
131
#endif
132
#ifdef EX_SOFTWARE
133
  { 0xff & EX_SOFTWARE, "Internal error." },
134
#endif
135
#ifdef EX_OSERR
136
  { 0xff & EX_OSERR, "Operating system error." },
137
#endif
138
#ifdef EX_OSFILE
139
  { 0xff & EX_OSFILE, "System file missing." },
140
#endif
141
#ifdef EX_CANTCREAT
142
  { 0xff & EX_CANTCREAT, "Can't create output." },
143
#endif
144
#ifdef EX_IOERR
145
  { 0xff & EX_IOERR, "I/O error." },
146
#endif
147
#ifdef EX_TEMPFAIL
148
  { 0xff & EX_TEMPFAIL, "Deferred." },
149
#endif
150
#ifdef EX_PROTOCOL
151
  { 0xff & EX_PROTOCOL, "Remote protocol error." },
152
#endif
153
#ifdef EX_NOPERM
154
  { 0xff & EX_NOPERM, "Insufficient permission." },
155
#endif
156
#ifdef EX_CONFIG
157
  { 0xff & EX_NOPERM, "Local configuration error." },
158
#endif
159
  { S_ERR, "Exec error." },
160
};
161
162
/**
163
 * mutt_str_sysexit - Return a string matching an error code
164
 * @param err_num Error code, e.g. EX_NOPERM
165
 * @retval ptr string representing the error code
166
 */
167
const char *mutt_str_sysexit(int err_num)
168
0
{
169
0
  for (size_t i = 0; i < mutt_array_size(SysExits); i++)
170
0
  {
171
0
    if (err_num == SysExits[i].err_num)
172
0
      return SysExits[i].err_str;
173
0
  }
174
175
0
  return NULL;
176
0
}
177
178
/**
179
 * mutt_str_sep - Find first occurrence of any of delim characters in *stringp
180
 * @param stringp Pointer to string to search for delim, updated with position of after delim if found else NULL
181
 * @param delim   String with characters to search for in *stringp
182
 * @retval ptr Input value of *stringp
183
 */
184
char *mutt_str_sep(char **stringp, const char *delim)
185
0
{
186
0
  if (!stringp || !*stringp || !delim)
187
0
    return NULL;
188
0
  return strsep(stringp, delim);
189
0
}
190
191
/**
192
 * startswith - Check whether a string starts with a prefix
193
 * @param str String to check
194
 * @param prefix Prefix to match
195
 * @param match_case True if case needs to match
196
 * @retval num Length of prefix if str starts with prefix
197
 * @retval 0   str does not start with prefix
198
 */
199
static size_t startswith(const char *str, const char *prefix, bool match_case)
200
373k
{
201
373k
  if (!str || (str[0] == '\0') || !prefix || (prefix[0] == '\0'))
202
6.79k
  {
203
6.79k
    return 0;
204
6.79k
  }
205
206
366k
  const char *saved_prefix = prefix;
207
881k
  for (; *str && *prefix; str++, prefix++)
208
825k
  {
209
825k
    if (*str == *prefix)
210
493k
      continue;
211
212
332k
    if (!match_case && tolower(*str) == tolower(*prefix))
213
22.1k
      continue;
214
215
310k
    return 0;
216
332k
  }
217
218
56.2k
  return (*prefix == '\0') ? (prefix - saved_prefix) : 0;
219
366k
}
220
221
/**
222
 * mutt_str_startswith - Check whether a string starts with a prefix
223
 * @param str String to check
224
 * @param prefix Prefix to match
225
 * @retval num Length of prefix if str starts with prefix
226
 * @retval 0   str does not start with prefix
227
 */
228
size_t mutt_str_startswith(const char *str, const char *prefix)
229
59.6k
{
230
59.6k
  return startswith(str, prefix, true);
231
59.6k
}
232
233
/**
234
 * mutt_istr_startswith - Check whether a string starts with a prefix, ignoring case
235
 * @param str String to check
236
 * @param prefix Prefix to match
237
 * @retval num Length of prefix if str starts with prefix
238
 * @retval 0   str does not start with prefix
239
 */
240
size_t mutt_istr_startswith(const char *str, const char *prefix)
241
313k
{
242
313k
  return startswith(str, prefix, false);
243
313k
}
244
245
/**
246
 * mutt_str_dup - Copy a string, safely
247
 * @param str String to copy
248
 * @retval ptr  Copy of the string
249
 * @retval NULL str was NULL or empty
250
 */
251
char *mutt_str_dup(const char *str)
252
3.36M
{
253
3.36M
  if (!str || (*str == '\0'))
254
698k
    return NULL;
255
256
2.66M
  return strdup(str);
257
3.36M
}
258
259
/**
260
 * mutt_str_cat - Concatenate two strings
261
 * @param buf    Buffer containing source string
262
 * @param buflen Length of buffer
263
 * @param s      String to add
264
 * @retval ptr Start of the buffer
265
 */
266
char *mutt_str_cat(char *buf, size_t buflen, const char *s)
267
0
{
268
0
  if (!buf || (buflen == 0) || !s)
269
0
    return buf;
270
271
0
  char *p = buf;
272
273
0
  buflen--; /* Space for the trailing '\0'. */
274
275
0
  for (; (*buf != '\0') && buflen; buflen--)
276
0
    buf++;
277
0
  for (; *s && buflen; buflen--)
278
0
    *buf++ = *s++;
279
280
0
  *buf = '\0';
281
282
0
  return p;
283
0
}
284
285
/**
286
 * mutt_strn_cat - Concatenate two strings
287
 * @param d  Buffer containing source string
288
 * @param l  Length of buffer
289
 * @param s  String to add
290
 * @param sl Maximum amount of string to add
291
 * @retval ptr Start of joined string
292
 *
293
 * Add a string to a maximum of @a sl bytes.
294
 */
295
char *mutt_strn_cat(char *d, size_t l, const char *s, size_t sl)
296
0
{
297
0
  if (!d || (l == 0) || !s)
298
0
    return d;
299
300
0
  char *p = d;
301
302
0
  l--; /* Space for the trailing '\0'. */
303
304
0
  for (; *d && l; l--)
305
0
    d++;
306
0
  for (; *s && l && sl; l--, sl--)
307
0
    *d++ = *s++;
308
309
0
  *d = '\0';
310
311
0
  return p;
312
0
}
313
314
/**
315
 * mutt_str_replace - Replace one string with another
316
 * @param[out] p String to replace
317
 * @param[in]  s New string
318
 * @retval ptr Replaced string
319
 *
320
 * This function free()s the original string, strdup()s the new string and
321
 * overwrites the pointer to the first string.
322
 *
323
 * This function alters the pointer of the caller.
324
 *
325
 * @note Free *p afterwards to handle the case that *p and s reference the same memory
326
 */
327
char *mutt_str_replace(char **p, const char *s)
328
51.5k
{
329
51.5k
  if (!p)
330
0
    return NULL;
331
51.5k
  const char *tmp = *p;
332
51.5k
  *p = mutt_str_dup(s);
333
51.5k
  FREE(&tmp);
334
51.5k
  return *p;
335
51.5k
}
336
337
/**
338
 * mutt_str_append_item - Add string to another separated by sep
339
 * @param[out] str  String appended
340
 * @param[in]  item String to append
341
 * @param[in]  sep separator between string item
342
 *
343
 * Append a string to another, separating them by sep if needed.
344
 *
345
 * This function alters the pointer of the caller.
346
 */
347
void mutt_str_append_item(char **str, const char *item, char sep)
348
0
{
349
0
  if (!str || !item)
350
0
    return;
351
352
0
  size_t sz = mutt_str_len(item);
353
0
  size_t ssz = mutt_str_len(*str);
354
355
0
  mutt_mem_realloc(str, ssz + (((ssz > 0) && (sep != '\0')) ? 1 : 0) + sz + 1);
356
0
  char *p = *str + ssz;
357
0
  if ((ssz > 0) && (sep != '\0'))
358
0
    *p++ = sep;
359
0
  memcpy(p, item, sz + 1);
360
0
}
361
362
/**
363
 * mutt_str_adjust - Shrink-to-fit a string
364
 * @param[out] ptr String to alter
365
 *
366
 * Take a string which is allocated on the heap, find its length and reallocate
367
 * the memory to be exactly the right size.
368
 *
369
 * This function alters the pointer of the caller.
370
 */
371
void mutt_str_adjust(char **ptr)
372
0
{
373
0
  if (!ptr || !*ptr)
374
0
    return;
375
0
  mutt_mem_realloc(ptr, strlen(*ptr) + 1);
376
0
}
377
378
/**
379
 * mutt_str_lower - Convert all characters in the string to lowercase
380
 * @param str String to lowercase
381
 * @retval ptr Lowercase string
382
 *
383
 * The string is transformed in place.
384
 */
385
char *mutt_str_lower(char *str)
386
0
{
387
0
  if (!str)
388
0
    return NULL;
389
390
0
  char *p = str;
391
392
0
  while (*p)
393
0
  {
394
0
    *p = tolower((unsigned char) *p);
395
0
    p++;
396
0
  }
397
398
0
  return str;
399
0
}
400
401
/**
402
 * mutt_str_upper - Convert all characters in the string to uppercase
403
 * @param str String to uppercase
404
 * @retval ptr Uppercase string
405
 *
406
 * The string is transformed in place.
407
 */
408
char *mutt_str_upper(char *str)
409
0
{
410
0
  if (!str)
411
0
    return NULL;
412
413
0
  char *p = str;
414
415
0
  while (*p)
416
0
  {
417
0
    *p = toupper((unsigned char) *p);
418
0
    p++;
419
0
  }
420
421
0
  return str;
422
0
}
423
424
/**
425
 * mutt_strn_copy - Copy a sub-string into a buffer
426
 * @param dest   Buffer for the result
427
 * @param src    Start of the string to copy
428
 * @param len    Length of the string to copy
429
 * @param dsize  Destination buffer size
430
 * @retval ptr Destination buffer
431
 */
432
char *mutt_strn_copy(char *dest, const char *src, size_t len, size_t dsize)
433
0
{
434
0
  if (!src || !dest || (len == 0) || (dsize == 0))
435
0
    return dest;
436
437
0
  if (len > (dsize - 1))
438
0
    len = dsize - 1;
439
0
  memcpy(dest, src, len);
440
0
  dest[len] = '\0';
441
0
  return dest;
442
0
}
443
444
/**
445
 * mutt_strn_dup - Duplicate a sub-string
446
 * @param begin Start of the string to copy
447
 * @param len   Length of string to copy
448
 * @retval ptr New string
449
 *
450
 * The caller must free the returned string.
451
 */
452
char *mutt_strn_dup(const char *begin, size_t len)
453
1.44M
{
454
1.44M
  if (!begin)
455
0
    return NULL;
456
457
1.44M
  char *p = mutt_mem_malloc(len + 1);
458
1.44M
  memcpy(p, begin, len);
459
1.44M
  p[len] = '\0';
460
1.44M
  return p;
461
1.44M
}
462
463
/**
464
 * mutt_str_cmp - Compare two strings, safely
465
 * @param a First string to compare
466
 * @param b Second string to compare
467
 * @retval -1 a precedes b
468
 * @retval  0 a and b are identical
469
 * @retval  1 b precedes a
470
 */
471
int mutt_str_cmp(const char *a, const char *b)
472
53.6M
{
473
53.6M
  return strcmp(NONULL(a), NONULL(b));
474
53.6M
}
475
476
/**
477
 * mutt_istr_cmp - Compare two strings ignoring case, safely
478
 * @param a First string to compare
479
 * @param b Second string to compare
480
 * @retval -1 a precedes b
481
 * @retval  0 a and b are identical
482
 * @retval  1 b precedes a
483
 */
484
int mutt_istr_cmp(const char *a, const char *b)
485
7.18M
{
486
7.18M
  return strcasecmp(NONULL(a), NONULL(b));
487
7.18M
}
488
489
/**
490
 * mutt_strn_equal - Check for equality of two strings (to a maximum), safely
491
 * @param a   First string to compare
492
 * @param b   Second string to compare
493
 * @param num Maximum number of bytes to compare
494
 * @retval true First num chars of both strings are equal
495
 * @retval false First num chars of both strings not equal
496
 */
497
bool mutt_strn_equal(const char *a, const char *b, size_t num)
498
1.45k
{
499
1.45k
  return strncmp(NONULL(a), NONULL(b), num) == 0;
500
1.45k
}
501
502
/**
503
 * mutt_istrn_cmp - Compare two strings ignoring case (to a maximum), safely
504
 * @param a   First string to compare
505
 * @param b   Second string to compare
506
 * @param num Maximum number of bytes to compare
507
 * @retval -1 a precedes b
508
 * @retval  0 a and b are identical
509
 * @retval  1 b precedes a
510
 */
511
int mutt_istrn_cmp(const char *a, const char *b, size_t num)
512
0
{
513
0
  return strncasecmp(NONULL(a), NONULL(b), num);
514
0
}
515
516
/**
517
 * mutt_istrn_equal - Check for equality of two strings ignoring case (to a maximum), safely
518
 * @param a   First string to compare
519
 * @param b   Second string to compare
520
 * @param num Maximum number of bytes to compare
521
 * @retval -1 a precedes b
522
 * @retval true First num chars of both strings are equal, ignoring case
523
 * @retval false First num chars of both strings not equal, ignoring case
524
 */
525
bool mutt_istrn_equal(const char *a, const char *b, size_t num)
526
144k
{
527
144k
  return strncasecmp(NONULL(a), NONULL(b), num) == 0;
528
144k
}
529
530
/**
531
 * mutt_istrn_rfind - Find last instance of a substring, ignoring case
532
 * @param haystack        String to search through
533
 * @param haystack_length Length of the string
534
 * @param needle          String to find
535
 * @retval NULL String not found
536
 * @retval ptr  Location of string
537
 *
538
 * Return the last instance of needle in the haystack, or NULL.
539
 * Like strcasestr(), only backwards, and for a limited haystack length.
540
 */
541
const char *mutt_istrn_rfind(const char *haystack, size_t haystack_length, const char *needle)
542
0
{
543
0
  if (!haystack || (haystack_length == 0) || !needle)
544
0
    return NULL;
545
546
0
  int needle_length = strlen(needle);
547
0
  const char *haystack_end = haystack + haystack_length - needle_length;
548
549
0
  for (const char *p = haystack_end; p >= haystack; --p)
550
0
  {
551
0
    for (size_t i = 0; i < needle_length; i++)
552
0
    {
553
0
      if ((tolower((unsigned char) p[i]) != tolower((unsigned char) needle[i])))
554
0
        goto next;
555
0
    }
556
0
    return p;
557
558
0
  next:;
559
0
  }
560
0
  return NULL;
561
0
}
562
563
/**
564
 * mutt_str_len - Calculate the length of a string, safely
565
 * @param a String to measure
566
 * @retval num Length in bytes
567
 */
568
size_t mutt_str_len(const char *a)
569
3.51M
{
570
3.51M
  return a ? strlen(a) : 0;
571
3.51M
}
572
573
/**
574
 * mutt_str_coll - Collate two strings (compare using locale), safely
575
 * @param a First string to compare
576
 * @param b Second string to compare
577
 * @retval <0 a precedes b
578
 * @retval  0 a and b are identical
579
 * @retval >0 b precedes a
580
 */
581
int mutt_str_coll(const char *a, const char *b)
582
0
{
583
0
  return strcoll(NONULL(a), NONULL(b));
584
0
}
585
586
/**
587
 * mutt_istr_find - Find first occurrence of string (ignoring case)
588
 * @param haystack String to search through
589
 * @param needle   String to find
590
 * @retval ptr  First match of the search string
591
 * @retval NULL No match, or an error
592
 */
593
const char *mutt_istr_find(const char *haystack, const char *needle)
594
0
{
595
0
  if (!haystack)
596
0
    return NULL;
597
0
  if (!needle)
598
0
    return haystack;
599
600
0
  const char *p = NULL, *q = NULL;
601
602
0
  while (*(p = haystack))
603
0
  {
604
0
    for (q = needle;
605
0
         *p && *q && (tolower((unsigned char) *p) == tolower((unsigned char) *q));
606
0
         p++, q++)
607
0
    {
608
0
    }
609
0
    if ((*q == '\0'))
610
0
      return haystack;
611
0
    haystack++;
612
0
  }
613
0
  return NULL;
614
0
}
615
616
/**
617
 * mutt_str_skip_whitespace - Find the first non-whitespace character in a string
618
 * @param p String to search
619
 * @retval ptr
620
 * - First non-whitespace character
621
 * - Terminating NUL character, if the string was entirely whitespace
622
 */
623
char *mutt_str_skip_whitespace(const char *p)
624
4.15k
{
625
4.15k
  if (!p)
626
0
    return NULL;
627
4.15k
  SKIPWS(p);
628
4.15k
  return (char *) p;
629
4.15k
}
630
631
/**
632
 * mutt_str_remove_trailing_ws - Trim trailing whitespace from a string
633
 * @param s String to trim
634
 *
635
 * The string is modified in place.
636
 */
637
void mutt_str_remove_trailing_ws(char *s)
638
2.69k
{
639
2.69k
  if (!s)
640
209
    return;
641
642
2.68k
  for (char *p = s + mutt_str_len(s) - 1; (p >= s) && isspace(*p); p--)
643
194
    *p = '\0';
644
2.48k
}
645
646
/**
647
 * mutt_str_copy - Copy a string into a buffer (guaranteeing NUL-termination)
648
 * @param dest  Buffer for the result
649
 * @param src   String to copy
650
 * @param dsize Destination buffer size
651
 * @retval num Destination string length
652
 */
653
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
654
308k
{
655
308k
  if (!dest || (dsize == 0))
656
0
    return 0;
657
308k
  if (!src)
658
0
  {
659
0
    dest[0] = '\0';
660
0
    return 0;
661
0
  }
662
663
308k
  char *dest0 = dest;
664
3.60M
  while ((--dsize > 0) && (*src != '\0'))
665
3.29M
    *dest++ = *src++;
666
667
308k
  *dest = '\0';
668
308k
  return dest - dest0;
669
308k
}
670
671
/**
672
 * mutt_str_skip_email_wsp - Skip over whitespace as defined by RFC5322
673
 * @param s String to search
674
 * @retval ptr
675
 * - First non-whitespace character
676
 * - Terminating NUL character, if the string was entirely whitespace
677
 *
678
 * This is used primarily for parsing header fields.
679
 */
680
char *mutt_str_skip_email_wsp(const char *s)
681
1.15M
{
682
1.15M
  if (!s)
683
0
    return NULL;
684
685
1.17M
  for (; mutt_str_is_email_wsp(*s); s++)
686
20.6k
    ; // Do nothing
687
688
1.15M
  return (char *) s;
689
1.15M
}
690
691
/**
692
 * mutt_str_lws_len - Measure the linear-white-space at the beginning of a string
693
 * @param s String to check
694
 * @param n Maximum number of characters to check
695
 * @retval num Count of whitespace characters
696
 *
697
 * Count the number of whitespace characters at the beginning of a string.
698
 * They can be `<space>`, `<tab>`, `<cr>` or `<lf>`.
699
 */
700
size_t mutt_str_lws_len(const char *s, size_t n)
701
0
{
702
0
  if (!s)
703
0
    return 0;
704
705
0
  const char *p = s;
706
0
  size_t len = n;
707
708
0
  if (n == 0)
709
0
    return 0;
710
711
0
  for (; p < (s + n); p++)
712
0
  {
713
0
    if (!strchr(" \t\r\n", *p))
714
0
    {
715
0
      len = p - s;
716
0
      break;
717
0
    }
718
0
  }
719
720
0
  if ((len != 0) && strchr("\r\n", *(p - 1))) /* LWS doesn't end with CRLF */
721
0
    len = 0;
722
0
  return len;
723
0
}
724
725
/**
726
 * mutt_str_lws_rlen - Measure the linear-white-space at the end of a string
727
 * @param s String to check
728
 * @param n Maximum number of characters to check
729
 * @retval num Count of whitespace characters
730
 *
731
 * Count the number of whitespace characters at the end of a string.
732
 * They can be `<space>`, `<tab>`, `<cr>` or `<lf>`.
733
 */
734
size_t mutt_str_lws_rlen(const char *s, size_t n)
735
0
{
736
0
  if (!s)
737
0
    return 0;
738
739
0
  const char *p = s + n - 1;
740
0
  size_t len = n;
741
742
0
  if (n == 0)
743
0
    return 0;
744
745
0
  if (strchr("\r\n", *p)) /* LWS doesn't end with CRLF */
746
0
    return 0;
747
748
0
  for (; p >= s; p--)
749
0
  {
750
0
    if (!strchr(" \t\r\n", *p))
751
0
    {
752
0
      len = s + n - 1 - p;
753
0
      break;
754
0
    }
755
0
  }
756
757
0
  return len;
758
0
}
759
760
/**
761
 * mutt_str_dequote_comment - Un-escape characters in an email address comment
762
 * @param str String to be un-escaped
763
 *
764
 * @note The string is changed in-place
765
 */
766
void mutt_str_dequote_comment(char *str)
767
0
{
768
0
  if (!str)
769
0
    return;
770
771
0
  char *w = str;
772
773
0
  for (; *str; str++)
774
0
  {
775
0
    if (*str == '\\')
776
0
    {
777
0
      if (!*++str)
778
0
        break; /* error? */
779
0
      *w++ = *str;
780
0
    }
781
0
    else if (*str != '\"')
782
0
    {
783
0
      if (w != str)
784
0
        *w = *str;
785
0
      w++;
786
0
    }
787
0
  }
788
0
  *w = '\0';
789
0
}
790
791
/**
792
 * mutt_str_equal - Compare two strings
793
 * @param a First string
794
 * @param b Second string
795
 * @retval true The strings are equal
796
 * @retval false The strings are not equal
797
 */
798
bool mutt_str_equal(const char *a, const char *b)
799
1.95M
{
800
1.95M
  return (a == b) || (mutt_str_cmp(a, b) == 0);
801
1.95M
}
802
803
/**
804
 * mutt_istr_equal - Compare two strings, ignoring case
805
 * @param a First string
806
 * @param b Second string
807
 * @retval true The strings are equal
808
 * @retval false The strings are not equal
809
 */
810
bool mutt_istr_equal(const char *a, const char *b)
811
7.18M
{
812
7.18M
  return (a == b) || (mutt_istr_cmp(a, b) == 0);
813
7.18M
}
814
815
/**
816
 * mutt_str_next_word - Find the next word in a string
817
 * @param s String to examine
818
 * @retval ptr Next word
819
 *
820
 * If the s is pointing to a word (non-space) is is skipped over.
821
 * Then, any whitespace is skipped over.
822
 *
823
 * @note What is/isn't a word is determined by isspace()
824
 */
825
const char *mutt_str_next_word(const char *s)
826
0
{
827
0
  if (!s)
828
0
    return NULL;
829
830
0
  while (*s && !isspace(*s))
831
0
    s++;
832
0
  SKIPWS(s);
833
0
  return s;
834
0
}
835
836
/**
837
 * mutt_strn_rfind - Find last instance of a substring
838
 * @param haystack        String to search through
839
 * @param haystack_length Length of the string
840
 * @param needle          String to find
841
 * @retval NULL String not found
842
 * @retval ptr  Location of string
843
 *
844
 * Return the last instance of needle in the haystack, or NULL.
845
 * Like strstr(), only backwards, and for a limited haystack length.
846
 */
847
const char *mutt_strn_rfind(const char *haystack, size_t haystack_length, const char *needle)
848
0
{
849
0
  if (!haystack || (haystack_length == 0) || !needle)
850
0
    return NULL;
851
852
0
  int needle_length = strlen(needle);
853
0
  const char *haystack_end = haystack + haystack_length - needle_length;
854
855
0
  for (const char *p = haystack_end; p >= haystack; --p)
856
0
  {
857
0
    for (size_t i = 0; i < needle_length; i++)
858
0
    {
859
0
      if (p[i] != needle[i])
860
0
        goto next;
861
0
    }
862
0
    return p;
863
864
0
  next:;
865
0
  }
866
0
  return NULL;
867
0
}
868
869
/**
870
 * mutt_str_is_ascii - Is a string ASCII (7-bit)?
871
 * @param str String to examine
872
 * @param len Length of string to examine
873
 * @retval true There are no 8-bit chars
874
 */
875
bool mutt_str_is_ascii(const char *str, size_t len)
876
0
{
877
0
  if (!str)
878
0
    return true;
879
880
0
  for (; (*str != '\0') && (len > 0); str++, len--)
881
0
    if ((*str & 0x80) != 0)
882
0
      return false;
883
884
0
  return true;
885
0
}
886
887
/**
888
 * mutt_str_find_word - Find the end of a word (non-space)
889
 * @param src String to search
890
 * @retval ptr End of the word
891
 *
892
 * Skip to the end of the current word.
893
 * Skip past any whitespace characters.
894
 *
895
 * @note If there aren't any more words, this will return a pointer to the
896
 *       final NUL character.
897
 */
898
const char *mutt_str_find_word(const char *src)
899
0
{
900
0
  if (!src)
901
0
    return NULL;
902
903
0
  while (*src && strchr(" \t\n", *src))
904
0
    src++;
905
0
  while (*src && !strchr(" \t\n", *src))
906
0
    src++;
907
0
  return src;
908
0
}
909
910
/**
911
 * mutt_str_getenv - Get an environment variable
912
 * @param name Environment variable to get
913
 * @retval ptr Value of variable
914
 * @retval NULL Variable isn't set, or is empty
915
 *
916
 * @warning The caller must not free the returned pointer.
917
 */
918
const char *mutt_str_getenv(const char *name)
919
0
{
920
0
  if (!name)
921
0
    return NULL;
922
923
0
  const char *val = getenv(name);
924
0
  if (val && (val[0] != '\0'))
925
0
    return val;
926
927
0
  return NULL;
928
0
}
929
930
/**
931
 * mutt_str_inline_replace - Replace the beginning of a string
932
 * @param buf    Buffer to modify
933
 * @param buflen Length of buffer
934
 * @param xlen   Length of string to overwrite
935
 * @param rstr   Replacement string
936
 * @retval true Success
937
 *
938
 * String (`XX<OOOOOO>......`, 16, 2, `RRRR`) becomes `RRRR<OOOOOO>....`
939
 */
940
bool mutt_str_inline_replace(char *buf, size_t buflen, size_t xlen, const char *rstr)
941
0
{
942
0
  if (!buf || !rstr || (xlen >= buflen))
943
0
    return false;
944
945
0
  size_t slen = mutt_str_len(buf + xlen);
946
0
  size_t rlen = mutt_str_len(rstr);
947
948
0
  if ((slen + rlen) >= buflen)
949
0
    return false;
950
951
0
  memmove(buf + rlen, buf + xlen, slen + 1);
952
0
  memmove(buf, rstr, rlen);
953
954
0
  return true;
955
0
}
956
957
/**
958
 * mutt_istr_remall - Remove all occurrences of substring, ignoring case
959
 * @param str     String containing the substring
960
 * @param target  Target substring for removal
961
 * @retval 0 String contained substring and substring was removed successfully
962
 * @retval 1 String did not contain substring
963
 */
964
int mutt_istr_remall(char *str, const char *target)
965
0
{
966
0
  int rc = 1;
967
0
  if (!str || !target)
968
0
    return rc;
969
970
  // Look through an ensure all instances of the substring are gone.
971
0
  while ((str = (char *) strcasestr(str, target)))
972
0
  {
973
0
    size_t target_len = mutt_str_len(target);
974
0
    memmove(str, str + target_len, 1 + strlen(str + target_len));
975
0
    rc = 0; // If we got here, then a substring existed and has been removed.
976
0
  }
977
978
0
  return rc;
979
0
}
980
981
#ifdef HAVE_VASPRINTF
982
/**
983
 * mutt_str_asprintf - Format a string, allocating space as necessary
984
 * @param[out] strp New string saved here
985
 * @param[in]  fmt  Format string
986
 * @param[in]  ...  Format arguments
987
 * @retval num Characters written
988
 * @retval -1  Error
989
 */
990
int mutt_str_asprintf(char **strp, const char *fmt, ...)
991
0
{
992
0
  if (!strp || !fmt)
993
0
    return -1;
994
995
0
  va_list ap;
996
0
  int n;
997
998
0
  va_start(ap, fmt);
999
0
  n = vasprintf(strp, fmt, ap);
1000
0
  va_end(ap);
1001
1002
  /* GNU libc man page for vasprintf(3) states that the value of *strp
1003
   * is undefined when the return code is -1.  */
1004
0
  if (n < 0)
1005
0
  {
1006
0
    mutt_error(_("Out of memory")); /* LCOV_EXCL_LINE */
1007
0
    mutt_exit(1);                   /* LCOV_EXCL_LINE */
1008
0
  }
1009
1010
0
  if (n == 0)
1011
0
  {
1012
    /* NeoMutt convention is to use NULL for 0-length strings */
1013
0
    FREE(strp); /* LCOV_EXCL_LINE */
1014
0
  }
1015
1016
0
  return n;
1017
0
}
1018
#else
1019
/* Allocate a C-string large enough to contain the formatted string.
1020
 * This is essentially malloc+sprintf in one.
1021
 */
1022
int mutt_str_asprintf(char **strp, const char *fmt, ...)
1023
{
1024
  if (!strp || !fmt)
1025
    return -1;
1026
1027
  int rlen = 256;
1028
1029
  *strp = mutt_mem_malloc(rlen);
1030
  while (true)
1031
  {
1032
    va_list ap;
1033
    va_start(ap, fmt);
1034
    const int n = vsnprintf(*strp, rlen, fmt, ap);
1035
    va_end(ap);
1036
    if (n < 0)
1037
    {
1038
      FREE(strp);
1039
      return n;
1040
    }
1041
1042
    if (n < rlen)
1043
    {
1044
      /* reduce space to just that which was used.  note that 'n' does not
1045
       * include the terminal nul char.  */
1046
      if (n == 0) /* convention is to use NULL for zero-length strings. */
1047
        FREE(strp);
1048
      else if (n != rlen - 1)
1049
        mutt_mem_realloc(strp, n + 1);
1050
      return n;
1051
    }
1052
    /* increase size and try again */
1053
    rlen = n + 1;
1054
    mutt_mem_realloc(strp, rlen);
1055
  }
1056
  /* not reached */
1057
}
1058
#endif /* HAVE_ASPRINTF */
1059
1060
/**
1061
 * mutt_str_hyphenate - Hyphenate a snake-case string
1062
 * @param buf    Buffer for the result
1063
 * @param buflen Length of the buffer
1064
 * @param str    String to convert
1065
 *
1066
 * Replace underscores (`_`) with hyphens -`).
1067
 */
1068
void mutt_str_hyphenate(char *buf, size_t buflen, const char *str)
1069
0
{
1070
0
  if (!buf || (buflen == 0) || !str)
1071
0
    return;
1072
1073
0
  mutt_str_copy(buf, str, buflen);
1074
0
  for (; *buf != '\0'; buf++)
1075
0
  {
1076
0
    if (*buf == '_')
1077
0
      *buf = '-';
1078
0
  }
1079
0
}