NeoMutt  2025-09-05-29-ga12b18
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
string.c
Go to the documentation of this file.
1
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
58static 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
84static 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
111{
113 const char *err_str;
114};
115
117static 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
171const char *mutt_str_sysexit(int err_num)
172{
173 for (size_t i = 0; i < countof(SysExits); i++)
174 {
175 if (err_num == SysExits[i].err_num)
176 return SysExits[i].err_str;
177 }
178
179 return NULL;
180}
181
188char *mutt_str_sep(char **stringp, const char *delim)
189{
190 if (!stringp || !*stringp || !delim)
191 return NULL;
192 return strsep(stringp, delim);
193}
194
203static size_t startswith(const char *str, const char *prefix, bool match_case)
204{
205 if (!str || (str[0] == '\0') || !prefix || (prefix[0] == '\0'))
206 {
207 return 0;
208 }
209
210 const char *saved_prefix = prefix;
211 for (; *str && *prefix; str++, prefix++)
212 {
213 if (*str == *prefix)
214 continue;
215
216 if (!match_case && mutt_tolower(*str) == mutt_tolower(*prefix))
217 continue;
218
219 return 0;
220 }
221
222 return (*prefix == '\0') ? (prefix - saved_prefix) : 0;
223}
224
232size_t mutt_str_startswith(const char *str, const char *prefix)
233{
234 return startswith(str, prefix, true);
235}
236
244size_t mutt_istr_startswith(const char *str, const char *prefix)
245{
246 return startswith(str, prefix, false);
247}
248
255char *mutt_str_dup(const char *str)
256{
257 if (!str || (*str == '\0'))
258 return NULL;
259
260 char *p = strdup(str);
261 if (!p)
262 {
263 mutt_error("%s", strerror(errno)); // LCOV_EXCL_LINE
264 mutt_exit(1); // LCOV_EXCL_LINE
265 }
266 return p;
267}
268
282char *mutt_str_replace(char **p, const char *s)
283{
284 if (!p)
285 return NULL;
286 const char *tmp = *p;
287 *p = mutt_str_dup(s);
288 FREE(&tmp);
289 return *p;
290}
291
301void mutt_str_adjust(char **ptr)
302{
303 if (!ptr || !*ptr)
304 return;
305 MUTT_MEM_REALLOC(ptr, strlen(*ptr) + 1, char);
306}
307
315char *mutt_str_lower(char *str)
316{
317 if (!str)
318 return NULL;
319
320 char *p = str;
321
322 while (*p)
323 {
324 *p = mutt_tolower(*p);
325 p++;
326 }
327
328 return str;
329}
330
338char *mutt_str_upper(char *str)
339{
340 if (!str)
341 return NULL;
342
343 char *p = str;
344
345 while (*p)
346 {
347 *p = mutt_toupper(*p);
348 p++;
349 }
350
351 return str;
352}
353
362char *mutt_strn_copy(char *dest, const char *src, size_t len, size_t dsize)
363{
364 if (!src || !dest || (len == 0) || (dsize == 0))
365 return dest;
366
367 if (len > (dsize - 1))
368 len = dsize - 1;
369 memcpy(dest, src, len);
370 dest[len] = '\0';
371 return dest;
372}
373
382char *mutt_strn_dup(const char *begin, size_t len)
383{
384 if (!begin)
385 return NULL;
386
387 char *p = MUTT_MEM_MALLOC(len + 1, char);
388 memcpy(p, begin, len);
389 p[len] = '\0';
390 return p;
391}
392
401int mutt_str_cmp(const char *a, const char *b)
402{
403 return strcmp(NONULL(a), NONULL(b));
404}
405
414int mutt_istr_cmp(const char *a, const char *b)
415{
416 return strcasecmp(NONULL(a), NONULL(b));
417}
418
427bool mutt_strn_equal(const char *a, const char *b, size_t num)
428{
429 return strncmp(NONULL(a), NONULL(b), num) == 0;
430}
431
441int mutt_istrn_cmp(const char *a, const char *b, size_t num)
442{
443 return strncasecmp(NONULL(a), NONULL(b), num);
444}
445
455bool mutt_istrn_equal(const char *a, const char *b, size_t num)
456{
457 return strncasecmp(NONULL(a), NONULL(b), num) == 0;
458}
459
471const char *mutt_istrn_rfind(const char *haystack, size_t haystack_length, const char *needle)
472{
473 if (!haystack || (haystack_length == 0) || !needle)
474 return NULL;
475
476 int needle_length = strlen(needle);
477 const char *haystack_end = haystack + haystack_length - needle_length;
478
479 for (const char *p = haystack_end; p >= haystack; p--)
480 {
481 for (size_t i = 0; i < needle_length; i++)
482 {
483 if ((mutt_tolower(p[i]) != mutt_tolower(needle[i])))
484 goto next;
485 }
486 return p;
487
488 next:;
489 }
490 return NULL;
491}
492
498size_t mutt_str_len(const char *a)
499{
500 return a ? strlen(a) : 0;
501}
502
511int mutt_str_coll(const char *a, const char *b)
512{
513 return strcoll(NONULL(a), NONULL(b));
514}
515
523const char *mutt_istr_find(const char *haystack, const char *needle)
524{
525 if (!haystack)
526 return NULL;
527 if (!needle)
528 return haystack;
529
530 const char *p = NULL, *q = NULL;
531
532 while (*(p = haystack))
533 {
534 for (q = needle; *p && *q && (mutt_tolower(*p) == mutt_tolower(*q)); p++, q++)
535 {
536 }
537 if ((*q == '\0'))
538 return haystack;
539 haystack++;
540 }
541 return NULL;
542}
543
551char *mutt_str_skip_whitespace(const char *p)
552{
553 if (!p)
554 return NULL;
555 SKIPWS(p);
556 return (char *) p;
557}
558
566{
567 if (!s)
568 return;
569
570 for (char *p = s + mutt_str_len(s) - 1; (p >= s) && mutt_isspace(*p); p--)
571 *p = '\0';
572}
573
581size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
582{
583 if (!dest || (dsize == 0))
584 return 0;
585 if (!src)
586 {
587 dest[0] = '\0';
588 return 0;
589 }
590
591 char *dest0 = dest;
592 while ((--dsize > 0) && (*src != '\0'))
593 *dest++ = *src++;
594
595 *dest = '\0';
596 return dest - dest0;
597}
598
608char *mutt_str_skip_email_wsp(const char *s)
609{
610 if (!s)
611 return NULL;
612
613 for (; mutt_str_is_email_wsp(*s); s++)
614 ; // Do nothing
615
616 return (char *) s;
617}
618
628size_t mutt_str_lws_len(const char *s, size_t n)
629{
630 if (!s)
631 return 0;
632
633 const char *p = s;
634 size_t len = n;
635
636 if (n == 0)
637 return 0;
638
639 for (; p < (s + n); p++)
640 {
641 if (!strchr(" \t\r\n", *p))
642 {
643 len = p - s;
644 break;
645 }
646 }
647
648 if ((len != 0) && strchr("\r\n", *(p - 1))) /* LWS doesn't end with CRLF */
649 len = 0;
650 return len;
651}
652
660bool mutt_str_equal(const char *a, const char *b)
661{
662 return (a == b) || (mutt_str_cmp(a, b) == 0);
663}
664
672bool mutt_istr_equal(const char *a, const char *b)
673{
674 return (a == b) || (mutt_istr_cmp(a, b) == 0);
675}
676
683bool mutt_str_is_ascii(const char *str, size_t len)
684{
685 if (!str)
686 return true;
687
688 for (; (*str != '\0') && (len > 0); str++, len--)
689 if ((*str & 0x80) != 0)
690 return false;
691
692 return true;
693}
694
706const char *mutt_str_find_word(const char *src)
707{
708 if (!src)
709 return NULL;
710
711 while (*src && strchr(" \t\n", *src))
712 src++;
713 while (*src && !strchr(" \t\n", *src))
714 src++;
715 return src;
716}
717
726const char *mutt_str_getenv(const char *name)
727{
728 if (!name)
729 return NULL;
730
731 const char *val = getenv(name);
732 if (val && (val[0] != '\0'))
733 return val;
734
735 return NULL;
736}
737
745int mutt_istr_remall(char *str, const char *target)
746{
747 int rc = 1;
748 if (!str || !target)
749 return rc;
750
751 // Look through an ensure all instances of the substring are gone.
752 while ((str = (char *) strcasestr(str, target)))
753 {
754 size_t target_len = mutt_str_len(target);
755 memmove(str, str + target_len, 1 + strlen(str + target_len));
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
771int mutt_str_asprintf(char **strp, const char *fmt, ...)
772{
773 if (!strp || !fmt)
774 return -1;
775
776 va_list ap;
777 int n;
778
779 va_start(ap, fmt);
780 n = vasprintf(strp, fmt, ap);
781 va_end(ap);
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)
786 {
787 mutt_error("%s", strerror(errno)); /* LCOV_EXCL_LINE */
788 mutt_exit(1); /* LCOV_EXCL_LINE */
789 }
790
791 if (n == 0)
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 */
803int 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
849void mutt_str_hyphenate(char *buf, size_t buflen, const char *str)
850{
851 if (!buf || (buflen == 0) || !str)
852 return;
853
854 mutt_str_copy(buf, str, buflen);
855 for (; *buf != '\0'; buf++)
856 {
857 if (*buf == '_')
858 *buf = '-';
859 }
860}
861
887int mutt_str_inbox_cmp(const char *a, const char *b)
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] == '+'))
894 {
895 return CMP_INBOX(a + 1, b + 1);
896 }
897
898 const char *a_end = strrchr(a, '/');
899 const char *b_end = strrchr(b, '/');
900
901 /* If one path contains a '/', but not the other */
902 if ((!a_end) ^ (!b_end))
903 return 0;
904
905 /* If neither path contains a '/' */
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') &&
914 (b[min + 1] != '\0') && mutt_istrn_equal(a, b, min);
915
916 if (!same)
917 return 0;
918
919 return CMP_INBOX(a + 1 + min, b + 1 + min);
920
921#undef CMP_INBOX
922#undef IS_INBOX
923}
924
931void string_array_clear(struct StringArray *arr)
932{
933 const char **str = NULL;
934 ARRAY_FOREACH(str, arr)
935 {
936 FREE(str);
937 }
938
939 ARRAY_FREE(arr);
940}
Linear Array data structure.
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:214
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:204
ctype(3) wrapper functions
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition: ctype.c:95
int mutt_toupper(int arg)
Wrapper for toupper(3)
Definition: ctype.c:139
int mutt_tolower(int arg)
Wrapper for tolower(3)
Definition: ctype.c:125
void mutt_exit(int code)
Leave NeoMutt NOW.
Definition: exit.c:41
Leave the program NOW.
#define mutt_error(...)
Definition: logging2.h:93
int mutt_str_inbox_cmp(const char *a, const char *b)
Do two folders share the same path and one is an inbox -.
Definition: string.c:887
Logging Dispatcher.
Memory management wrappers.
#define countof(x)
Definition: memory.h:44
#define FREE(x)
Definition: memory.h:62
#define MIN(a, b)
Definition: memory.h:37
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:50
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:48
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition: string.c:382
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:401
int mutt_istrn_cmp(const char *a, const char *b, size_t num)
Compare two strings ignoring case (to a maximum), safely.
Definition: string.c:441
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:565
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:672
static size_t startswith(const char *str, const char *prefix, bool match_case)
Check whether a string starts with a prefix.
Definition: string.c:203
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:255
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:803
char * mutt_str_upper(char *str)
Convert all characters in the string to uppercase.
Definition: string.c:338
#define CMP_INBOX(a, b)
char * mutt_str_lower(char *str)
Convert all characters in the string to lowercase.
Definition: string.c:315
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:608
const char * mutt_istrn_rfind(const char *haystack, size_t haystack_length, const char *needle)
Find last instance of a substring, ignoring case.
Definition: string.c:471
size_t mutt_str_lws_len(const char *s, size_t n)
Measure the linear-white-space at the beginning of a string.
Definition: string.c:628
bool mutt_str_is_ascii(const char *str, size_t len)
Is a string ASCII (7-bit)?
Definition: string.c:683
int mutt_istr_cmp(const char *a, const char *b)
Compare two strings ignoring case, safely.
Definition: string.c:414
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:427
static char * strsep(char **stringp, const char *delim)
Extract a token from a string.
Definition: string.c:84
const char * mutt_str_find_word(const char *src)
Find the end of a word (non-space)
Definition: string.c:706
char * mutt_strn_copy(char *dest, const char *src, size_t len, size_t dsize)
Copy a sub-string into a buffer.
Definition: string.c:362
int mutt_istr_remall(char *str, const char *target)
Remove all occurrences of substring, ignoring case.
Definition: string.c:745
const char * mutt_str_getenv(const char *name)
Get an environment variable.
Definition: string.c:726
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:523
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:232
void mutt_str_hyphenate(char *buf, size_t buflen, const char *str)
Hyphenate a snake-case string.
Definition: string.c:849
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:551
void mutt_str_adjust(char **ptr)
Shrink-to-fit a string.
Definition: string.c:301
void string_array_clear(struct StringArray *arr)
Free all memory of a StringArray.
Definition: string.c:931
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:498
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:581
static char * strcasestr(const char *haystack, const char *needle)
Find the first occurrence of needle in haystack, ignoring case.
Definition: string.c:58
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:244
bool mutt_istrn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings ignoring case (to a maximum), safely.
Definition: string.c:455
const char * mutt_str_sysexit(int err_num)
Return a string matching an error code.
Definition: string.c:171
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:282
int mutt_str_coll(const char *a, const char *b)
Collate two strings (compare using locale), safely.
Definition: string.c:511
char * mutt_str_sep(char **stringp, const char *delim)
Find first occurrence of any of delim characters in *stringp.
Definition: string.c:188
String manipulation functions.
#define S_ERR
Definition: string2.h:46
#define NONULL(x)
Definition: string2.h:43
static bool mutt_str_is_email_wsp(char c)
Is this a whitespace character (for an email header)
Definition: string2.h:110
#define SKIPWS(ch)
Definition: string2.h:51
Lookup table of error messages.
Definition: string.c:111
const char * err_str
Human-readable string for error.
Definition: string.c:113
int err_num
Error number, see errno(3)
Definition: string.c:112