NeoMutt  2025-12-11-980-ge38c27
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
string.c
Go to the documentation of this file.
1
28
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
60static 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
86static 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
113{
115 const char *err_str;
116};
117
119static 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
173const char *mutt_str_sysexit(int err_num)
174{
175 for (size_t i = 0; i < countof(SysExits); i++)
176 {
177 if (err_num == SysExits[i].err_num)
178 return SysExits[i].err_str;
179 }
180
181 return NULL;
182}
183
190char *mutt_str_sep(char **stringp, const char *delim)
191{
192 if (!stringp || !*stringp || !delim)
193 return NULL;
194 return strsep(stringp, delim);
195}
196
205static size_t startswith(const char *str, const char *prefix, bool match_case)
206{
207 if (!str || (str[0] == '\0') || !prefix || (prefix[0] == '\0'))
208 {
209 return 0;
210 }
211
212 const char *saved_prefix = prefix;
213 for (; *str && *prefix; str++, prefix++)
214 {
215 if (*str == *prefix)
216 continue;
217
218 if (!match_case && mutt_tolower(*str) == mutt_tolower(*prefix))
219 continue;
220
221 return 0;
222 }
223
224 return (*prefix == '\0') ? (prefix - saved_prefix) : 0;
225}
226
234size_t mutt_str_startswith(const char *str, const char *prefix)
235{
236 return startswith(str, prefix, true);
237}
238
246size_t mutt_istr_startswith(const char *str, const char *prefix)
247{
248 return startswith(str, prefix, false);
249}
250
257char *mutt_str_dup(const char *str)
258{
259 if (!str || (*str == '\0'))
260 return NULL;
261
262 char *p = strdup(str);
263 if (!p)
264 {
265 mutt_error("%s", strerror(errno)); // LCOV_EXCL_LINE
266 mutt_exit(1); // LCOV_EXCL_LINE
267 }
268 return p;
269}
270
284char *mutt_str_replace(char **p, const char *s)
285{
286 if (!p)
287 return NULL;
288 const char *tmp = *p;
289 *p = mutt_str_dup(s);
290 FREE(&tmp);
291 return *p;
292}
293
303void mutt_str_adjust(char **ptr)
304{
305 if (!ptr || !*ptr)
306 return;
307 MUTT_MEM_REALLOC(ptr, strlen(*ptr) + 1, char);
308}
309
317char *mutt_str_lower(char *str)
318{
319 if (!str)
320 return NULL;
321
322 char *p = str;
323
324 while (*p)
325 {
326 *p = mutt_tolower(*p);
327 p++;
328 }
329
330 return str;
331}
332
340char *mutt_str_upper(char *str)
341{
342 if (!str)
343 return NULL;
344
345 char *p = str;
346
347 while (*p)
348 {
349 *p = mutt_toupper(*p);
350 p++;
351 }
352
353 return str;
354}
355
364char *mutt_strn_copy(char *dest, const char *src, size_t len, size_t dsize)
365{
366 if (!src || !dest || (len == 0) || (dsize == 0))
367 return dest;
368
369 if (len > (dsize - 1))
370 len = dsize - 1;
371 memcpy(dest, src, len);
372 dest[len] = '\0';
373 return dest;
374}
375
384char *mutt_strn_dup(const char *begin, size_t len)
385{
386 if (!begin)
387 return NULL;
388
389 char *p = MUTT_MEM_MALLOC(len + 1, char);
390 memcpy(p, begin, len);
391 p[len] = '\0';
392 return p;
393}
394
403int mutt_str_cmp(const char *a, const char *b)
404{
405 return strcmp(NONULL(a), NONULL(b));
406}
407
416int mutt_istr_cmp(const char *a, const char *b)
417{
418 return strcasecmp(NONULL(a), NONULL(b));
419}
420
429bool mutt_strn_equal(const char *a, const char *b, size_t num)
430{
431 return strncmp(NONULL(a), NONULL(b), num) == 0;
432}
433
443int mutt_istrn_cmp(const char *a, const char *b, size_t num)
444{
445 return strncasecmp(NONULL(a), NONULL(b), num);
446}
447
457bool mutt_istrn_equal(const char *a, const char *b, size_t num)
458{
459 return strncasecmp(NONULL(a), NONULL(b), num) == 0;
460}
461
473const char *mutt_istrn_rfind(const char *haystack, size_t haystack_length, const char *needle)
474{
475 if (!haystack || (haystack_length == 0) || !needle)
476 return NULL;
477
478 size_t needle_length = strlen(needle);
479 if (needle_length > haystack_length)
480 return NULL;
481
482 const char *haystack_end = haystack + haystack_length - needle_length;
483
484 for (const char *p = haystack_end; p >= haystack; p--)
485 {
486 for (size_t i = 0; i < needle_length; i++)
487 {
488 if ((mutt_tolower(p[i]) != mutt_tolower(needle[i])))
489 goto next;
490 }
491 return p;
492
493 next:;
494 }
495 return NULL;
496}
497
503size_t mutt_str_len(const char *a)
504{
505 return a ? strlen(a) : 0;
506}
507
516int mutt_str_coll(const char *a, const char *b)
517{
518 return strcoll(NONULL(a), NONULL(b));
519}
520
528const char *mutt_istr_find(const char *haystack, const char *needle)
529{
530 if (!haystack)
531 return NULL;
532 if (!needle)
533 return haystack;
534
535 const char *p = NULL;
536 const char *q = NULL;
537
538 while (*(p = haystack))
539 {
540 for (q = needle; *p && *q && (mutt_tolower(*p) == mutt_tolower(*q)); p++, q++)
541 {
542 }
543 if ((*q == '\0'))
544 return haystack;
545 haystack++;
546 }
547 return NULL;
548}
549
557char *mutt_str_skip_whitespace(const char *p)
558{
559 if (!p)
560 return NULL;
561 SKIPWS(p);
562 return (char *) p;
563}
564
572{
573 if (!s)
574 return;
575
576 for (char *p = s + mutt_str_len(s) - 1; (p >= s) && mutt_isspace(*p); p--)
577 *p = '\0';
578}
579
587size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
588{
589 if (!dest || (dsize == 0))
590 return 0;
591 if (!src)
592 {
593 dest[0] = '\0';
594 return 0;
595 }
596
597 char *dest0 = dest;
598 while ((--dsize > 0) && (*src != '\0'))
599 *dest++ = *src++;
600
601 *dest = '\0';
602 return dest - dest0;
603}
604
614char *mutt_str_skip_email_wsp(const char *s)
615{
616 if (!s)
617 return NULL;
618
619 for (; mutt_str_is_email_wsp(*s); s++)
620 ; // Do nothing
621
622 return (char *) s;
623}
624
634size_t mutt_str_lws_len(const char *s, size_t n)
635{
636 if (!s)
637 return 0;
638
639 const char *p = s;
640 size_t len = n;
641
642 if (n == 0)
643 return 0;
644
645 for (; p < (s + n); p++)
646 {
647 if (!strchr(" \t\r\n", *p))
648 {
649 len = p - s;
650 break;
651 }
652 }
653
654 if ((len != 0) && strchr("\r\n", *(p - 1))) /* LWS doesn't end with CRLF */
655 len = 0;
656 return len;
657}
658
666bool mutt_str_equal(const char *a, const char *b)
667{
668 return (a == b) || (mutt_str_cmp(a, b) == 0);
669}
670
678bool mutt_istr_equal(const char *a, const char *b)
679{
680 return (a == b) || (mutt_istr_cmp(a, b) == 0);
681}
682
689bool mutt_str_is_ascii(const char *str, size_t len)
690{
691 if (!str)
692 return true;
693
694 for (; (*str != '\0') && (len > 0); str++, len--)
695 if ((*str & 0x80) != 0)
696 return false;
697
698 return true;
699}
700
712const char *mutt_str_find_word(const char *src)
713{
714 if (!src)
715 return NULL;
716
717 while (*src && strchr(" \t\n", *src))
718 src++;
719 while (*src && !strchr(" \t\n", *src))
720 src++;
721 return src;
722}
723
732const char *mutt_str_getenv(const char *name)
733{
734 if (!name)
735 return NULL;
736
737 const char *val = getenv(name);
738 if (val && (val[0] != '\0'))
739 return val;
740
741 return NULL;
742}
743
751int mutt_istr_remall(char *str, const char *target)
752{
753 int rc = 1;
754 if (!str || !target)
755 return rc;
756
757 // Look through an ensure all instances of the substring are gone.
758 while ((str = (char *) strcasestr(str, target)))
759 {
760 size_t target_len = mutt_str_len(target);
761 memmove(str, str + target_len, 1 + strlen(str + target_len));
762 rc = 0; // If we got here, then a substring existed and has been removed.
763 }
764
765 return rc;
766}
767
768#ifdef HAVE_VASPRINTF
777int mutt_str_asprintf(char **strp, const char *fmt, ...)
778{
779 if (!strp || !fmt)
780 return -1;
781
782 va_list ap = { 0 };
783 int n;
784
785 va_start(ap, fmt);
786 n = vasprintf(strp, fmt, ap);
787 va_end(ap);
788
789 /* GNU libc man page for vasprintf(3) states that the value of *strp
790 * is undefined when the return code is -1. */
791 if (n < 0)
792 {
793 mutt_error("%s", strerror(errno)); /* LCOV_EXCL_LINE */
794 mutt_exit(1); /* LCOV_EXCL_LINE */
795 }
796
797 if (n == 0)
798 {
799 /* NeoMutt convention is to use NULL for 0-length strings */
800 FREE(strp); /* LCOV_EXCL_LINE */
801 }
802
803 return n;
804}
805#else
806/* Allocate a C-string large enough to contain the formatted string.
807 * This is essentially malloc+sprintf in one.
808 */
809int mutt_str_asprintf(char **strp, const char *fmt, ...)
810{
811 if (!strp || !fmt)
812 return -1;
813
814 int rlen = 256;
815
816 *strp = MUTT_MEM_MALLOC(rlen, char);
817 while (true)
818 {
819 va_list ap;
820 va_start(ap, fmt);
821 const int n = vsnprintf(*strp, rlen, fmt, ap);
822 va_end(ap);
823 if (n < 0)
824 {
825 FREE(strp);
826 return n;
827 }
828
829 if (n < rlen)
830 {
831 /* reduce space to just that which was used. note that 'n' does not
832 * include the terminal nul char. */
833 if (n == 0) /* convention is to use NULL for zero-length strings. */
834 FREE(strp);
835 else if (n != rlen - 1)
836 MUTT_MEM_REALLOC(strp, n + 1, char);
837 return n;
838 }
839 /* increase size and try again */
840 rlen = n + 1;
841 MUTT_MEM_REALLOC(strp, rlen, char);
842 }
843 /* not reached */
844}
845#endif /* HAVE_ASPRINTF */
846
855void mutt_str_hyphenate(char *buf, size_t buflen, const char *str)
856{
857 if (!buf || (buflen == 0) || !str)
858 return;
859
860 mutt_str_copy(buf, str, buflen);
861 for (; *buf != '\0'; buf++)
862 {
863 if (*buf == '_')
864 *buf = '-';
865 }
866}
867
893int mutt_str_inbox_cmp(const char *a, const char *b)
894{
895#define IS_INBOX(s) (mutt_istrn_equal(s, "inbox", 5) && !mutt_isalnum((s)[5]))
896#define CMP_INBOX(a, b) (IS_INBOX(b) - IS_INBOX(a))
897
898 /* fast-track in case the paths have been mutt_pretty_mailbox'ified */
899 if ((a[0] == '+') && (b[0] == '+'))
900 {
901 return CMP_INBOX(a + 1, b + 1);
902 }
903
904 const char *a_end = strrchr(a, '/');
905 const char *b_end = strrchr(b, '/');
906
907 /* If one path contains a '/', but not the other */
908 if ((!a_end) ^ (!b_end))
909 return 0;
910
911 /* If neither path contains a '/' */
912 if (!a_end)
913 return 0;
914
915 /* Compare the subpaths */
916 size_t a_len = a_end - a;
917 size_t b_len = b_end - b;
918 size_t min = MIN(a_len, b_len);
919 int same = (a[min] == '/') && (b[min] == '/') && (a[min + 1] != '\0') &&
920 (b[min + 1] != '\0') && mutt_istrn_equal(a, b, min);
921
922 if (!same)
923 return 0;
924
925 return CMP_INBOX(a + 1 + min, b + 1 + min);
926
927#undef CMP_INBOX
928#undef IS_INBOX
929}
930
937void string_array_clear(struct StringArray *arr)
938{
939 const char **str = NULL;
940 ARRAY_FOREACH(str, arr)
941 {
942 FREE(str);
943 }
944
945 ARRAY_FREE(arr);
946}
Linear Array data structure.
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
#define ARRAY_FREE(head)
Release all memory.
Definition array.h:209
ctype(3) wrapper functions
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition ctype.c:96
int mutt_toupper(int arg)
Wrapper for toupper(3)
Definition ctype.c:140
int mutt_tolower(int arg)
Wrapper for tolower(3)
Definition ctype.c:126
void mutt_exit(int code)
Leave NeoMutt NOW.
Definition exit.c:41
Leave the program NOW.
#define mutt_error(...)
Definition logging2.h:94
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:893
Logging Dispatcher.
Memory management wrappers.
#define countof(x)
Definition memory.h:49
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MIN(a, b)
Return the minimum of two values.
Definition memory.h:40
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:53
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition string.c:384
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition string.c:403
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:443
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition string.c:571
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition string.c:678
static size_t startswith(const char *str, const char *prefix, bool match_case)
Check whether a string starts with a prefix.
Definition string.c:205
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition string.c:809
char * mutt_str_upper(char *str)
Convert all characters in the string to uppercase.
Definition string.c:340
#define CMP_INBOX(a, b)
char * mutt_str_lower(char *str)
Convert all characters in the string to lowercase.
Definition string.c:317
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition string.c:614
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:473
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:634
bool mutt_str_is_ascii(const char *str, size_t len)
Is a string ASCII (7-bit)?
Definition string.c:689
int mutt_istr_cmp(const char *a, const char *b)
Compare two strings ignoring case, safely.
Definition string.c:416
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:666
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:429
static char * strsep(char **stringp, const char *delim)
Extract a token from a string.
Definition string.c:86
const char * mutt_str_find_word(const char *src)
Find the end of a word (non-space)
Definition string.c:712
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:364
int mutt_istr_remall(char *str, const char *target)
Remove all occurrences of substring, ignoring case.
Definition string.c:751
const char * mutt_str_getenv(const char *name)
Get an environment variable.
Definition string.c:732
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition string.c:528
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
void mutt_str_hyphenate(char *buf, size_t buflen, const char *str)
Hyphenate a snake-case string.
Definition string.c:855
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition string.c:557
void mutt_str_adjust(char **ptr)
Shrink-to-fit a string.
Definition string.c:303
void string_array_clear(struct StringArray *arr)
Free all memory of a StringArray.
Definition string.c:937
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
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:587
static char * strcasestr(const char *haystack, const char *needle)
Find the first occurrence of needle in haystack, ignoring case.
Definition string.c:60
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition string.c:246
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:457
const char * mutt_str_sysexit(int err_num)
Return a string matching an error code.
Definition string.c:173
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition string.c:284
int mutt_str_coll(const char *a, const char *b)
Collate two strings (compare using locale), safely.
Definition string.c:516
char * mutt_str_sep(char **stringp, const char *delim)
Find first occurrence of any of delim characters in *stringp.
Definition string.c:190
String manipulation functions.
#define S_ERR
Definition string2.h:47
#define NONULL(x)
Definition string2.h:44
static bool mutt_str_is_email_wsp(char c)
Is this a whitespace character (for an email header)
Definition string2.h:111
#define SKIPWS(ch)
Definition string2.h:52
Lookup table of error messages.
Definition string.c:113
const char * err_str
Human-readable string for error.
Definition string.c:115
int err_num
Error number, see errno(3)
Definition string.c:114