]> granicus.if.org Git - neomutt/blob - muttlib.c
Convert mutt_attach_reply() to use buffer pool
[neomutt] / muttlib.c
1 /**
2  * @file
3  * Some miscellaneous functions
4  *
5  * @authors
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>
9  *
10  * @copyright
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
14  * version.
15  *
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
19  * details.
20  *
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/>.
23  */
24
25 /**
26  * @page muttlib Some miscellaneous functions
27  *
28  * Some miscellaneous functions
29  */
30
31 #include "config.h"
32 #include <ctype.h>
33 #include <errno.h>
34 #include <inttypes.h>
35 #include <limits.h>
36 #include <pwd.h>
37 #include <regex.h>
38 #include <stdbool.h>
39 #include <stdint.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
45 #include "mutt/mutt.h"
46 #include "address/lib.h"
47 #include "config/lib.h"
48 #include "email/lib.h"
49 #include "core/lib.h"
50 #include "mutt.h"
51 #include "muttlib.h"
52 #include "alias.h"
53 #include "curs_lib.h"
54 #include "filter.h"
55 #include "format_flags.h"
56 #include "globals.h"
57 #include "hook.h"
58 #include "mutt_window.h"
59 #include "mx.h"
60 #include "ncrypt/ncrypt.h"
61 #include "protos.h"
62 #if defined(HAVE_SYSCALL_H)
63 #include <syscall.h>
64 #elif defined(HAVE_SYS_SYSCALL_H)
65 #include <sys/syscall.h>
66 #endif
67 #ifdef USE_IMAP
68 #include "imap/imap.h"
69 #endif
70
71 /* These Config Variables are only used in muttlib.c */
72 struct Regex *C_GecosMask; ///< Config: Regex for parsing GECOS field of /etc/passwd
73
74 static const char *xdg_env_vars[] = {
75   [XDG_CONFIG_HOME] = "XDG_CONFIG_HOME",
76   [XDG_CONFIG_DIRS] = "XDG_CONFIG_DIRS",
77 };
78
79 static const char *xdg_defaults[] = {
80   [XDG_CONFIG_HOME] = "~/.config",
81   [XDG_CONFIG_DIRS] = "/etc/xdg",
82 };
83
84 /**
85  * mutt_adv_mktemp - Create a temporary file
86  * @param buf Buffer for the name
87  *
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.
91  */
92 void mutt_adv_mktemp(struct Buffer *buf)
93 {
94   if (!(buf->data && buf->data[0]))
95   {
96     mutt_buffer_mktemp(buf);
97   }
98   else
99   {
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));
104
105     struct stat sb;
106     if ((lstat(mutt_b2s(buf), &sb) == -1) && (errno == ENOENT))
107       goto out;
108
109     char *suffix = strchr(prefix->data, '.');
110     if (suffix)
111     {
112       *suffix = '\0';
113       suffix++;
114     }
115     mutt_buffer_mktemp_pfx_sfx(buf, prefix->data, suffix);
116
117   out:
118     mutt_buffer_pool_release(&prefix);
119   }
120 }
121
122 /**
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
127  *
128  * @note The path is expanded in-place
129  */
130 char *mutt_expand_path(char *buf, size_t buflen)
131 {
132   return mutt_expand_path_regex(buf, buflen, false);
133 }
134
135 /**
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
139  *
140  * @note The path is expanded in-place
141  */
142 void mutt_buffer_expand_path_regex(struct Buffer *buf, bool regex)
143 {
144   const char *s = NULL;
145   const char *tail = "";
146
147   bool recurse = false;
148
149   struct Buffer *p = mutt_buffer_pool_get();
150   struct Buffer *q = mutt_buffer_pool_get();
151   struct Buffer *tmp = mutt_buffer_pool_get();
152
153   do
154   {
155     recurse = false;
156     s = mutt_b2s(buf);
157
158     switch (*s)
159     {
160       case '~':
161       {
162         if ((s[1] == '/') || (s[1] == '\0'))
163         {
164           mutt_buffer_strcpy(p, HomeDir);
165           tail = s + 1;
166         }
167         else
168         {
169           char *t = strchr(s + 1, '/');
170           if (t)
171             *t = '\0';
172
173           struct passwd *pw = getpwnam(s + 1);
174           if (pw)
175           {
176             mutt_buffer_strcpy(p, pw->pw_dir);
177             if (t)
178             {
179               *t = '/';
180               tail = t;
181             }
182             else
183               tail = "";
184           }
185           else
186           {
187             /* user not found! */
188             if (t)
189               *t = '/';
190             mutt_buffer_reset(p);
191             tail = s;
192           }
193         }
194         break;
195       }
196
197       case '=':
198       case '+':
199       {
200         enum MailboxType mb_type = mx_path_probe(C_Folder, NULL);
201
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] == '/')))
205         {
206           mutt_buffer_strcpy(p, NONULL(C_Folder));
207         }
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));
212         else
213           mutt_buffer_printf(p, "%s/", NONULL(C_Folder));
214
215         tail = s + 1;
216         break;
217       }
218
219         /* elm compatibility, @ expands alias to user name */
220
221       case '@':
222       {
223         struct AddressList *al = mutt_alias_lookup(s + 1);
224         if (!TAILQ_EMPTY(al))
225         {
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);
230
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);
235
236           email_free(&e);
237           /* Avoid infinite recursion if the resulting folder starts with '@' */
238           if (*p->data != '@')
239             recurse = true;
240
241           tail = "";
242         }
243         break;
244       }
245
246       case '>':
247       {
248         mutt_buffer_strcpy(p, C_Mbox);
249         tail = s + 1;
250         break;
251       }
252
253       case '<':
254       {
255         mutt_buffer_strcpy(p, C_Record);
256         tail = s + 1;
257         break;
258       }
259
260       case '!':
261       {
262         if (s[1] == '!')
263         {
264           mutt_buffer_strcpy(p, LastFolder);
265           tail = s + 2;
266         }
267         else
268         {
269           mutt_buffer_strcpy(p, C_Spoolfile);
270           tail = s + 1;
271         }
272         break;
273       }
274
275       case '-':
276       {
277         mutt_buffer_strcpy(p, LastFolder);
278         tail = s + 1;
279         break;
280       }
281
282       case '^':
283       {
284         mutt_buffer_strcpy(p, CurrentFolder);
285         tail = s + 1;
286         break;
287       }
288
289       default:
290       {
291         mutt_buffer_reset(p);
292         tail = s;
293       }
294     }
295
296     if (regex && *(mutt_b2s(p)) && !recurse)
297     {
298       mutt_file_sanitize_regex(q, mutt_b2s(p));
299       mutt_buffer_printf(tmp, "%s%s", mutt_b2s(q), tail);
300     }
301     else
302       mutt_buffer_printf(tmp, "%s%s", mutt_b2s(p), tail);
303
304     mutt_buffer_strcpy(buf, mutt_b2s(tmp));
305   } while (recurse);
306
307   mutt_buffer_pool_release(&p);
308   mutt_buffer_pool_release(&q);
309   mutt_buffer_pool_release(&tmp);
310
311 #ifdef USE_IMAP
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);
316 #endif
317 }
318
319 /**
320  * mutt_buffer_expand_path - Create the canonical path
321  * @param buf     Buffer with path
322  *
323  * @note The path is expanded in-place
324  */
325 void mutt_buffer_expand_path(struct Buffer *buf)
326 {
327   mutt_buffer_expand_path_regex(buf, false);
328 }
329
330 /**
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
336  *
337  * @note The path is expanded in-place
338  */
339 char *mutt_expand_path_regex(char *buf, size_t buflen, bool regex)
340 {
341   struct Buffer *tmp = mutt_buffer_pool_get();
342
343   mutt_buffer_addstr(tmp, NONULL(buf));
344   mutt_buffer_expand_path_regex(tmp, regex);
345   mutt_str_strfcpy(buf, mutt_b2s(tmp), buflen);
346
347   mutt_buffer_pool_release(&tmp);
348
349   return buf;
350 }
351
352 /**
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
358  *
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.
363  */
364 char *mutt_gecos_name(char *dest, size_t destlen, struct passwd *pw)
365 {
366   regmatch_t pat_match[1];
367   size_t pwnl;
368   char *p = NULL;
369
370   if (!pw || !pw->pw_gecos)
371     return NULL;
372
373   memset(dest, 0, destlen);
374
375   if (mutt_regex_capture(C_GecosMask, pw->pw_gecos, 1, pat_match))
376   {
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));
379   }
380   else if ((p = strchr(pw->pw_gecos, ',')))
381     mutt_str_strfcpy(dest, pw->pw_gecos, MIN(destlen, p - pw->pw_gecos + 1));
382   else
383     mutt_str_strfcpy(dest, pw->pw_gecos, destlen);
384
385   pwnl = strlen(pw->pw_name);
386
387   for (int idx = 0; dest[idx]; idx++)
388   {
389     if (dest[idx] == '&')
390     {
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]);
395     }
396   }
397
398   return dest;
399 }
400
401 /**
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
406  */
407 bool mutt_needs_mailcap(struct Body *m)
408 {
409   switch (m->type)
410   {
411     case TYPE_TEXT:
412       if (mutt_str_strcasecmp("plain", m->subtype) == 0)
413         return false;
414       break;
415     case TYPE_APPLICATION:
416       if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(m))
417         return false;
418       if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(m))
419         return false;
420       break;
421
422     case TYPE_MULTIPART:
423     case TYPE_MESSAGE:
424       return false;
425   }
426
427   return true;
428 }
429
430 /**
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
434  */
435 bool mutt_is_text_part(struct Body *b)
436 {
437   int t = b->type;
438   char *s = b->subtype;
439
440   if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
441     return false;
442
443   if (t == TYPE_TEXT)
444     return true;
445
446   if (t == TYPE_MESSAGE)
447   {
448     if (mutt_str_strcasecmp("delivery-status", s) == 0)
449       return true;
450   }
451
452   if (((WithCrypto & APPLICATION_PGP) != 0) && (t == TYPE_APPLICATION))
453   {
454     if (mutt_str_strcasecmp("pgp-keys", s) == 0)
455       return true;
456   }
457
458   return false;
459 }
460
461 static FILE *fp_random;
462
463 /**
464  * mutt_randbuf - Fill a buffer with randomness
465  * @param buf    Buffer for result
466  * @param buflen Size of buffer
467  * @retval  0 Success
468  * @retval -1 Error
469  */
470 int mutt_randbuf(void *buf, size_t buflen)
471 {
472   if (buflen > 1048576)
473   {
474     mutt_error(_("mutt_randbuf buflen=%zu"), buflen);
475     return -1;
476   }
477 /* XXX switch to HAVE_GETRANDOM and getrandom() in about 2017 */
478 #if defined(SYS_getrandom) && defined(__linux__)
479   long ret;
480   do
481   {
482     ret = syscall(SYS_getrandom, buf, buflen, 0, 0, 0, 0);
483   } while ((ret == -1) && (errno == EINTR));
484   if (ret == buflen)
485     return 0;
486 #endif
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 */
489   if (!fp_random)
490   {
491     fp_random = fopen("/dev/urandom", "rb");
492     if (!fp_random)
493     {
494       mutt_error(_("open /dev/urandom: %s"), strerror(errno));
495       return -1;
496     }
497     setbuf(fp_random, NULL);
498   }
499   if (fread(buf, 1, buflen, fp_random) != buflen)
500   {
501     mutt_error(_("read /dev/urandom: %s"), strerror(errno));
502     return -1;
503   }
504
505   return 0;
506 }
507
508 static const unsigned char base32[] = "abcdefghijklmnopqrstuvwxyz234567";
509
510 /**
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
514  */
515 void mutt_rand_base32(void *buf, size_t buflen)
516 {
517   uint8_t *p = buf;
518
519   if (mutt_randbuf(p, buflen) < 0)
520     mutt_exit(1);
521   for (size_t pos = 0; pos < buflen; pos++)
522     p[pos] = base32[p[pos] % 32];
523 }
524
525 /**
526  * mutt_rand32 - Create a 32-bit random number
527  * @retval num Random number
528  */
529 uint32_t mutt_rand32(void)
530 {
531   uint32_t num = 0;
532
533   if (mutt_randbuf(&num, sizeof(num)) < 0)
534     mutt_exit(1);
535   return num;
536 }
537
538 /**
539  * mutt_rand64 - Create a 64-bit random number
540  * @retval num Random number
541  */
542 uint64_t mutt_rand64(void)
543 {
544   uint64_t num = 0;
545
546   if (mutt_randbuf(&num, sizeof(num)) < 0)
547     mutt_exit(1);
548   return num;
549 }
550
551 /**
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
558  */
559 void mutt_buffer_mktemp_full(struct Buffer *buf, const char *prefix,
560                              const char *suffix, const char *src, int line)
561 {
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));
565
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))
568   {
569     mutt_debug(LL_DEBUG1, "%s:%d: ERROR: unlink(\"%s\"): %s (errno %d)\n", src,
570                line, mutt_b2s(buf), strerror(errno), errno);
571   }
572 }
573
574 /**
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
582  *
583  * @note This doesn't create the file, only the name
584  */
585 void mutt_mktemp_full(char *buf, size_t buflen, const char *prefix,
586                       const char *suffix, const char *src, int line)
587 {
588   size_t n =
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));
592   if (n >= buflen)
593   {
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);
598   }
599   mutt_debug(LL_DEBUG3, "%s:%d: mutt_mktemp returns \"%s\"\n", src, line, buf);
600   if (unlink(buf) && (errno != ENOENT))
601   {
602     mutt_debug(LL_DEBUG1, "%s:%d: ERROR: unlink(\"%s\"): %s (errno %d)\n", src,
603                line, buf, strerror(errno), errno);
604   }
605 }
606
607 /**
608  * mutt_pretty_mailbox - Shorten a mailbox path using '~' or '='
609  * @param buf    Buffer containing string to shorten
610  * @param buflen Length of buffer
611  *
612  * Collapse the pathname using ~ or = when possible
613  */
614 void mutt_pretty_mailbox(char *buf, size_t buflen)
615 {
616   if (!buf)
617     return;
618
619   char *p = buf, *q = buf;
620   size_t len;
621   enum UrlScheme scheme;
622   char tmp[PATH_MAX];
623
624   scheme = url_check_scheme(buf);
625
626   if ((scheme == U_IMAP) || (scheme == U_IMAPS))
627   {
628     imap_pretty_mailbox(buf, buflen, C_Folder);
629     return;
630   }
631
632   if (scheme == U_NOTMUCH)
633     return;
634
635   /* if buf is an url, only collapse path component */
636   if (scheme != U_UNKNOWN)
637   {
638     p = strchr(buf, ':') + 1;
639     if (strncmp(p, "//", 2) == 0)
640       q = strchr(p + 2, '/');
641     if (!q)
642       q = strchr(p, '\0');
643     p = q;
644   }
645
646   /* cleanup path */
647   if (strstr(p, "//") || strstr(p, "/./"))
648   {
649     /* first attempt to collapse the pathname, this is more
650      * lightweight than realpath() and doesn't resolve links */
651     while (*p)
652     {
653       if ((p[0] == '/') && (p[1] == '/'))
654       {
655         *q++ = '/';
656         p += 2;
657       }
658       else if ((p[0] == '/') && (p[1] == '.') && (p[2] == '/'))
659       {
660         *q++ = '/';
661         p += 3;
662       }
663       else
664         *q++ = *p++;
665     }
666     *q = '\0';
667   }
668   else if (strstr(p, "..") && ((scheme == U_UNKNOWN) || (scheme == U_FILE)) &&
669            realpath(p, tmp))
670   {
671     mutt_str_strfcpy(p, tmp, buflen - (p - buf));
672   }
673
674   if ((len = mutt_str_startswith(buf, C_Folder, CASE_MATCH)) && (buf[len] == '/'))
675   {
676     *buf++ = '=';
677     memmove(buf, buf + len, mutt_str_strlen(buf + len) + 1);
678   }
679   else if ((len = mutt_str_startswith(buf, HomeDir, CASE_MATCH)) && (buf[len] == '/'))
680   {
681     *buf++ = '~';
682     memmove(buf, buf + len - 1, mutt_str_strlen(buf + len - 1) + 1);
683   }
684 }
685
686 /**
687  * mutt_buffer_pretty_mailbox - Shorten a mailbox path using '~' or '='
688  * @param buf Buffer containing Mailbox name
689  */
690 void mutt_buffer_pretty_mailbox(struct Buffer *buf)
691 {
692   if (!buf || !buf->data)
693     return;
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);
699 }
700
701 /**
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)
708  * @retval  0 Success
709  * @retval -1 Abort
710  * @retval  1 Error
711  */
712 int mutt_check_overwrite(const char *attname, const char *path, struct Buffer *fname,
713                          enum SaveAttach *opt, char **directory)
714 {
715   struct stat st;
716
717   mutt_buffer_strcpy(fname, path);
718   if (access(mutt_b2s(fname), F_OK) != 0)
719     return 0;
720   if (stat(mutt_b2s(fname), &st) != 0)
721     return -1;
722   if (S_ISDIR(st.st_mode))
723   {
724     enum QuadOption ans = MUTT_NO;
725     if (directory)
726     {
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")))
732       {
733         case 3: /* all */
734           mutt_str_replace(directory, mutt_b2s(fname));
735           break;
736         case 1: /* yes */
737           FREE(directory);
738           break;
739         case -1: /* abort */
740           FREE(directory);
741           return -1;
742         case 2: /* no */
743           FREE(directory);
744           return 1;
745       }
746     }
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;
751
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))
756     {
757       mutt_buffer_pool_release(&tmp);
758       return (-1);
759     }
760     mutt_buffer_concat_path(fname, path, mutt_b2s(tmp));
761     mutt_buffer_pool_release(&tmp);
762   }
763
764   if ((*opt == MUTT_SAVE_NO_FLAGS) && (access(mutt_b2s(fname), F_OK) == 0))
765   {
766     switch (
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?
769                           _("oac")))
770     {
771       case -1: /* abort */
772         return -1;
773       case 3: /* cancel */
774         return 1;
775
776       case 2: /* append */
777         *opt = MUTT_SAVE_APPEND;
778         break;
779       case 1: /* overwrite */
780         *opt = MUTT_SAVE_OVERWRITE;
781         break;
782     }
783   }
784   return 0;
785 }
786
787 /**
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
792  *
793  * If the user hasn't set `$save_address` the name will be truncated to the '@'
794  * character.
795  */
796 void mutt_save_path(char *buf, size_t buflen, const struct Address *addr)
797 {
798   if (addr && addr->mailbox)
799   {
800     mutt_str_strfcpy(buf, addr->mailbox, buflen);
801     if (!C_SaveAddress)
802     {
803       char *p = strpbrk(buf, "%@");
804       if (p)
805         *p = '\0';
806     }
807     mutt_str_strlower(buf);
808   }
809   else
810     *buf = '\0';
811 }
812
813 /**
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
817  */
818 void mutt_buffer_save_path(struct Buffer *dest, const struct Address *a)
819 {
820   if (a && a->mailbox)
821   {
822     mutt_buffer_strcpy(dest, a->mailbox);
823     if (!C_SaveAddress)
824     {
825       char *p = strpbrk(dest->data, "%@");
826       if (p)
827       {
828         *p = '\0';
829         mutt_buffer_fix_dptr(dest);
830       }
831     }
832     mutt_str_strlower(dest->data);
833   }
834   else
835     mutt_buffer_reset(dest);
836 }
837
838 /**
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
842  *
843  * The filename will be stripped of '/', space, etc to make it safe.
844  */
845 void mutt_safe_path(struct Buffer *dest, const struct Address *a)
846 {
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))
850       *p = '_';
851 }
852
853 /**
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
863  */
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)
866 {
867   char prefix[128], tmp[1024];
868   char *cp = NULL, *wptr = buf;
869   char ch;
870   char if_str[128], else_str[128];
871   size_t wlen, count, len, wid;
872   FILE *fp_filter = NULL;
873   char *recycler = NULL;
874
875   char src2[256];
876   mutt_str_strfcpy(src2, src, mutt_str_strlen(src) + 1);
877   src = src2;
878
879   prefix[0] = '\0';
880   buflen--; /* save room for the terminal \0 */
881   wlen = ((flags & MUTT_FORMAT_ARROWCURSOR) && C_ArrowCursor) ? 3 : 0;
882   col += wlen;
883
884   if ((flags & MUTT_FORMAT_NOFILTER) == 0)
885   {
886     int off = -1;
887
888     /* Do not consider filters if no pipe at end */
889     int n = mutt_str_strlen(src);
890     if ((n > 1) && (src[n - 1] == '|'))
891     {
892       /* Scan backwards for backslashes */
893       off = n;
894       while ((off > 0) && (src[off - 2] == '\\'))
895         off--;
896     }
897
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))
901     {
902       char srccopy[1024];
903       int i = 0;
904
905       mutt_debug(LL_DEBUG3, "fmtpipe = %s\n", src);
906
907       strncpy(srccopy, src, n);
908       srccopy[n - 1] = '\0';
909
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);
918
919       /* Iterate expansions across successive arguments */
920       do
921       {
922         /* Extract the command name and copy to command line */
923         mutt_debug(LL_DEBUG3, "fmtpipe +++: %s\n", srcbuf.dptr);
924         if (word.data)
925           *word.data = '\0';
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++)
932         {
933           if (*p == '\'')
934           {
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, "'\"'\"'");
940           }
941           else
942             mutt_buffer_addch(&cmd, *p);
943         }
944         mutt_buffer_addch(&cmd, '\'');
945         mutt_buffer_addch(&cmd, ' ');
946       } while (MoreArgs(&srcbuf));
947
948       mutt_debug(LL_DEBUG3, "fmtpipe > %s\n", cmd.data);
949
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);
953       if (pid != -1)
954       {
955         int rc;
956
957         n = fread(buf, 1, buflen /* already decremented */, fp_filter);
958         mutt_file_fclose(&fp_filter);
959         rc = mutt_wait_filter(pid);
960         if (rc != 0)
961           mutt_debug(LL_DEBUG1, "format pipe cmd exited code %d\n", rc);
962         if (n > 0)
963         {
964           buf[n] = '\0';
965           while ((n > 0) && ((buf[n - 1] == '\n') || (buf[n - 1] == '\r')))
966             buf[--n] = '\0';
967           mutt_debug(LL_DEBUG5, "fmtpipe < %s\n", buf);
968
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] == '%'))
974           {
975             n--;
976             buf[n] = '\0'; /* remove '%' */
977             if ((n > 0) && (buf[n - 1] != '%'))
978             {
979               recycler = mutt_str_strdup(buf);
980               if (recycler)
981               {
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);
988                 FREE(&recycler);
989               }
990             }
991           }
992         }
993         else
994         {
995           /* read error */
996           mutt_debug(LL_DEBUG1, "error reading from fmtpipe: %s (errno=%d)\n",
997                      strerror(errno), errno);
998           *wptr = '\0';
999         }
1000       }
1001       else
1002       {
1003         /* Filter failed; erase write buffer */
1004         *wptr = '\0';
1005       }
1006
1007       mutt_buffer_dealloc(&cmd);
1008       mutt_buffer_dealloc(&srcbuf);
1009       mutt_buffer_dealloc(&word);
1010       return;
1011     }
1012   }
1013
1014   while (*src && (wlen < buflen))
1015   {
1016     if (*src == '%')
1017     {
1018       if (*++src == '%')
1019       {
1020         *wptr++ = '%';
1021         wlen++;
1022         col++;
1023         src++;
1024         continue;
1025       }
1026
1027       if (*src == '?')
1028       {
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;
1032         *p = '<';
1033         /* skip over "x" */
1034         for (; *p && *p != '?'; p++)
1035           ;
1036         /* nothing */
1037         if (*p == '?')
1038           p++;
1039         /* fix up the "y&z" section */
1040         for (; *p && *p != '?'; p++)
1041         {
1042           /* escape '<' and '>' to work inside nested-if */
1043           if ((*p == '<') || (*p == '>'))
1044           {
1045             memmove(p + 2, p, mutt_str_strlen(p) + 1);
1046             *p++ = '\\';
1047             *p++ = '\\';
1048           }
1049         }
1050         if (*p == '?')
1051           *p = '>';
1052       }
1053
1054       if (*src == '<')
1055       {
1056         flags |= MUTT_FORMAT_OPTIONAL;
1057         ch = *(++src); /* save the character to switch on */
1058         src++;
1059         cp = prefix;
1060         count = 0;
1061         while ((count < sizeof(prefix)) && (*src != '?'))
1062         {
1063           *cp++ = *src++;
1064           count++;
1065         }
1066         *cp = '\0';
1067       }
1068       else
1069       {
1070         flags &= ~MUTT_FORMAT_OPTIONAL;
1071
1072         /* eat the format string */
1073         cp = prefix;
1074         count = 0;
1075         while ((count < sizeof(prefix)) && (isdigit((unsigned char) *src) || (*src == '.') ||
1076                                             (*src == '-') || (*src == '=')))
1077         {
1078           *cp++ = *src++;
1079           count++;
1080         }
1081         *cp = '\0';
1082
1083         if (!*src)
1084           break; /* bad format */
1085
1086         ch = *src++; /* save the character to switch on */
1087       }
1088
1089       if (flags & MUTT_FORMAT_OPTIONAL)
1090       {
1091         int lrbalance;
1092
1093         if (*src != '?')
1094           break; /* bad format */
1095         src++;
1096
1097         /* eat the 'if' part of the string */
1098         cp = if_str;
1099         count = 0;
1100         lrbalance = 1;
1101         while ((lrbalance > 0) && (count < sizeof(if_str)) && *src)
1102         {
1103           if ((src[0] == '%') && (src[1] == '>'))
1104           {
1105             /* This is a padding expando; copy two chars and carry on */
1106             *cp++ = *src++;
1107             *cp++ = *src++;
1108             count += 2;
1109             continue;
1110           }
1111
1112           if (*src == '\\')
1113           {
1114             src++;
1115             *cp++ = *src++;
1116           }
1117           else if ((src[0] == '%') && (src[1] == '<'))
1118           {
1119             lrbalance++;
1120           }
1121           else if (src[0] == '>')
1122           {
1123             lrbalance--;
1124           }
1125           if (lrbalance == 0)
1126             break;
1127           if ((lrbalance == 1) && (src[0] == '&'))
1128             break;
1129           *cp++ = *src++;
1130           count++;
1131         }
1132         *cp = '\0';
1133
1134         /* eat the 'else' part of the string (optional) */
1135         if (*src == '&')
1136           src++; /* skip the & */
1137         cp = else_str;
1138         count = 0;
1139         while ((lrbalance > 0) && (count < sizeof(else_str)) && *src)
1140         {
1141           if ((src[0] == '%') && (src[1] == '>'))
1142           {
1143             /* This is a padding expando; copy two chars and carry on */
1144             *cp++ = *src++;
1145             *cp++ = *src++;
1146             count += 2;
1147             continue;
1148           }
1149
1150           if (*src == '\\')
1151           {
1152             src++;
1153             *cp++ = *src++;
1154           }
1155           else if ((src[0] == '%') && (src[1] == '<'))
1156           {
1157             lrbalance++;
1158           }
1159           else if (src[0] == '>')
1160           {
1161             lrbalance--;
1162           }
1163           if (lrbalance == 0)
1164             break;
1165           if ((lrbalance == 1) && (src[0] == '&'))
1166             break;
1167           *cp++ = *src++;
1168           count++;
1169         }
1170         *cp = '\0';
1171
1172         if (!*src)
1173           break; /* bad format */
1174
1175         src++; /* move past the trailing '>' (formerly '?') */
1176       }
1177
1178       /* handle generic cases first */
1179       if ((ch == '>') || (ch == '*'))
1180       {
1181         /* %>X: right justify to EOL, left takes precedence
1182          * %*X: right justify to EOL, right takes precedence */
1183         int soft = ch == '*';
1184         int pl, pw;
1185         pl = mutt_mb_charlen(src, &pw);
1186         if (pl <= 0)
1187         {
1188           pl = 1;
1189           pw = 1;
1190         }
1191
1192         /* see if there's room to add content, else ignore */
1193         if (((col < cols) && (wlen < buflen)) || soft)
1194         {
1195           int pad;
1196
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);
1201
1202           pad = (cols - col - wid) / pw;
1203           if (pad >= 0)
1204           {
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;
1209             else
1210             {
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))
1214               {
1215                 *wptr++ = ' ';
1216                 wlen++;
1217                 col++;
1218               }
1219             }
1220             while (pad-- > 0)
1221             {
1222               memcpy(wptr, src, pl);
1223               wptr += pl;
1224               wlen += pl;
1225               col += pw;
1226             }
1227           }
1228           else if (soft)
1229           {
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() */
1233             *wptr = '\0';
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);
1238             wptr = buf + wlen;
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))
1242             {
1243               *wptr++ = ' ';
1244               wlen++;
1245               col++;
1246             }
1247           }
1248           if ((len + wlen) > buflen)
1249             len = mutt_wstr_trunc(tmp, buflen - wlen, cols - col, NULL);
1250           memcpy(wptr, tmp, len);
1251           wptr += len;
1252         }
1253         break; /* skip rest of input */
1254       }
1255       else if (ch == '|')
1256       {
1257         /* pad to EOL */
1258         int pl, pw;
1259         pl = mutt_mb_charlen(src, &pw);
1260         if (pl <= 0)
1261         {
1262           pl = 1;
1263           pw = 1;
1264         }
1265
1266         /* see if there's room to add content, else ignore */
1267         if ((col < cols) && (wlen < buflen))
1268         {
1269           int c = (cols - col) / pw;
1270           if ((c > 0) && (wlen + (c * pl) > buflen))
1271             c = ((signed) (buflen - wlen)) / pl;
1272           while (c > 0)
1273           {
1274             memcpy(wptr, src, pl);
1275             wptr += pl;
1276             wlen += pl;
1277             col += pw;
1278             c--;
1279           }
1280         }
1281         break; /* skip rest of input */
1282       }
1283       else
1284       {
1285         bool to_lower = false;
1286         bool no_dots = false;
1287
1288         while ((ch == '_') || (ch == ':'))
1289         {
1290           if (ch == '_')
1291             to_lower = true;
1292           else if (ch == ':')
1293             no_dots = true;
1294
1295           ch = *src++;
1296         }
1297
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);
1301
1302         if (to_lower)
1303           mutt_str_strlower(tmp);
1304         if (no_dots)
1305         {
1306           char *p = tmp;
1307           for (; *p; p++)
1308             if (*p == '.')
1309               *p = '_';
1310         }
1311
1312         len = mutt_str_strlen(tmp);
1313         if ((len + wlen) > buflen)
1314           len = mutt_wstr_trunc(tmp, buflen - wlen, cols - col, NULL);
1315
1316         memcpy(wptr, tmp, len);
1317         wptr += len;
1318         wlen += len;
1319         col += mutt_strwidth(tmp);
1320       }
1321     }
1322     else if (*src == '\\')
1323     {
1324       if (!*++src)
1325         break;
1326       switch (*src)
1327       {
1328         case 'f':
1329           *wptr = '\f';
1330           break;
1331         case 'n':
1332           *wptr = '\n';
1333           break;
1334         case 'r':
1335           *wptr = '\r';
1336           break;
1337         case 't':
1338           *wptr = '\t';
1339           break;
1340         case 'v':
1341           *wptr = '\v';
1342           break;
1343         default:
1344           *wptr = *src;
1345           break;
1346       }
1347       src++;
1348       wptr++;
1349       wlen++;
1350       col++;
1351     }
1352     else
1353     {
1354       int bytes, width;
1355       /* in case of error, simply copy byte */
1356       bytes = mutt_mb_charlen(src, &width);
1357       if (bytes < 0)
1358       {
1359         bytes = 1;
1360         width = 1;
1361       }
1362       if ((bytes > 0) && ((wlen + bytes) < buflen))
1363       {
1364         memcpy(wptr, src, bytes);
1365         wptr += bytes;
1366         src += bytes;
1367         wlen += bytes;
1368         col += width;
1369       }
1370       else
1371       {
1372         src += buflen - wlen;
1373         wlen = buflen;
1374       }
1375     }
1376   }
1377   *wptr = '\0';
1378 }
1379
1380 /**
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
1385  *
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.
1389  */
1390 FILE *mutt_open_read(const char *path, pid_t *thepid)
1391 {
1392   FILE *fp = NULL;
1393   struct stat s;
1394
1395   size_t len = mutt_str_strlen(path);
1396   if (len == 0)
1397   {
1398     return NULL;
1399   }
1400
1401   if (path[len - 1] == '|')
1402   {
1403     /* read from a pipe */
1404
1405     char *p = mutt_str_strdup(path);
1406
1407     p[len - 1] = 0;
1408     mutt_endwin();
1409     *thepid = mutt_create_filter(p, NULL, &fp, NULL);
1410     FREE(&p);
1411   }
1412   else
1413   {
1414     if (stat(path, &s) < 0)
1415       return NULL;
1416     if (S_ISDIR(s.st_mode))
1417     {
1418       errno = EINVAL;
1419       return NULL;
1420     }
1421     fp = fopen(path, "r");
1422     *thepid = -1;
1423   }
1424   return fp;
1425 }
1426
1427 /**
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
1434  */
1435 int mutt_save_confirm(const char *s, struct stat *st)
1436 {
1437   int ret = 0;
1438
1439   enum MailboxType magic = mx_path_probe(s, NULL);
1440
1441 #ifdef USE_POP
1442   if (magic == MUTT_POP)
1443   {
1444     mutt_error(_("Can't save message to POP mailbox"));
1445     return 1;
1446   }
1447 #endif
1448
1449   if ((magic != MUTT_MAILBOX_ERROR) && (magic != MUTT_UNKNOWN) && !mx_access(s, W_OK))
1450   {
1451     if (C_Confirmappend)
1452     {
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);
1456       if (ans == MUTT_NO)
1457         ret = 1;
1458       else if (ans == MUTT_ABORT)
1459         ret = -1;
1460       mutt_buffer_pool_release(&tmp);
1461     }
1462   }
1463
1464 #ifdef USE_NNTP
1465   if (magic == MUTT_NNTP)
1466   {
1467     mutt_error(_("Can't save message to news server"));
1468     return 0;
1469   }
1470 #endif
1471
1472   if (stat(s, st) != -1)
1473   {
1474     if (magic == MUTT_MAILBOX_ERROR)
1475     {
1476       mutt_error(_("%s is not a mailbox"), s);
1477       return 1;
1478     }
1479   }
1480   else if (magic != MUTT_IMAP)
1481   {
1482     st->st_mtime = 0;
1483     st->st_atime = 0;
1484
1485     /* pathname does not exist */
1486     if (errno == ENOENT)
1487     {
1488       if (C_Confirmcreate)
1489       {
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);
1493         if (ans == MUTT_NO)
1494           ret = 1;
1495         else if (ans == MUTT_ABORT)
1496           ret = -1;
1497         mutt_buffer_pool_release(&tmp);
1498       }
1499
1500       /* user confirmed with MUTT_YES or set C_Confirmcreate */
1501       if (ret == 0)
1502       {
1503         /* create dir recursively */
1504         char *tmp_path = mutt_path_dirname(s);
1505         if (mutt_file_mkdir(tmp_path, S_IRWXU) == -1)
1506         {
1507           /* report failure & abort */
1508           mutt_perror(s);
1509           FREE(&tmp_path);
1510           return 1;
1511         }
1512         FREE(&tmp_path);
1513       }
1514     }
1515     else
1516     {
1517       mutt_perror(s);
1518       return 1;
1519     }
1520   }
1521
1522   mutt_window_clearline(MuttMessageWindow, 0);
1523   return ret;
1524 }
1525
1526 /**
1527  * mutt_sleep - Sleep for a while
1528  * @param s Number of seconds to sleep
1529  *
1530  * If the user config '$sleep_time' is larger, sleep that long instead.
1531  */
1532 void mutt_sleep(short s)
1533 {
1534   if (C_SleepTime > s)
1535     sleep(C_SleepTime);
1536   else if (s)
1537     sleep(s);
1538 }
1539
1540 /**
1541  * mutt_make_version - Generate the NeoMutt version string
1542  * @retval ptr Version string
1543  *
1544  * @note This returns a pointer to a static buffer
1545  */
1546 const char *mutt_make_version(void)
1547 {
1548   static char vstring[256];
1549   snprintf(vstring, sizeof(vstring), "NeoMutt %s%s", PACKAGE_VERSION, GitVer);
1550   return vstring;
1551 }
1552
1553 /**
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)
1558  *
1559  * If `src` is NULL, the path in `buf` will be converted in-place.
1560  */
1561 void mutt_encode_path(char *buf, size_t buflen, const char *src)
1562 {
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);
1567
1568   /* convert the path to POSIX "Portable Filename Character Set" */
1569   for (size_t i = 0; i < len; i++)
1570   {
1571     if (!isalnum(buf[i]) && !strchr("/.-_", buf[i]))
1572     {
1573       buf[i] = '_';
1574     }
1575   }
1576   FREE(&p);
1577 }
1578
1579 /**
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)
1583  *
1584  * If `src` is NULL, the path in `buf` will be converted in-place.
1585  */
1586 void mutt_buffer_encode_path(struct Buffer *buf, const char *src)
1587 {
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));
1591   FREE(&p);
1592 }
1593
1594 /**
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
1600  *
1601  * Process an XDG environment variable or its fallback.
1602  */
1603 int mutt_set_xdg_path(enum XdgType type, char *buf, size_t bufsize)
1604 {
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 */
1608   char *token = NULL;
1609   int rc = 0;
1610
1611   while ((token = strsep(&xdg, ":")))
1612   {
1613     if (snprintf(buf, bufsize, "%s/%s/neomuttrc", token, PACKAGE) < 0)
1614       continue;
1615     mutt_expand_path(buf, bufsize);
1616     if (access(buf, F_OK) == 0)
1617     {
1618       rc = 1;
1619       break;
1620     }
1621
1622     if (snprintf(buf, bufsize, "%s/%s/Muttrc", token, PACKAGE) < 0)
1623       continue;
1624     mutt_expand_path(buf, bufsize);
1625     if (access(buf, F_OK) == 0)
1626     {
1627       rc = 1;
1628       break;
1629     }
1630   }
1631
1632   FREE(&x);
1633   return rc;
1634 }
1635
1636 /**
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
1641  */
1642 void mutt_get_parent_path(const char *path, char *buf, size_t buflen)
1643 {
1644   enum MailboxType mb_magic = mx_path_probe(path, NULL);
1645
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);
1650   else
1651   {
1652     mutt_str_strfcpy(buf, path, buflen);
1653     int n = mutt_str_strlen(buf);
1654     if (n == 0)
1655       return;
1656
1657     /* remove any final trailing '/' */
1658     if (buf[n - 1] == '/')
1659       buf[n - 1] = '\0';
1660
1661     /* Remove everything until the next slash */
1662     for (n--; ((n >= 0) && (buf[n] != '/')); n--)
1663       ;
1664
1665     if (n > 0)
1666       buf[n] = '\0';
1667     else
1668     {
1669       buf[0] = '/';
1670       buf[1] = '\0';
1671     }
1672   }
1673 }
1674
1675 /**
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
1682  *
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.
1690  *
1691  * Examples:
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
1696  */
1697 int mutt_inbox_cmp(const char *a, const char *b)
1698 {
1699   /* fast-track in case the paths have been mutt_pretty_mailbox'ified */
1700   if ((a[0] == '+') && (b[0] == '+'))
1701   {
1702     return (mutt_str_strcasecmp(a + 1, "inbox") == 0) ?
1703                -1 :
1704                (mutt_str_strcasecmp(b + 1, "inbox") == 0) ? 1 : 0;
1705   }
1706
1707   const char *a_end = strrchr(a, '/');
1708   const char *b_end = strrchr(b, '/');
1709
1710   /* If one path contains a '/', but not the other */
1711   if ((!a_end) ^ (!b_end))
1712     return 0;
1713
1714   /* If neither path contains a '/' */
1715   if (!a_end)
1716     return 0;
1717
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);
1724
1725   if (!same)
1726     return 0;
1727
1728   if (mutt_str_strcasecmp(&a[min + 1], "inbox") == 0)
1729     return -1;
1730
1731   if (mutt_str_strcasecmp(&b[min + 1], "inbox") == 0)
1732     return 1;
1733
1734   return 0;
1735 }
1736
1737 /**
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
1742  */
1743 void mutt_buffer_sanitize_filename(struct Buffer *buf, const char *path, short slash)
1744 {
1745   if (!buf || !path)
1746     return;
1747
1748   mutt_buffer_reset(buf);
1749
1750   for (; *path; path++)
1751   {
1752     if ((slash && (*path == '/')) || !strchr(filename_safe_chars, *path))
1753       mutt_buffer_addch(buf, '_');
1754     else
1755       mutt_buffer_addch(buf, *path);
1756   }
1757 }
1758
1759 /**
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
1764  */
1765 void mutt_str_pretty_size(char *buf, size_t buflen, size_t num)
1766 {
1767   if (!buf || (buflen == 0))
1768     return;
1769
1770   if (C_SizeShowBytes && (num < 1024))
1771   {
1772     snprintf(buf, buflen, "%d", (int) num);
1773   }
1774   else if (num == 0)
1775   {
1776     mutt_str_strfcpy(buf, C_SizeUnitsOnLeft ? "K0" : "0K", buflen);
1777   }
1778   else if (C_SizeShowFractions && (num < 10189)) /* 0.1K - 9.9K */
1779   {
1780     snprintf(buf, buflen, C_SizeUnitsOnLeft ? "K%3.1f" : "%3.1fK",
1781              (num < 103) ? 0.1 : (num / 1024.0));
1782   }
1783   else if (!C_SizeShowMb || (num < 1023949)) /* 10K - 999K */
1784   {
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);
1787   }
1788   else if (C_SizeShowFractions && (num < 10433332)) /* 1.0M - 9.9M */
1789   {
1790     snprintf(buf, buflen, C_SizeUnitsOnLeft ? "M%3.1f" : "%3.1fM", num / 1048576.0);
1791   }
1792   else /* 10M+ */
1793   {
1794     /* (10433332 + 52428) / 1048576 = 10 */
1795     snprintf(buf, buflen, C_SizeUnitsOnLeft ? ("M%zu") : ("%zuM"), (num + 52428) / 1048576);
1796   }
1797 }