/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 | } |