3 * Some miscellaneous functions
6 * Copyright (C) 1996-2000,2007,2010,2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1999-2008 Thomas Roessler <roessler@does-not-exist.org>
8 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
11 * This program is free software: you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License as published by the Free Software
13 * Foundation, either version 2 of the License, or (at your option) any later
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
21 * You should have received a copy of the GNU General Public License along with
22 * this program. If not, see <http://www.gnu.org/licenses/>.
26 * @page muttlib Some miscellaneous functions
28 * Some miscellaneous functions
45 #include "mutt/mutt.h"
46 #include "address/lib.h"
47 #include "config/lib.h"
48 #include "email/lib.h"
55 #include "format_flags.h"
58 #include "mutt_window.h"
60 #include "ncrypt/ncrypt.h"
62 #if defined(HAVE_SYSCALL_H)
64 #elif defined(HAVE_SYS_SYSCALL_H)
65 #include <sys/syscall.h>
68 #include "imap/imap.h"
71 /* These Config Variables are only used in muttlib.c */
72 struct Regex *C_GecosMask; ///< Config: Regex for parsing GECOS field of /etc/passwd
74 static const char *xdg_env_vars[] = {
75 [XDG_CONFIG_HOME] = "XDG_CONFIG_HOME",
76 [XDG_CONFIG_DIRS] = "XDG_CONFIG_DIRS",
79 static const char *xdg_defaults[] = {
80 [XDG_CONFIG_HOME] = "~/.config",
81 [XDG_CONFIG_DIRS] = "/etc/xdg",
85 * mutt_adv_mktemp - Create a temporary file
86 * @param buf Buffer for the name
88 * Accept a "suggestion" for file name. If that file exists, then
89 * construct one with unique name but keep any extension.
90 * This might fail, I guess.
92 void mutt_adv_mktemp(struct Buffer *buf)
94 if (!(buf->data && buf->data[0]))
96 mutt_buffer_mktemp(buf);
100 struct Buffer *prefix = mutt_buffer_pool_get();
101 mutt_buffer_strcpy(prefix, buf->data);
102 mutt_file_sanitize_filename(prefix->data, true);
103 mutt_buffer_printf(buf, "%s/%s", NONULL(C_Tmpdir), mutt_b2s(prefix));
106 if ((lstat(mutt_b2s(buf), &sb) == -1) && (errno == ENOENT))
109 char *suffix = strchr(prefix->data, '.');
115 mutt_buffer_mktemp_pfx_sfx(buf, prefix->data, suffix);
118 mutt_buffer_pool_release(&prefix);
123 * mutt_expand_path - Create the canonical path
124 * @param buf Buffer with path
125 * @param buflen Length of buffer
126 * @retval ptr The expanded string
128 * @note The path is expanded in-place
130 char *mutt_expand_path(char *buf, size_t buflen)
132 return mutt_expand_path_regex(buf, buflen, false);
136 * mutt_buffer_expand_path_regex - Create the canonical path (with regex char escaping)
137 * @param buf Buffer with path
138 * @param regex If true, escape any regex characters
140 * @note The path is expanded in-place
142 void mutt_buffer_expand_path_regex(struct Buffer *buf, bool regex)
144 const char *s = NULL;
145 const char *tail = "";
147 bool recurse = false;
149 struct Buffer *p = mutt_buffer_pool_get();
150 struct Buffer *q = mutt_buffer_pool_get();
151 struct Buffer *tmp = mutt_buffer_pool_get();
162 if ((s[1] == '/') || (s[1] == '\0'))
164 mutt_buffer_strcpy(p, HomeDir);
169 char *t = strchr(s + 1, '/');
173 struct passwd *pw = getpwnam(s + 1);
176 mutt_buffer_strcpy(p, pw->pw_dir);
187 /* user not found! */
190 mutt_buffer_reset(p);
200 enum MailboxType mb_type = mx_path_probe(C_Folder, NULL);
202 /* if folder = {host} or imap[s]://host/: don't append slash */
203 if ((mb_type == MUTT_IMAP) && ((C_Folder[strlen(C_Folder) - 1] == '}') ||
204 (C_Folder[strlen(C_Folder) - 1] == '/')))
206 mutt_buffer_strcpy(p, NONULL(C_Folder));
208 else if (mb_type == MUTT_NOTMUCH)
209 mutt_buffer_strcpy(p, NONULL(C_Folder));
210 else if (C_Folder && (C_Folder[strlen(C_Folder) - 1] == '/'))
211 mutt_buffer_strcpy(p, NONULL(C_Folder));
213 mutt_buffer_printf(p, "%s/", NONULL(C_Folder));
219 /* elm compatibility, @ expands alias to user name */
223 struct AddressList *al = mutt_alias_lookup(s + 1);
224 if (!TAILQ_EMPTY(al))
226 struct Email *e = email_new();
227 e->env = mutt_env_new();
228 mutt_addrlist_copy(&e->env->from, al, false);
229 mutt_addrlist_copy(&e->env->to, al, false);
231 /* TODO: fix mutt_default_save() to use Buffer */
232 mutt_buffer_alloc(p, PATH_MAX);
233 mutt_default_save(p->data, p->dsize, e);
234 mutt_buffer_fix_dptr(p);
237 /* Avoid infinite recursion if the resulting folder starts with '@' */
248 mutt_buffer_strcpy(p, C_Mbox);
255 mutt_buffer_strcpy(p, C_Record);
264 mutt_buffer_strcpy(p, LastFolder);
269 mutt_buffer_strcpy(p, C_Spoolfile);
277 mutt_buffer_strcpy(p, LastFolder);
284 mutt_buffer_strcpy(p, CurrentFolder);
291 mutt_buffer_reset(p);
296 if (regex && *(mutt_b2s(p)) && !recurse)
298 mutt_file_sanitize_regex(q, mutt_b2s(p));
299 mutt_buffer_printf(tmp, "%s%s", mutt_b2s(q), tail);
302 mutt_buffer_printf(tmp, "%s%s", mutt_b2s(p), tail);
304 mutt_buffer_strcpy(buf, mutt_b2s(tmp));
307 mutt_buffer_pool_release(&p);
308 mutt_buffer_pool_release(&q);
309 mutt_buffer_pool_release(&tmp);
312 /* Rewrite IMAP path in canonical form - aids in string comparisons of
313 * folders. May possibly fail, in which case buf should be the same. */
314 if (imap_path_probe(mutt_b2s(buf), NULL) == MUTT_IMAP)
315 imap_expand_path(buf);
320 * mutt_buffer_expand_path - Create the canonical path
321 * @param buf Buffer with path
323 * @note The path is expanded in-place
325 void mutt_buffer_expand_path(struct Buffer *buf)
327 mutt_buffer_expand_path_regex(buf, false);
331 * mutt_expand_path_regex - Create the canonical path (with regex char escaping)
332 * @param buf Buffer with path
333 * @param buflen Length of buffer
334 * @param regex If true, escape any regex characters
335 * @retval ptr The expanded string
337 * @note The path is expanded in-place
339 char *mutt_expand_path_regex(char *buf, size_t buflen, bool regex)
341 struct Buffer *tmp = mutt_buffer_pool_get();
343 mutt_buffer_addstr(tmp, NONULL(buf));
344 mutt_buffer_expand_path_regex(tmp, regex);
345 mutt_str_strfcpy(buf, mutt_b2s(tmp), buflen);
347 mutt_buffer_pool_release(&tmp);
353 * mutt_gecos_name - Lookup a user's real name in /etc/passwd
354 * @param dest Buffer for the result
355 * @param destlen Length of buffer
356 * @param pw Passwd entry
357 * @retval ptr Result buffer on success
359 * Extract the real name from /etc/passwd's GECOS field. When set, honor the
360 * regular expression in #C_GecosMask, otherwise assume that the GECOS field is a
361 * comma-separated list.
362 * Replace "&" by a capitalized version of the user's login name.
364 char *mutt_gecos_name(char *dest, size_t destlen, struct passwd *pw)
366 regmatch_t pat_match[1];
370 if (!pw || !pw->pw_gecos)
373 memset(dest, 0, destlen);
375 if (mutt_regex_capture(C_GecosMask, pw->pw_gecos, 1, pat_match))
377 mutt_str_strfcpy(dest, pw->pw_gecos + pat_match[0].rm_so,
378 MIN(pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen));
380 else if ((p = strchr(pw->pw_gecos, ',')))
381 mutt_str_strfcpy(dest, pw->pw_gecos, MIN(destlen, p - pw->pw_gecos + 1));
383 mutt_str_strfcpy(dest, pw->pw_gecos, destlen);
385 pwnl = strlen(pw->pw_name);
387 for (int idx = 0; dest[idx]; idx++)
389 if (dest[idx] == '&')
391 memmove(&dest[idx + pwnl], &dest[idx + 1],
392 MAX((ssize_t)(destlen - idx - pwnl - 1), 0));
393 memcpy(&dest[idx], pw->pw_name, MIN(destlen - idx - 1, pwnl));
394 dest[idx] = toupper((unsigned char) dest[idx]);
402 * mutt_needs_mailcap - Does this type need a mailcap entry do display
403 * @param m Attachment body to be displayed
404 * @retval true NeoMutt requires a mailcap entry to display
405 * @retval false otherwise
407 bool mutt_needs_mailcap(struct Body *m)
412 if (mutt_str_strcasecmp("plain", m->subtype) == 0)
415 case TYPE_APPLICATION:
416 if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(m))
418 if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(m))
431 * mutt_is_text_part - Is this part of an email in plain text?
432 * @param b Part of an email
433 * @retval true If part is in plain text
435 bool mutt_is_text_part(struct Body *b)
438 char *s = b->subtype;
440 if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
446 if (t == TYPE_MESSAGE)
448 if (mutt_str_strcasecmp("delivery-status", s) == 0)
452 if (((WithCrypto & APPLICATION_PGP) != 0) && (t == TYPE_APPLICATION))
454 if (mutt_str_strcasecmp("pgp-keys", s) == 0)
461 static FILE *fp_random;
464 * mutt_randbuf - Fill a buffer with randomness
465 * @param buf Buffer for result
466 * @param buflen Size of buffer
470 int mutt_randbuf(void *buf, size_t buflen)
472 if (buflen > 1048576)
474 mutt_error(_("mutt_randbuf buflen=%zu"), buflen);
477 /* XXX switch to HAVE_GETRANDOM and getrandom() in about 2017 */
478 #if defined(SYS_getrandom) && defined(__linux__)
482 ret = syscall(SYS_getrandom, buf, buflen, 0, 0, 0, 0);
483 } while ((ret == -1) && (errno == EINTR));
487 /* let's try urandom in case we're on an old kernel, or the user has
488 * configured selinux, seccomp or something to not allow getrandom */
491 fp_random = fopen("/dev/urandom", "rb");
494 mutt_error(_("open /dev/urandom: %s"), strerror(errno));
497 setbuf(fp_random, NULL);
499 if (fread(buf, 1, buflen, fp_random) != buflen)
501 mutt_error(_("read /dev/urandom: %s"), strerror(errno));
508 static const unsigned char base32[] = "abcdefghijklmnopqrstuvwxyz234567";
511 * mutt_rand_base32 - Fill a buffer with a base32-encoded random string
512 * @param buf Buffer for result
513 * @param buflen Length of buffer
515 void mutt_rand_base32(void *buf, size_t buflen)
519 if (mutt_randbuf(p, buflen) < 0)
521 for (size_t pos = 0; pos < buflen; pos++)
522 p[pos] = base32[p[pos] % 32];
526 * mutt_rand32 - Create a 32-bit random number
527 * @retval num Random number
529 uint32_t mutt_rand32(void)
533 if (mutt_randbuf(&num, sizeof(num)) < 0)
539 * mutt_rand64 - Create a 64-bit random number
540 * @retval num Random number
542 uint64_t mutt_rand64(void)
546 if (mutt_randbuf(&num, sizeof(num)) < 0)
552 * mutt_buffer_mktemp_full - Create a temporary file
553 * @param buf Buffer for result
554 * @param prefix Prefix for filename
555 * @param suffix Suffix for filename
556 * @param src Source file of caller
557 * @param line Source line number of caller
559 void mutt_buffer_mktemp_full(struct Buffer *buf, const char *prefix,
560 const char *suffix, const char *src, int line)
562 mutt_buffer_printf(buf, "%s/%s-%s-%d-%d-%" PRIu64 "%s%s", NONULL(C_Tmpdir),
563 NONULL(prefix), NONULL(ShortHostname), (int) getuid(),
564 (int) getpid(), mutt_rand64(), suffix ? "." : "", NONULL(suffix));
566 mutt_debug(LL_DEBUG3, "%s:%d: mutt_mktemp returns \"%s\"\n", src, line, mutt_b2s(buf));
567 if (unlink(mutt_b2s(buf)) && (errno != ENOENT))
569 mutt_debug(LL_DEBUG1, "%s:%d: ERROR: unlink(\"%s\"): %s (errno %d)\n", src,
570 line, mutt_b2s(buf), strerror(errno), errno);
575 * mutt_mktemp_full - Create a temporary filename
576 * @param buf Buffer for result
577 * @param buflen Length of buffer
578 * @param prefix Prefix for filename
579 * @param suffix Suffix for filename
580 * @param src Source file of caller
581 * @param line Source line number of caller
583 * @note This doesn't create the file, only the name
585 void mutt_mktemp_full(char *buf, size_t buflen, const char *prefix,
586 const char *suffix, const char *src, int line)
589 snprintf(buf, buflen, "%s/%s-%s-%d-%d-%" PRIu64 "%s%s", NONULL(C_Tmpdir),
590 NONULL(prefix), NONULL(ShortHostname), (int) getuid(),
591 (int) getpid(), mutt_rand64(), suffix ? "." : "", NONULL(suffix));
594 mutt_debug(LL_DEBUG1,
595 "%s:%d: ERROR: insufficient buffer space to hold temporary "
596 "filename! buflen=%zu but need %zu\n",
597 src, line, buflen, n);
599 mutt_debug(LL_DEBUG3, "%s:%d: mutt_mktemp returns \"%s\"\n", src, line, buf);
600 if (unlink(buf) && (errno != ENOENT))
602 mutt_debug(LL_DEBUG1, "%s:%d: ERROR: unlink(\"%s\"): %s (errno %d)\n", src,
603 line, buf, strerror(errno), errno);
608 * mutt_pretty_mailbox - Shorten a mailbox path using '~' or '='
609 * @param buf Buffer containing string to shorten
610 * @param buflen Length of buffer
612 * Collapse the pathname using ~ or = when possible
614 void mutt_pretty_mailbox(char *buf, size_t buflen)
619 char *p = buf, *q = buf;
621 enum UrlScheme scheme;
624 scheme = url_check_scheme(buf);
626 if ((scheme == U_IMAP) || (scheme == U_IMAPS))
628 imap_pretty_mailbox(buf, buflen, C_Folder);
632 if (scheme == U_NOTMUCH)
635 /* if buf is an url, only collapse path component */
636 if (scheme != U_UNKNOWN)
638 p = strchr(buf, ':') + 1;
639 if (strncmp(p, "//", 2) == 0)
640 q = strchr(p + 2, '/');
647 if (strstr(p, "//") || strstr(p, "/./"))
649 /* first attempt to collapse the pathname, this is more
650 * lightweight than realpath() and doesn't resolve links */
653 if ((p[0] == '/') && (p[1] == '/'))
658 else if ((p[0] == '/') && (p[1] == '.') && (p[2] == '/'))
668 else if (strstr(p, "..") && ((scheme == U_UNKNOWN) || (scheme == U_FILE)) &&
671 mutt_str_strfcpy(p, tmp, buflen - (p - buf));
674 if ((len = mutt_str_startswith(buf, C_Folder, CASE_MATCH)) && (buf[len] == '/'))
677 memmove(buf, buf + len, mutt_str_strlen(buf + len) + 1);
679 else if ((len = mutt_str_startswith(buf, HomeDir, CASE_MATCH)) && (buf[len] == '/'))
682 memmove(buf, buf + len - 1, mutt_str_strlen(buf + len - 1) + 1);
687 * mutt_buffer_pretty_mailbox - Shorten a mailbox path using '~' or '='
688 * @param buf Buffer containing Mailbox name
690 void mutt_buffer_pretty_mailbox(struct Buffer *buf)
692 if (!buf || !buf->data)
694 /* This reduces the size of the Buffer, so we can pass it through.
695 * We adjust the size just to make sure buf->data is not NULL though */
696 mutt_buffer_alloc(buf, PATH_MAX);
697 mutt_pretty_mailbox(buf->data, buf->dsize);
698 mutt_buffer_fix_dptr(buf);
702 * mutt_check_overwrite - Ask the user if overwriting is necessary
703 * @param[in] attname Attachment name
704 * @param[in] path Path to save the file
705 * @param[out] fname Buffer for filename
706 * @param[out] opt Save option, see #SaveAttach
707 * @param[out] directory Directory to save under (OPTIONAL)
712 int mutt_check_overwrite(const char *attname, const char *path, struct Buffer *fname,
713 enum SaveAttach *opt, char **directory)
717 mutt_buffer_strcpy(fname, path);
718 if (access(mutt_b2s(fname), F_OK) != 0)
720 if (stat(mutt_b2s(fname), &st) != 0)
722 if (S_ISDIR(st.st_mode))
724 enum QuadOption ans = MUTT_NO;
727 switch (mutt_multi_choice
728 /* L10N: Means "The path you specified as the destination file is a directory."
729 See the msgid "Save to file: " (alias.c, recvattach.c)
730 These three letters correspond to the choices in the string. */
731 (_("File is a directory, save under it: (y)es, (n)o, (a)ll?"), _("yna")))
734 mutt_str_replace(directory, mutt_b2s(fname));
747 /* L10N: Means "The path you specified as the destination file is a directory."
748 See the msgid "Save to file: " (alias.c, recvattach.c) */
749 else if ((ans = mutt_yesorno(_("File is a directory, save under it?"), MUTT_YES)) != MUTT_YES)
750 return (ans == MUTT_NO) ? 1 : -1;
752 struct Buffer *tmp = mutt_buffer_pool_get();
753 mutt_buffer_strcpy(tmp, mutt_path_basename(NONULL(attname)));
754 if ((mutt_buffer_get_field(_("File under directory: "), tmp, MUTT_FILE | MUTT_CLEAR) != 0) ||
755 mutt_buffer_is_empty(tmp))
757 mutt_buffer_pool_release(&tmp);
760 mutt_buffer_concat_path(fname, path, mutt_b2s(tmp));
761 mutt_buffer_pool_release(&tmp);
764 if ((*opt == MUTT_SAVE_NO_FLAGS) && (access(mutt_b2s(fname), F_OK) == 0))
767 mutt_multi_choice(_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"),
768 // L10N: Options for: File exists, (o)verwrite, (a)ppend, or (c)ancel?
777 *opt = MUTT_SAVE_APPEND;
779 case 1: /* overwrite */
780 *opt = MUTT_SAVE_OVERWRITE;
788 * mutt_save_path - Turn an email address into a filename (for saving)
789 * @param buf Buffer for the result
790 * @param buflen Length of buffer
791 * @param addr Email address to use
793 * If the user hasn't set `$save_address` the name will be truncated to the '@'
796 void mutt_save_path(char *buf, size_t buflen, const struct Address *addr)
798 if (addr && addr->mailbox)
800 mutt_str_strfcpy(buf, addr->mailbox, buflen);
803 char *p = strpbrk(buf, "%@");
807 mutt_str_strlower(buf);
814 * mutt_buffer_save_path - Make a safe filename from an email address
815 * @param dest Buffer for the result
816 * @param a Address to use
818 void mutt_buffer_save_path(struct Buffer *dest, const struct Address *a)
822 mutt_buffer_strcpy(dest, a->mailbox);
825 char *p = strpbrk(dest->data, "%@");
829 mutt_buffer_fix_dptr(dest);
832 mutt_str_strlower(dest->data);
835 mutt_buffer_reset(dest);
839 * mutt_safe_path - Make a safe filename from an email address
840 * @param dest Buffer for the result
841 * @param a Address to use
843 * The filename will be stripped of '/', space, etc to make it safe.
845 void mutt_safe_path(struct Buffer *dest, const struct Address *a)
847 mutt_buffer_save_path(dest, a);
848 for (char *p = dest->data; *p; p++)
849 if ((*p == '/') || IS_SPACE(*p) || !IsPrint((unsigned char) *p))
854 * mutt_expando_format - Expand expandos (%x) in a string
855 * @param[out] buf Buffer in which to save string
856 * @param[in] buflen Buffer length
857 * @param[in] col Starting column
858 * @param[in] cols Number of screen columns
859 * @param[in] src Printf-like format string
860 * @param[in] callback Callback - Implements ::format_t
861 * @param[in] data Callback data
862 * @param[in] flags Callback flags
864 void mutt_expando_format(char *buf, size_t buflen, size_t col, int cols, const char *src,
865 format_t *callback, unsigned long data, MuttFormatFlags flags)
867 char prefix[128], tmp[1024];
868 char *cp = NULL, *wptr = buf;
870 char if_str[128], else_str[128];
871 size_t wlen, count, len, wid;
872 FILE *fp_filter = NULL;
873 char *recycler = NULL;
876 mutt_str_strfcpy(src2, src, mutt_str_strlen(src) + 1);
880 buflen--; /* save room for the terminal \0 */
881 wlen = ((flags & MUTT_FORMAT_ARROWCURSOR) && C_ArrowCursor) ? 3 : 0;
884 if ((flags & MUTT_FORMAT_NOFILTER) == 0)
888 /* Do not consider filters if no pipe at end */
889 int n = mutt_str_strlen(src);
890 if ((n > 1) && (src[n - 1] == '|'))
892 /* Scan backwards for backslashes */
894 while ((off > 0) && (src[off - 2] == '\\'))
898 /* If number of backslashes is even, the pipe is real. */
899 /* n-off is the number of backslashes. */
900 if ((off > 0) && (((n - off) % 2) == 0))
905 mutt_debug(LL_DEBUG3, "fmtpipe = %s\n", src);
907 strncpy(srccopy, src, n);
908 srccopy[n - 1] = '\0';
910 /* prepare Buffers */
911 struct Buffer srcbuf = mutt_buffer_make(0);
912 mutt_buffer_addstr(&srcbuf, srccopy);
913 /* note: we are resetting dptr and *reading* from the buffer, so we don't
914 * want to use mutt_buffer_reset(). */
915 srcbuf.dptr = srcbuf.data;
916 struct Buffer word = mutt_buffer_make(0);
917 struct Buffer cmd = mutt_buffer_make(0);
919 /* Iterate expansions across successive arguments */
922 /* Extract the command name and copy to command line */
923 mutt_debug(LL_DEBUG3, "fmtpipe +++: %s\n", srcbuf.dptr);
926 mutt_extract_token(&word, &srcbuf, MUTT_TOKEN_NO_FLAGS);
927 mutt_debug(LL_DEBUG3, "fmtpipe %2d: %s\n", i++, word.data);
928 mutt_buffer_addch(&cmd, '\'');
929 mutt_expando_format(tmp, sizeof(tmp), 0, cols, word.data, callback,
930 data, flags | MUTT_FORMAT_NOFILTER);
931 for (char *p = tmp; p && *p; p++)
935 /* shell quoting doesn't permit escaping a single quote within
936 * single-quoted material. double-quoting instead will lead
937 * shell variable expansions, so break out of the single-quoted
938 * span, insert a double-quoted single quote, and resume. */
939 mutt_buffer_addstr(&cmd, "'\"'\"'");
942 mutt_buffer_addch(&cmd, *p);
944 mutt_buffer_addch(&cmd, '\'');
945 mutt_buffer_addch(&cmd, ' ');
946 } while (MoreArgs(&srcbuf));
948 mutt_debug(LL_DEBUG3, "fmtpipe > %s\n", cmd.data);
950 col -= wlen; /* reset to passed in value */
951 wptr = buf; /* reset write ptr */
952 pid_t pid = mutt_create_filter(cmd.data, NULL, &fp_filter, NULL);
957 n = fread(buf, 1, buflen /* already decremented */, fp_filter);
958 mutt_file_fclose(&fp_filter);
959 rc = mutt_wait_filter(pid);
961 mutt_debug(LL_DEBUG1, "format pipe cmd exited code %d\n", rc);
965 while ((n > 0) && ((buf[n - 1] == '\n') || (buf[n - 1] == '\r')))
967 mutt_debug(LL_DEBUG5, "fmtpipe < %s\n", buf);
969 /* If the result ends with '%', this indicates that the filter
970 * generated %-tokens that neomutt can expand. Eliminate the '%'
971 * marker and recycle the string through mutt_expando_format().
972 * To literally end with "%", use "%%". */
973 if ((n > 0) && (buf[n - 1] == '%'))
976 buf[n] = '\0'; /* remove '%' */
977 if ((n > 0) && (buf[n - 1] != '%'))
979 recycler = mutt_str_strdup(buf);
982 /* buflen is decremented at the start of this function
983 * to save space for the terminal nul char. We can add
984 * it back for the recursive call since the expansion of
985 * format pipes does not try to append a nul itself. */
986 mutt_expando_format(buf, buflen + 1, col, cols, recycler,
987 callback, data, flags);
996 mutt_debug(LL_DEBUG1, "error reading from fmtpipe: %s (errno=%d)\n",
997 strerror(errno), errno);
1003 /* Filter failed; erase write buffer */
1007 mutt_buffer_dealloc(&cmd);
1008 mutt_buffer_dealloc(&srcbuf);
1009 mutt_buffer_dealloc(&word);
1014 while (*src && (wlen < buflen))
1029 /* change original %? to new %< notation */
1030 /* %?x?y&z? to %<x?y&z> where y and z are nestable */
1031 char *p = (char *) src;
1034 for (; *p && *p != '?'; p++)
1039 /* fix up the "y&z" section */
1040 for (; *p && *p != '?'; p++)
1042 /* escape '<' and '>' to work inside nested-if */
1043 if ((*p == '<') || (*p == '>'))
1045 memmove(p + 2, p, mutt_str_strlen(p) + 1);
1056 flags |= MUTT_FORMAT_OPTIONAL;
1057 ch = *(++src); /* save the character to switch on */
1061 while ((count < sizeof(prefix)) && (*src != '?'))
1070 flags &= ~MUTT_FORMAT_OPTIONAL;
1072 /* eat the format string */
1075 while ((count < sizeof(prefix)) && (isdigit((unsigned char) *src) || (*src == '.') ||
1076 (*src == '-') || (*src == '=')))
1084 break; /* bad format */
1086 ch = *src++; /* save the character to switch on */
1089 if (flags & MUTT_FORMAT_OPTIONAL)
1094 break; /* bad format */
1097 /* eat the 'if' part of the string */
1101 while ((lrbalance > 0) && (count < sizeof(if_str)) && *src)
1103 if ((src[0] == '%') && (src[1] == '>'))
1105 /* This is a padding expando; copy two chars and carry on */
1117 else if ((src[0] == '%') && (src[1] == '<'))
1121 else if (src[0] == '>')
1127 if ((lrbalance == 1) && (src[0] == '&'))
1134 /* eat the 'else' part of the string (optional) */
1136 src++; /* skip the & */
1139 while ((lrbalance > 0) && (count < sizeof(else_str)) && *src)
1141 if ((src[0] == '%') && (src[1] == '>'))
1143 /* This is a padding expando; copy two chars and carry on */
1155 else if ((src[0] == '%') && (src[1] == '<'))
1159 else if (src[0] == '>')
1165 if ((lrbalance == 1) && (src[0] == '&'))
1173 break; /* bad format */
1175 src++; /* move past the trailing '>' (formerly '?') */
1178 /* handle generic cases first */
1179 if ((ch == '>') || (ch == '*'))
1181 /* %>X: right justify to EOL, left takes precedence
1182 * %*X: right justify to EOL, right takes precedence */
1183 int soft = ch == '*';
1185 pl = mutt_mb_charlen(src, &pw);
1192 /* see if there's room to add content, else ignore */
1193 if (((col < cols) && (wlen < buflen)) || soft)
1197 /* get contents after padding */
1198 mutt_expando_format(tmp, sizeof(tmp), 0, cols, src + pl, callback, data, flags);
1199 len = mutt_str_strlen(tmp);
1200 wid = mutt_strwidth(tmp);
1202 pad = (cols - col - wid) / pw;
1205 /* try to consume as many columns as we can, if we don't have
1206 * memory for that, use as much memory as possible */
1207 if (wlen + (pad * pl) + len > buflen)
1208 pad = (buflen > (wlen + len)) ? ((buflen - wlen - len) / pl) : 0;
1211 /* Add pre-spacing to make multi-column pad characters and
1212 * the contents after padding line up */
1213 while ((col + (pad * pw) + wid < cols) && (wlen + (pad * pl) + len < buflen))
1222 memcpy(wptr, src, pl);
1230 int offset = ((flags & MUTT_FORMAT_ARROWCURSOR) && C_ArrowCursor) ? 3 : 0;
1231 int avail_cols = (cols > offset) ? (cols - offset) : 0;
1232 /* \0-terminate buf for length computation in mutt_wstr_trunc() */
1234 /* make sure right part is at most as wide as display */
1235 len = mutt_wstr_trunc(tmp, buflen, avail_cols, &wid);
1236 /* truncate left so that right part fits completely in */
1237 wlen = mutt_wstr_trunc(buf, buflen - len, avail_cols - wid, &col);
1239 /* Multi-column characters may be truncated in the middle.
1240 * Add spacing so the right hand side lines up. */
1241 while ((col + wid < avail_cols) && (wlen + len < buflen))
1248 if ((len + wlen) > buflen)
1249 len = mutt_wstr_trunc(tmp, buflen - wlen, cols - col, NULL);
1250 memcpy(wptr, tmp, len);
1253 break; /* skip rest of input */
1259 pl = mutt_mb_charlen(src, &pw);
1266 /* see if there's room to add content, else ignore */
1267 if ((col < cols) && (wlen < buflen))
1269 int c = (cols - col) / pw;
1270 if ((c > 0) && (wlen + (c * pl) > buflen))
1271 c = ((signed) (buflen - wlen)) / pl;
1274 memcpy(wptr, src, pl);
1281 break; /* skip rest of input */
1285 bool to_lower = false;
1286 bool no_dots = false;
1288 while ((ch == '_') || (ch == ':'))
1298 /* use callback function to handle this case */
1299 src = callback(tmp, sizeof(tmp), col, cols, ch, src, prefix, if_str,
1300 else_str, data, flags);
1303 mutt_str_strlower(tmp);
1312 len = mutt_str_strlen(tmp);
1313 if ((len + wlen) > buflen)
1314 len = mutt_wstr_trunc(tmp, buflen - wlen, cols - col, NULL);
1316 memcpy(wptr, tmp, len);
1319 col += mutt_strwidth(tmp);
1322 else if (*src == '\\')
1355 /* in case of error, simply copy byte */
1356 bytes = mutt_mb_charlen(src, &width);
1362 if ((bytes > 0) && ((wlen + bytes) < buflen))
1364 memcpy(wptr, src, bytes);
1372 src += buflen - wlen;
1381 * mutt_open_read - Run a command to read from
1382 * @param[in] path Path to command
1383 * @param[out] thepid PID of the command
1384 * @retval ptr File containing output of command
1386 * This function allows the user to specify a command to read stdout from in
1387 * place of a normal file. If the last character in the string is a pipe (|),
1388 * then we assume it is a command to run instead of a normal file.
1390 FILE *mutt_open_read(const char *path, pid_t *thepid)
1395 size_t len = mutt_str_strlen(path);
1401 if (path[len - 1] == '|')
1403 /* read from a pipe */
1405 char *p = mutt_str_strdup(path);
1409 *thepid = mutt_create_filter(p, NULL, &fp, NULL);
1414 if (stat(path, &s) < 0)
1416 if (S_ISDIR(s.st_mode))
1421 fp = fopen(path, "r");
1428 * mutt_save_confirm - Ask the user to save
1429 * @param s Save location
1430 * @param st Timestamp
1431 * @retval 0 if OK to proceed
1432 * @retval -1 to abort
1433 * @retval 1 to retry
1435 int mutt_save_confirm(const char *s, struct stat *st)
1439 enum MailboxType magic = mx_path_probe(s, NULL);
1442 if (magic == MUTT_POP)
1444 mutt_error(_("Can't save message to POP mailbox"));
1449 if ((magic != MUTT_MAILBOX_ERROR) && (magic != MUTT_UNKNOWN) && !mx_access(s, W_OK))
1451 if (C_Confirmappend)
1453 struct Buffer *tmp = mutt_buffer_pool_get();
1454 mutt_buffer_printf(tmp, _("Append messages to %s?"), s);
1455 enum QuadOption ans = mutt_yesorno(mutt_b2s(tmp), MUTT_YES);
1458 else if (ans == MUTT_ABORT)
1460 mutt_buffer_pool_release(&tmp);
1465 if (magic == MUTT_NNTP)
1467 mutt_error(_("Can't save message to news server"));
1472 if (stat(s, st) != -1)
1474 if (magic == MUTT_MAILBOX_ERROR)
1476 mutt_error(_("%s is not a mailbox"), s);
1480 else if (magic != MUTT_IMAP)
1485 /* pathname does not exist */
1486 if (errno == ENOENT)
1488 if (C_Confirmcreate)
1490 struct Buffer *tmp = mutt_buffer_pool_get();
1491 mutt_buffer_printf(tmp, _("Create %s?"), s);
1492 enum QuadOption ans = mutt_yesorno(mutt_b2s(tmp), MUTT_YES);
1495 else if (ans == MUTT_ABORT)
1497 mutt_buffer_pool_release(&tmp);
1500 /* user confirmed with MUTT_YES or set C_Confirmcreate */
1503 /* create dir recursively */
1504 char *tmp_path = mutt_path_dirname(s);
1505 if (mutt_file_mkdir(tmp_path, S_IRWXU) == -1)
1507 /* report failure & abort */
1522 mutt_window_clearline(MuttMessageWindow, 0);
1527 * mutt_sleep - Sleep for a while
1528 * @param s Number of seconds to sleep
1530 * If the user config '$sleep_time' is larger, sleep that long instead.
1532 void mutt_sleep(short s)
1534 if (C_SleepTime > s)
1541 * mutt_make_version - Generate the NeoMutt version string
1542 * @retval ptr Version string
1544 * @note This returns a pointer to a static buffer
1546 const char *mutt_make_version(void)
1548 static char vstring[256];
1549 snprintf(vstring, sizeof(vstring), "NeoMutt %s%s", PACKAGE_VERSION, GitVer);
1554 * mutt_encode_path - Convert a path into the user's preferred character set
1555 * @param buf Buffer for the result
1556 * @param buflen Length of buffer
1557 * @param src Path to convert (OPTIONAL)
1559 * If `src` is NULL, the path in `buf` will be converted in-place.
1561 void mutt_encode_path(char *buf, size_t buflen, const char *src)
1563 char *p = mutt_str_strdup(src);
1564 int rc = mutt_ch_convert_string(&p, C_Charset, "us-ascii", 0);
1565 /* 'src' may be NULL, such as when called from the pop3 driver. */
1566 size_t len = mutt_str_strfcpy(buf, (rc == 0) ? p : src, buflen);
1568 /* convert the path to POSIX "Portable Filename Character Set" */
1569 for (size_t i = 0; i < len; i++)
1571 if (!isalnum(buf[i]) && !strchr("/.-_", buf[i]))
1580 * mutt_buffer_encode_path - Convert a path into the user's preferred character set
1581 * @param buf Buffer for the result
1582 * @param src Path to convert (OPTIONAL)
1584 * If `src` is NULL, the path in `buf` will be converted in-place.
1586 void mutt_buffer_encode_path(struct Buffer *buf, const char *src)
1588 char *p = mutt_str_strdup(src);
1589 int rc = mutt_ch_convert_string(&p, C_Charset, "utf-8", 0);
1590 mutt_buffer_strcpy(buf, (rc == 0) ? NONULL(p) : NONULL(src));
1595 * mutt_set_xdg_path - Find an XDG path or its fallback
1596 * @param type Type of XDG variable, e.g. #XDG_CONFIG_HOME
1597 * @param buf Buffer to save path
1598 * @param bufsize Buffer length
1599 * @retval 1 if an entry was found that actually exists on disk and 0 otherwise
1601 * Process an XDG environment variable or its fallback.
1603 int mutt_set_xdg_path(enum XdgType type, char *buf, size_t bufsize)
1605 const char *xdg_env = mutt_str_getenv(xdg_env_vars[type]);
1606 char *xdg = xdg_env ? mutt_str_strdup(xdg_env) : mutt_str_strdup(xdg_defaults[type]);
1607 char *x = xdg; /* strsep() changes xdg, so free x instead later */
1611 while ((token = strsep(&xdg, ":")))
1613 if (snprintf(buf, bufsize, "%s/%s/neomuttrc", token, PACKAGE) < 0)
1615 mutt_expand_path(buf, bufsize);
1616 if (access(buf, F_OK) == 0)
1622 if (snprintf(buf, bufsize, "%s/%s/Muttrc", token, PACKAGE) < 0)
1624 mutt_expand_path(buf, bufsize);
1625 if (access(buf, F_OK) == 0)
1637 * mutt_get_parent_path - Find the parent of a path (or mailbox)
1638 * @param path Path to use
1639 * @param buf Buffer for the result
1640 * @param buflen Length of buffer
1642 void mutt_get_parent_path(const char *path, char *buf, size_t buflen)
1644 enum MailboxType mb_magic = mx_path_probe(path, NULL);
1646 if (mb_magic == MUTT_IMAP)
1647 imap_get_parent_path(path, buf, buflen);
1648 else if (mb_magic == MUTT_NOTMUCH)
1649 mutt_str_strfcpy(buf, C_Folder, buflen);
1652 mutt_str_strfcpy(buf, path, buflen);
1653 int n = mutt_str_strlen(buf);
1657 /* remove any final trailing '/' */
1658 if (buf[n - 1] == '/')
1661 /* Remove everything until the next slash */
1662 for (n--; ((n >= 0) && (buf[n] != '/')); n--)
1676 * mutt_inbox_cmp - do two folders share the same path and one is an inbox
1677 * @param a First path
1678 * @param b Second path
1679 * @retval -1 if a is INBOX of b
1680 * @retval 0 if none is INBOX
1681 * @retval 1 if b is INBOX for a
1683 * This function compares two folder paths. It first looks for the position of
1684 * the last common '/' character. If a valid position is found and it's not the
1685 * last character in any of the two paths, the remaining parts of the paths are
1686 * compared (case insensitively) with the string "INBOX". If one of the two
1687 * paths matches, it's reported as being less than the other and the function
1688 * returns -1 (a < b) or 1 (a > b). If no paths match the requirements, the two
1689 * paths are considered equivalent and this function returns 0.
1692 * * mutt_inbox_cmp("/foo/bar", "/foo/baz") --> 0
1693 * * mutt_inbox_cmp("/foo/bar/", "/foo/bar/inbox") --> 0
1694 * * mutt_inbox_cmp("/foo/bar/sent", "/foo/bar/inbox") --> 1
1695 * * mutt_inbox_cmp("=INBOX", "=Drafts") --> -1
1697 int mutt_inbox_cmp(const char *a, const char *b)
1699 /* fast-track in case the paths have been mutt_pretty_mailbox'ified */
1700 if ((a[0] == '+') && (b[0] == '+'))
1702 return (mutt_str_strcasecmp(a + 1, "inbox") == 0) ?
1704 (mutt_str_strcasecmp(b + 1, "inbox") == 0) ? 1 : 0;
1707 const char *a_end = strrchr(a, '/');
1708 const char *b_end = strrchr(b, '/');
1710 /* If one path contains a '/', but not the other */
1711 if ((!a_end) ^ (!b_end))
1714 /* If neither path contains a '/' */
1718 /* Compare the subpaths */
1719 size_t a_len = a_end - a;
1720 size_t b_len = b_end - b;
1721 size_t min = MIN(a_len, b_len);
1722 int same = (a[min] == '/') && (b[min] == '/') && (a[min + 1] != '\0') &&
1723 (b[min + 1] != '\0') && (mutt_str_strncasecmp(a, b, min) == 0);
1728 if (mutt_str_strcasecmp(&a[min + 1], "inbox") == 0)
1731 if (mutt_str_strcasecmp(&b[min + 1], "inbox") == 0)
1738 * mutt_buffer_sanitize_filename - Replace unsafe characters in a filename
1739 * @param buf Buffer for the result
1740 * @param path Filename to make safe
1741 * @param slash Replace '/' characters too
1743 void mutt_buffer_sanitize_filename(struct Buffer *buf, const char *path, short slash)
1748 mutt_buffer_reset(buf);
1750 for (; *path; path++)
1752 if ((slash && (*path == '/')) || !strchr(filename_safe_chars, *path))
1753 mutt_buffer_addch(buf, '_');
1755 mutt_buffer_addch(buf, *path);
1760 * mutt_str_pretty_size - Display an abbreviated size, like 3.4K
1761 * @param buf Buffer for the result
1762 * @param buflen Length of the buffer
1763 * @param num Number to abbreviate
1765 void mutt_str_pretty_size(char *buf, size_t buflen, size_t num)
1767 if (!buf || (buflen == 0))
1770 if (C_SizeShowBytes && (num < 1024))
1772 snprintf(buf, buflen, "%d", (int) num);
1776 mutt_str_strfcpy(buf, C_SizeUnitsOnLeft ? "K0" : "0K", buflen);
1778 else if (C_SizeShowFractions && (num < 10189)) /* 0.1K - 9.9K */
1780 snprintf(buf, buflen, C_SizeUnitsOnLeft ? "K%3.1f" : "%3.1fK",
1781 (num < 103) ? 0.1 : (num / 1024.0));
1783 else if (!C_SizeShowMb || (num < 1023949)) /* 10K - 999K */
1785 /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
1786 snprintf(buf, buflen, C_SizeUnitsOnLeft ? ("K%zu") : ("%zuK"), (num + 51) / 1024);
1788 else if (C_SizeShowFractions && (num < 10433332)) /* 1.0M - 9.9M */
1790 snprintf(buf, buflen, C_SizeUnitsOnLeft ? "M%3.1f" : "%3.1fM", num / 1048576.0);
1794 /* (10433332 + 52428) / 1048576 = 10 */
1795 snprintf(buf, buflen, C_SizeUnitsOnLeft ? ("M%zu") : ("%zuM"), (num + 52428) / 1048576);