]> granicus.if.org Git - neomutt/blob - compose.c
Convert mutt_attach_reply() to use buffer pool
[neomutt] / compose.c
1 /**
2  * @file
3  * GUI editor for an email's headers
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2002,2007,2010,2012 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2004 g10 Code GmbH
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 compose GUI editor for an email's headers
27  *
28  * GUI editor for an email's headers
29  */
30
31 #include "config.h"
32 #include <errno.h>
33 #include <limits.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39 #include "mutt/mutt.h"
40 #include "address/lib.h"
41 #include "config/lib.h"
42 #include "email/lib.h"
43 #include "core/lib.h"
44 #include "conn/conn.h"
45 #include "mutt.h"
46 #include "compose.h"
47 #include "alias.h"
48 #include "browser.h"
49 #include "color.h"
50 #include "commands.h"
51 #include "context.h"
52 #include "curs_lib.h"
53 #include "edit.h"
54 #include "format_flags.h"
55 #include "globals.h"
56 #include "hook.h"
57 #include "index.h"
58 #include "keymap.h"
59 #include "mutt_attach.h"
60 #include "mutt_curses.h"
61 #include "mutt_header.h"
62 #include "mutt_logging.h"
63 #include "mutt_menu.h"
64 #include "mutt_window.h"
65 #include "muttlib.h"
66 #include "mx.h"
67 #include "ncrypt/ncrypt.h"
68 #include "opcodes.h"
69 #include "options.h"
70 #include "protos.h"
71 #include "recvattach.h"
72 #include "rfc3676.h"
73 #include "sendlib.h"
74 #include "sort.h"
75 #ifdef ENABLE_NLS
76 #include <libintl.h>
77 #endif
78 #ifdef MIXMASTER
79 #include "remailer.h"
80 #endif
81 #ifdef USE_NNTP
82 #include "nntp/nntp.h"
83 #endif
84 #ifdef USE_POP
85 #include "pop/pop.h"
86 #endif
87 #ifdef USE_IMAP
88 #include "imap/imap.h"
89 #endif
90 #ifdef USE_AUTOCRYPT
91 #include "autocrypt/autocrypt.h"
92 #endif
93
94 /* These Config Variables are only used in compose.c */
95 char *C_ComposeFormat; ///< Config: printf-like format string for the Compose panel's status bar
96 char *C_Ispell; ///< Config: External command to perform spell-checking
97 unsigned char C_Postpone; ///< Config: Save messages to the #C_Postponed folder
98
99 static const char *There_are_no_attachments = N_("There are no attachments");
100
101 static void compose_status_line(char *buf, size_t buflen, size_t col, int cols,
102                                 struct Menu *menu, const char *p);
103
104 /**
105  * struct ComposeRedrawData - Keep track when the compose screen needs redrawing
106  */
107 struct ComposeRedrawData
108 {
109   struct Email *email;
110   char *fcc;
111 #ifdef USE_AUTOCRYPT
112   enum AutocryptRec autocrypt_rec;
113   int autocrypt_rec_override;
114 #endif
115   struct MuttWindow *win;
116 };
117
118 #define CHECK_COUNT                                                            \
119   if (actx->idxlen == 0)                                                       \
120   {                                                                            \
121     mutt_error(_(There_are_no_attachments));                                   \
122     break;                                                                     \
123   }
124
125 #define CUR_ATTACH actx->idx[actx->v2r[menu->current]]
126
127 /**
128  * enum HeaderField - Ordered list of headers for the compose screen
129  *
130  * The position of various fields on the compose screen.
131  */
132 enum HeaderField
133 {
134   HDR_FROM = 0, ///< "From:" field
135   HDR_TO,       ///< "To:" field
136   HDR_CC,       ///< "Cc:" field
137   HDR_BCC,      ///< "Bcc:" field
138   HDR_SUBJECT,  ///< "Subject:" field
139   HDR_REPLYTO,  ///< "Reply-To:" field
140   HDR_FCC,      ///< "Fcc:" (save folder) field
141 #ifdef MIXMASTER
142   HDR_MIX, ///< "Mix:" field (Mixmaster chain)
143 #endif
144   HDR_CRYPT,     ///< "Security:" field (encryption/signing info)
145   HDR_CRYPTINFO, ///< "Sign as:" field (encryption/signing info)
146 #ifdef USE_AUTOCRYPT
147   HDR_AUTOCRYPT,
148 #endif
149 #ifdef USE_NNTP
150   HDR_NEWSGROUPS, ///< "Newsgroups:" field
151   HDR_FOLLOWUPTO, ///< "Followup-To:" field
152   HDR_XCOMMENTTO, ///< "X-Comment-To:" field
153 #endif
154   HDR_ATTACH_TITLE, ///< The "-- Attachments" line
155   HDR_ATTACH        ///< Where to start printing the attachments
156 };
157
158 int HeaderPadding[HDR_ATTACH_TITLE] = { 0 };
159 int MaxHeaderWidth = 0;
160
161 #define HDR_XOFFSET MaxHeaderWidth
162 #define W (rd->win->cols - MaxHeaderWidth)
163
164 static const char *const Prompts[] = {
165   /* L10N: Compose menu field.  May not want to translate. */
166   N_("From: "),
167   /* L10N: Compose menu field.  May not want to translate. */
168   N_("To: "),
169   /* L10N: Compose menu field.  May not want to translate. */
170   N_("Cc: "),
171   /* L10N: Compose menu field.  May not want to translate. */
172   N_("Bcc: "),
173   /* L10N: Compose menu field.  May not want to translate. */
174   N_("Subject: "),
175   /* L10N: Compose menu field.  May not want to translate. */
176   N_("Reply-To: "),
177   /* L10N: Compose menu field.  May not want to translate. */
178   N_("Fcc: "),
179 #ifdef MIXMASTER
180   /* L10N: "Mix" refers to the MixMaster chain for anonymous email */
181   N_("Mix: "),
182 #endif
183   /* L10N: Compose menu field.  Holds "Encrypt", "Sign" related information */
184   N_("Security: "),
185   /* L10N:
186      This string is used by the compose menu.
187      Since it is hidden by default, it does not increase the
188      indentation of other compose menu fields.  However, if possible,
189      it should not be longer than the other compose menu fields.
190      Since it shares the row with "Encrypt with:", it should not be longer
191      than 15-20 character cells.  */
192   N_("Sign as: "),
193 #ifdef USE_AUTOCRYPT
194   // L10N: The compose menu autocrypt line
195   N_("Autocrypt: "),
196 #endif
197 #ifdef USE_NNTP
198   /* L10N: Compose menu field.  May not want to translate. */
199   N_("Newsgroups: "),
200   /* L10N: Compose menu field.  May not want to translate. */
201   N_("Followup-To: "),
202   /* L10N: Compose menu field.  May not want to translate. */
203   N_("X-Comment-To: "),
204 #endif
205 };
206
207 static const struct Mapping ComposeHelp[] = {
208   { N_("Send"), OP_COMPOSE_SEND_MESSAGE },
209   { N_("Abort"), OP_EXIT },
210   /* L10N: compose menu help line entry */
211   { N_("To"), OP_COMPOSE_EDIT_TO },
212   /* L10N: compose menu help line entry */
213   { N_("CC"), OP_COMPOSE_EDIT_CC },
214   /* L10N: compose menu help line entry */
215   { N_("Subj"), OP_COMPOSE_EDIT_SUBJECT },
216   { N_("Attach file"), OP_COMPOSE_ATTACH_FILE },
217   { N_("Descrip"), OP_COMPOSE_EDIT_DESCRIPTION },
218   { N_("Help"), OP_HELP },
219   { NULL, 0 },
220 };
221
222 #ifdef USE_NNTP
223 static struct Mapping ComposeNewsHelp[] = {
224   { N_("Send"), OP_COMPOSE_SEND_MESSAGE },
225   { N_("Abort"), OP_EXIT },
226   { N_("Newsgroups"), OP_COMPOSE_EDIT_NEWSGROUPS },
227   { N_("Subj"), OP_COMPOSE_EDIT_SUBJECT },
228   { N_("Attach file"), OP_COMPOSE_ATTACH_FILE },
229   { N_("Descrip"), OP_COMPOSE_EDIT_DESCRIPTION },
230   { N_("Help"), OP_HELP },
231   { NULL, 0 },
232 };
233 #endif
234
235 #ifdef USE_AUTOCRYPT
236 static const char *AutocryptRecUiFlags[] = {
237   /* L10N: Autocrypt recommendation flag: off.
238    * This is displayed when Autocrypt is turned off. */
239   N_("Off"),
240   /* L10N: Autocrypt recommendation flag: no.
241    * This is displayed when Autocrypt cannot encrypt to the recipients. */
242   N_("No"),
243   /* L10N: Autocrypt recommendation flag: discouraged.
244    * This is displayed when Autocrypt believes encryption should not be used.
245    * This might occur if one of the recipient Autocrypt Keys has not been
246    * used recently, or if the only key available is a Gossip Header key. */
247   N_("Discouraged"),
248   /* L10N: Autocrypt recommendation flag: available.
249    * This is displayed when Autocrypt believes encryption is possible, but
250    * leaves enabling it up to the sender.  Probably because "prefer encrypt"
251    * is not set in both the sender and recipient keys. */
252   N_("Available"),
253   /* L10N: Autocrypt recommendation flag: yes.
254    * This is displayed when Autocrypt would normally enable encryption
255    * automatically. */
256   N_("Yes"),
257 };
258 #endif
259
260 /**
261  * calc_header_width_padding - Calculate the width needed for the compose labels
262  * @param idx      Store the result at this index of HeaderPadding
263  * @param header   Header string
264  * @param calc_max If true, calculate the maximum width
265  */
266 static void calc_header_width_padding(int idx, const char *header, bool calc_max)
267 {
268   int width;
269
270   HeaderPadding[idx] = mutt_str_strlen(header);
271   width = mutt_strwidth(header);
272   if (calc_max && (MaxHeaderWidth < width))
273     MaxHeaderWidth = width;
274   HeaderPadding[idx] -= width;
275 }
276
277 /**
278  * init_header_padding - Calculate how much padding the compose table will need
279  *
280  * The padding needed for each header is strlen() + max_width - strwidth().
281  *
282  * calc_header_width_padding sets each entry in HeaderPadding to strlen -
283  * width.  Then, afterwards, we go through and add max_width to each entry.
284  */
285 static void init_header_padding(void)
286 {
287   static bool done = false;
288
289   if (done)
290     return;
291   done = true;
292
293   for (int i = 0; i < HDR_ATTACH_TITLE; i++)
294   {
295     if (i == HDR_CRYPTINFO)
296       continue;
297     calc_header_width_padding(i, _(Prompts[i]), true);
298   }
299
300   /* Don't include "Sign as: " in the MaxHeaderWidth calculation.  It
301    * doesn't show up by default, and so can make the indentation of
302    * the other fields look funny. */
303   calc_header_width_padding(HDR_CRYPTINFO, _(Prompts[HDR_CRYPTINFO]), false);
304
305   for (int i = 0; i < HDR_ATTACH_TITLE; i++)
306   {
307     HeaderPadding[i] += MaxHeaderWidth;
308     if (HeaderPadding[i] < 0)
309       HeaderPadding[i] = 0;
310   }
311 }
312
313 /**
314  * snd_make_entry - Format a menu item for the attachment list - Implements Menu::menu_make_entry()
315  */
316 static void snd_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
317 {
318   struct AttachCtx *actx = menu->data;
319
320   mutt_expando_format(buf, buflen, 0, menu->indexwin->cols, NONULL(C_AttachFormat),
321                       attach_format_str, (unsigned long) (actx->idx[actx->v2r[line]]),
322                       MUTT_FORMAT_STAT_FILE | MUTT_FORMAT_ARROWCURSOR);
323 }
324
325 #ifdef USE_AUTOCRYPT
326 /**
327  * autocrypt_compose_menu - Autocrypt compose settings
328  * @param e Email
329  */
330 static void autocrypt_compose_menu(struct Email *e)
331 {
332   /* L10N:
333      The compose menu autocrypt prompt.
334      (e)ncrypt enables encryption via autocrypt.
335      (c)lear sets cleartext.
336      (a)utomatic defers to the recommendation.
337   */
338   const char *prompt = _("Autocrypt: (e)ncrypt, (c)lear, (a)utomatic?");
339
340   /* L10N:
341      The letter corresponding to the compose menu autocrypt prompt
342      (e)ncrypt, (c)lear, (a)utomatic
343    */
344   const char *letters = _("eca");
345
346   int choice = mutt_multi_choice(prompt, letters);
347   switch (choice)
348   {
349     case 1:
350       e->security |= (SEC_AUTOCRYPT | SEC_AUTOCRYPT_OVERRIDE);
351       e->security &= ~(SEC_ENCRYPT | SEC_SIGN | SEC_OPPENCRYPT | SEC_INLINE);
352       break;
353     case 2:
354       e->security &= ~SEC_AUTOCRYPT;
355       e->security |= SEC_AUTOCRYPT_OVERRIDE;
356       break;
357     case 3:
358       e->security &= ~SEC_AUTOCRYPT_OVERRIDE;
359       if (C_CryptOpportunisticEncrypt)
360         e->security |= SEC_OPPENCRYPT;
361       break;
362   }
363 }
364 #endif
365
366 /**
367  * redraw_crypt_lines - Update the encryption info in the compose window
368  * @param rd Email and other compose data
369  */
370 static void redraw_crypt_lines(struct ComposeRedrawData *rd)
371 {
372   struct Email *e = rd->email;
373
374   mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
375   mutt_window_mvprintw(rd->win, HDR_CRYPT, 0, "%*s", HeaderPadding[HDR_CRYPT],
376                        _(Prompts[HDR_CRYPT]));
377   mutt_curses_set_color(MT_COLOR_NORMAL);
378
379   if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0)
380   {
381     mutt_window_addstr(_("Not supported"));
382     return;
383   }
384
385   if ((e->security & (SEC_ENCRYPT | SEC_SIGN)) == (SEC_ENCRYPT | SEC_SIGN))
386   {
387     mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_BOTH);
388     mutt_window_addstr(_("Sign, Encrypt"));
389   }
390   else if (e->security & SEC_ENCRYPT)
391   {
392     mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_ENCRYPT);
393     mutt_window_addstr(_("Encrypt"));
394   }
395   else if (e->security & SEC_SIGN)
396   {
397     mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_SIGN);
398     mutt_window_addstr(_("Sign"));
399   }
400   else
401   {
402     /* L10N: This refers to the encryption of the email, e.g. "Security: None" */
403     mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_NONE);
404     mutt_window_addstr(_("None"));
405   }
406   mutt_curses_set_color(MT_COLOR_NORMAL);
407
408   if ((e->security & (SEC_ENCRYPT | SEC_SIGN)))
409   {
410     if (((WithCrypto & APPLICATION_PGP) != 0) && (e->security & APPLICATION_PGP))
411     {
412       if ((e->security & SEC_INLINE))
413         mutt_window_addstr(_(" (inline PGP)"));
414       else
415         mutt_window_addstr(_(" (PGP/MIME)"));
416     }
417     else if (((WithCrypto & APPLICATION_SMIME) != 0) && (e->security & APPLICATION_SMIME))
418       mutt_window_addstr(_(" (S/MIME)"));
419   }
420
421   if (C_CryptOpportunisticEncrypt && (e->security & SEC_OPPENCRYPT))
422     mutt_window_addstr(_(" (OppEnc mode)"));
423
424   mutt_window_clrtoeol(rd->win);
425   mutt_window_move(rd->win, HDR_CRYPTINFO, 0);
426   mutt_window_clrtoeol(rd->win);
427
428   if (((WithCrypto & APPLICATION_PGP) != 0) &&
429       (e->security & APPLICATION_PGP) && (e->security & SEC_SIGN))
430   {
431     mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
432     mutt_window_printf("%*s", HeaderPadding[HDR_CRYPTINFO], _(Prompts[HDR_CRYPTINFO]));
433     mutt_curses_set_color(MT_COLOR_NORMAL);
434     mutt_window_printf("%s", C_PgpSignAs ? C_PgpSignAs : _("<default>"));
435   }
436
437   if (((WithCrypto & APPLICATION_SMIME) != 0) &&
438       (e->security & APPLICATION_SMIME) && (e->security & SEC_SIGN))
439   {
440     mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
441     mutt_window_printf("%*s", HeaderPadding[HDR_CRYPTINFO], _(Prompts[HDR_CRYPTINFO]));
442     mutt_curses_set_color(MT_COLOR_NORMAL);
443     mutt_window_printf("%s", C_SmimeSignAs ? C_SmimeSignAs : _("<default>"));
444   }
445
446   if (((WithCrypto & APPLICATION_SMIME) != 0) && (e->security & APPLICATION_SMIME) &&
447       (e->security & SEC_ENCRYPT) && C_SmimeEncryptWith)
448   {
449     mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
450     mutt_window_mvprintw(rd->win, HDR_CRYPTINFO, 40, "%s", _("Encrypt with: "));
451     mutt_curses_set_color(MT_COLOR_NORMAL);
452     mutt_window_printf("%s", NONULL(C_SmimeEncryptWith));
453   }
454
455 #ifdef USE_AUTOCRYPT
456   mutt_window_move(rd->win, HDR_AUTOCRYPT, 0);
457   mutt_window_clrtoeol(rd->win);
458   if (C_Autocrypt)
459   {
460     mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
461     mutt_window_printf("%*s", HeaderPadding[HDR_AUTOCRYPT], _(Prompts[HDR_AUTOCRYPT]));
462     mutt_curses_set_color(MT_COLOR_NORMAL);
463     if (e->security & SEC_AUTOCRYPT)
464     {
465       mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_ENCRYPT);
466       mutt_window_addstr(_("Encrypt"));
467     }
468     else
469     {
470       mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_NONE);
471       mutt_window_addstr(_("Off"));
472     }
473
474     mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
475     mutt_window_mvprintw(rd->win, HDR_AUTOCRYPT, 40, "%s",
476                          /* L10N:
477                              The autocrypt compose menu Recommendation field.
478                              Displays the output of the recommendation engine
479                              (Off, No, Discouraged, Available, Yes)
480                           */
481                          _("Recommendation: "));
482     mutt_curses_set_color(MT_COLOR_NORMAL);
483     mutt_window_printf("%s", _(AutocryptRecUiFlags[rd->autocrypt_rec]));
484   }
485 #endif
486 }
487
488 /**
489  * update_crypt_info - Update the crypto info
490  * @param rd Email and other compose data
491  */
492 static void update_crypt_info(struct ComposeRedrawData *rd)
493 {
494   struct Email *e = rd->email;
495
496   if (C_CryptOpportunisticEncrypt)
497     crypt_opportunistic_encrypt(e);
498
499 #ifdef USE_AUTOCRYPT
500   if (C_Autocrypt)
501   {
502     rd->autocrypt_rec = mutt_autocrypt_ui_recommendation(e, NULL);
503
504     /* Anything that enables SEC_ENCRYPT or SEC_SIGN, or turns on SMIME
505      * overrides autocrypt, be it oppenc or the user having turned on
506      * those flags manually. */
507     if (e->security & (SEC_ENCRYPT | SEC_SIGN | APPLICATION_SMIME))
508       e->security &= ~(SEC_AUTOCRYPT | SEC_AUTOCRYPT_OVERRIDE);
509     else
510     {
511       if (!(e->security & SEC_AUTOCRYPT_OVERRIDE))
512       {
513         if (rd->autocrypt_rec == AUTOCRYPT_REC_YES)
514         {
515           e->security |= SEC_AUTOCRYPT;
516           e->security &= ~SEC_INLINE;
517         }
518         else
519           e->security &= ~SEC_AUTOCRYPT;
520       }
521     }
522   }
523 #endif
524
525   redraw_crypt_lines(rd);
526 }
527
528 #ifdef MIXMASTER
529 /**
530  * redraw_mix_line - Redraw the Mixmaster chain
531  * @param chain List of chain links
532  * @param rd    Email and other compose data
533  */
534 static void redraw_mix_line(struct ListHead *chain, struct ComposeRedrawData *rd)
535 {
536   char *t = NULL;
537
538   mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
539   mutt_window_mvprintw(rd->win, HDR_MIX, 0, "%*s", HeaderPadding[HDR_MIX],
540                        _(Prompts[HDR_MIX]));
541   mutt_curses_set_color(MT_COLOR_NORMAL);
542
543   if (STAILQ_EMPTY(chain))
544   {
545     mutt_window_addstr(_("<no chain defined>"));
546     mutt_window_clrtoeol(rd->win);
547     return;
548   }
549
550   int c = 12;
551   struct ListNode *np = NULL;
552   STAILQ_FOREACH(np, chain, entries)
553   {
554     t = np->data;
555     if (t && (t[0] == '0') && (t[1] == '\0'))
556       t = "<random>";
557
558     if (c + mutt_str_strlen(t) + 2 >= rd->win->cols)
559       break;
560
561     mutt_window_addstr(NONULL(t));
562     if (STAILQ_NEXT(np, entries))
563       mutt_window_addstr(", ");
564
565     c += mutt_str_strlen(t) + 2;
566   }
567 }
568 #endif /* MIXMASTER */
569
570 /**
571  * check_attachments - Check if any attachments have changed or been deleted
572  * @param actx Attachment context
573  * @retval  0 Success
574  * @retval -1 Error
575  */
576 static int check_attachments(struct AttachCtx *actx)
577 {
578   int rc = -1;
579   struct stat st;
580   struct Buffer *pretty = NULL, *msg = NULL;
581
582   for (int i = 0; i < actx->idxlen; i++)
583   {
584     if (actx->idx[i]->content->type == TYPE_MULTIPART)
585       continue;
586     if (stat(actx->idx[i]->content->filename, &st) != 0)
587     {
588       if (!pretty)
589         pretty = mutt_buffer_pool_get();
590       mutt_buffer_strcpy(pretty, actx->idx[i]->content->filename);
591       mutt_buffer_pretty_mailbox(pretty);
592       /* L10N:
593          This message is displayed in the compose menu when an attachment
594          doesn't stat.  %d is the attachment number and %s is the
595          attachment filename.
596          The filename is located last to avoid a long path hiding the
597          error message.
598       */
599       mutt_error(_("Attachment #%d no longer exists: %s"), i + 1, mutt_b2s(pretty));
600       goto cleanup;
601     }
602
603     if (actx->idx[i]->content->stamp < st.st_mtime)
604     {
605       if (!pretty)
606         pretty = mutt_buffer_pool_get();
607       mutt_buffer_strcpy(pretty, actx->idx[i]->content->filename);
608       mutt_buffer_pretty_mailbox(pretty);
609
610       if (!msg)
611         msg = mutt_buffer_pool_get();
612       /* L10N:
613          This message is displayed in the compose menu when an attachment
614          is modified behind the scenes.  %d is the attachment number
615          and %s is the attachment filename.
616          The filename is located last to avoid a long path hiding the
617          prompt question.
618       */
619       mutt_buffer_printf(msg, _("Attachment #%d modified. Update encoding for %s?"),
620                          i + 1, mutt_b2s(pretty));
621
622       enum QuadOption ans = mutt_yesorno(mutt_b2s(msg), MUTT_YES);
623       if (ans == MUTT_YES)
624         mutt_update_encoding(actx->idx[i]->content);
625       else if (ans == MUTT_ABORT)
626         goto cleanup;
627     }
628   }
629
630   rc = 0;
631
632 cleanup:
633   mutt_buffer_pool_release(&pretty);
634   mutt_buffer_pool_release(&msg);
635   return rc;
636 }
637
638 /**
639  * draw_envelope_addr - Write addresses to the compose window
640  * @param line Line to write to (index into Prompts)
641  * @param al   Address list to write
642  * @param rd  Email and other compose data
643  */
644 static void draw_envelope_addr(int line, struct AddressList *al, struct ComposeRedrawData *rd)
645 {
646   char buf[1024];
647
648   buf[0] = '\0';
649   mutt_addrlist_write(buf, sizeof(buf), al, true);
650   mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
651   mutt_window_mvprintw(rd->win, line, 0, "%*s", HeaderPadding[line], _(Prompts[line]));
652   mutt_curses_set_color(MT_COLOR_NORMAL);
653   mutt_paddstr(W, buf);
654 }
655
656 /**
657  * draw_envelope - Write the email headers to the compose window
658  * @param rd  Email and other compose data
659  */
660 static void draw_envelope(struct ComposeRedrawData *rd)
661 {
662   struct Email *e = rd->email;
663   char *fcc = rd->fcc;
664
665   draw_envelope_addr(HDR_FROM, &e->env->from, rd);
666 #ifdef USE_NNTP
667   if (!OptNewsSend)
668   {
669 #endif
670     draw_envelope_addr(HDR_TO, &e->env->to, rd);
671     draw_envelope_addr(HDR_CC, &e->env->cc, rd);
672     draw_envelope_addr(HDR_BCC, &e->env->bcc, rd);
673 #ifdef USE_NNTP
674   }
675   else
676   {
677     mutt_window_mvprintw(rd->win, HDR_TO, 0, "%*s",
678                          HeaderPadding[HDR_NEWSGROUPS], Prompts[HDR_NEWSGROUPS]);
679     mutt_paddstr(W, NONULL(e->env->newsgroups));
680     mutt_window_mvprintw(rd->win, HDR_CC, 0, "%*s",
681                          HeaderPadding[HDR_FOLLOWUPTO], Prompts[HDR_FOLLOWUPTO]);
682     mutt_paddstr(W, NONULL(e->env->followup_to));
683     if (C_XCommentTo)
684     {
685       mutt_window_mvprintw(rd->win, HDR_BCC, 0, "%*s",
686                            HeaderPadding[HDR_XCOMMENTTO], Prompts[HDR_XCOMMENTTO]);
687       mutt_paddstr(W, NONULL(e->env->x_comment_to));
688     }
689   }
690 #endif
691
692   mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
693   mutt_window_mvprintw(rd->win, HDR_SUBJECT, 0, "%*s",
694                        HeaderPadding[HDR_SUBJECT], _(Prompts[HDR_SUBJECT]));
695   mutt_curses_set_color(MT_COLOR_NORMAL);
696   mutt_paddstr(W, NONULL(e->env->subject));
697
698   draw_envelope_addr(HDR_REPLYTO, &e->env->reply_to, rd);
699
700   mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
701   mutt_window_mvprintw(rd->win, HDR_FCC, 0, "%*s", HeaderPadding[HDR_FCC],
702                        _(Prompts[HDR_FCC]));
703   mutt_curses_set_color(MT_COLOR_NORMAL);
704   mutt_paddstr(W, fcc);
705
706   if (WithCrypto)
707     redraw_crypt_lines(rd);
708
709 #ifdef MIXMASTER
710   redraw_mix_line(&e->chain, rd);
711 #endif
712
713   mutt_curses_set_color(MT_COLOR_STATUS);
714   mutt_window_mvaddstr(rd->win, HDR_ATTACH_TITLE, 0, _("-- Attachments"));
715   mutt_window_clrtoeol(rd->win);
716
717   mutt_curses_set_color(MT_COLOR_NORMAL);
718 }
719
720 /**
721  * edit_address_list - Let the user edit the address list
722  * @param[in]     line Index into the Prompts lists
723  * @param[in,out] al   AddressList to edit
724  * @param rd  Email and other compose data
725  */
726 static void edit_address_list(int line, struct AddressList *al, struct ComposeRedrawData *rd)
727 {
728   char buf[8192] = { 0 }; /* needs to be large for alias expansion */
729   char *err = NULL;
730
731   mutt_addrlist_to_local(al);
732   mutt_addrlist_write(buf, sizeof(buf), al, false);
733   if (mutt_get_field(_(Prompts[line]), buf, sizeof(buf), MUTT_ALIAS) == 0)
734   {
735     mutt_addrlist_clear(al);
736     mutt_addrlist_parse2(al, buf);
737     mutt_expand_aliases(al);
738   }
739
740   if (mutt_addrlist_to_intl(al, &err) != 0)
741   {
742     mutt_error(_("Bad IDN: '%s'"), err);
743     mutt_refresh();
744     FREE(&err);
745   }
746
747   /* redraw the expanded list so the user can see the result */
748   buf[0] = '\0';
749   mutt_addrlist_write(buf, sizeof(buf), al, true);
750   mutt_window_move(rd->win, line, HDR_XOFFSET);
751   mutt_paddstr(W, buf);
752 }
753
754 /**
755  * delete_attachment - Delete an attachment
756  * @param actx Attachment context
757  * @param x    Index number of attachment
758  * @retval  0 Success
759  * @retval -1 Error
760  */
761 static int delete_attachment(struct AttachCtx *actx, int x)
762 {
763   struct AttachPtr **idx = actx->idx;
764   int rindex = actx->v2r[x];
765
766   if ((rindex == 0) && (actx->idxlen == 1))
767   {
768     mutt_error(_("You may not delete the only attachment"));
769     idx[rindex]->content->tagged = false;
770     return -1;
771   }
772
773   for (int y = 0; y < actx->idxlen; y++)
774   {
775     if (idx[y]->content->next == idx[rindex]->content)
776     {
777       idx[y]->content->next = idx[rindex]->content->next;
778       break;
779     }
780   }
781
782   idx[rindex]->content->next = NULL;
783   /* mutt_make_message_attach() creates body->parts, shared by
784    * body->email->content.  If we NULL out that, it creates a memory
785    * leak because mutt_free_body() frees body->parts, not
786    * body->email->content.
787    *
788    * Other ci_send_message() message constructors are careful to free
789    * any body->parts, removing depth:
790    *  - mutt_prepare_template() used by postponed, resent, and draft files
791    *  - mutt_copy_body() used by the recvattach menu and $forward_attachments.
792    *
793    * I believe it is safe to completely remove the "content->parts =
794    * NULL" statement.  But for safety, am doing so only for the case
795    * it must be avoided: message attachments.
796    */
797   if (!idx[rindex]->content->email)
798     idx[rindex]->content->parts = NULL;
799   mutt_body_free(&(idx[rindex]->content));
800   FREE(&idx[rindex]->tree);
801   FREE(&idx[rindex]);
802   for (; rindex < actx->idxlen - 1; rindex++)
803     idx[rindex] = idx[rindex + 1];
804   idx[actx->idxlen - 1] = NULL;
805   actx->idxlen--;
806
807   return 0;
808 }
809
810 /**
811  * mutt_gen_compose_attach_list - Generate the attachment list for the compose screen
812  * @param actx        Attachment context
813  * @param m           Attachment
814  * @param parent_type Attachment type, e.g #TYPE_MULTIPART
815  * @param level       Nesting depth of attachment
816  */
817 static void mutt_gen_compose_attach_list(struct AttachCtx *actx, struct Body *m,
818                                          int parent_type, int level)
819 {
820   for (; m; m = m->next)
821   {
822     if ((m->type == TYPE_MULTIPART) && m->parts &&
823         (!(WithCrypto & APPLICATION_PGP) || !mutt_is_multipart_encrypted(m)))
824     {
825       mutt_gen_compose_attach_list(actx, m->parts, m->type, level);
826     }
827     else
828     {
829       struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
830       mutt_actx_add_attach(actx, ap);
831       ap->content = m;
832       m->aptr = ap;
833       ap->parent_type = parent_type;
834       ap->level = level;
835
836       /* We don't support multipart messages in the compose menu yet */
837     }
838   }
839 }
840
841 /**
842  * mutt_update_compose_menu - Redraw the compose window
843  * @param actx Attachment context
844  * @param menu Current menu
845  * @param init If true, initialise the attachment list
846  */
847 static void mutt_update_compose_menu(struct AttachCtx *actx, struct Menu *menu, bool init)
848 {
849   if (init)
850   {
851     mutt_gen_compose_attach_list(actx, actx->email->content, -1, 0);
852     mutt_attach_init(actx);
853     menu->data = actx;
854   }
855
856   mutt_update_tree(actx);
857
858   menu->max = actx->vcount;
859   if (menu->max)
860   {
861     if (menu->current >= menu->max)
862       menu->current = menu->max - 1;
863   }
864   else
865     menu->current = 0;
866
867   menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
868 }
869
870 /**
871  * update_idx - Add a new attchment to the message
872  * @param menu Current menu
873  * @param actx Attachment context
874  * @param ap   Attachment to add
875  */
876 static void update_idx(struct Menu *menu, struct AttachCtx *actx, struct AttachPtr *ap)
877 {
878   ap->level = (actx->idxlen > 0) ? actx->idx[actx->idxlen - 1]->level : 0;
879   if (actx->idxlen)
880     actx->idx[actx->idxlen - 1]->content->next = ap->content;
881   ap->content->aptr = ap;
882   mutt_actx_add_attach(actx, ap);
883   mutt_update_compose_menu(actx, menu, false);
884   menu->current = actx->vcount - 1;
885 }
886
887 /**
888  * compose_custom_redraw - Redraw the compose menu - Implements Menu::menu_custom_redraw()
889  */
890 static void compose_custom_redraw(struct Menu *menu)
891 {
892   struct ComposeRedrawData *rd = menu->redraw_data;
893
894   if (!rd)
895     return;
896
897   if (menu->redraw & REDRAW_FULL)
898   {
899     menu_redraw_full(menu);
900
901     draw_envelope(rd);
902     menu->offset = HDR_ATTACH;
903     menu->pagelen = menu->indexwin->rows - HDR_ATTACH;
904   }
905
906   menu_check_recenter(menu);
907
908   if (menu->redraw & REDRAW_STATUS)
909   {
910     char buf[1024];
911     compose_status_line(buf, sizeof(buf), 0, menu->statuswin->cols, menu,
912                         NONULL(C_ComposeFormat));
913     mutt_window_move(menu->statuswin, 0, 0);
914     mutt_curses_set_color(MT_COLOR_STATUS);
915     mutt_paddstr(menu->statuswin->cols, buf);
916     mutt_curses_set_color(MT_COLOR_NORMAL);
917     menu->redraw &= ~REDRAW_STATUS;
918   }
919
920 #ifdef USE_SIDEBAR
921   if (menu->redraw & REDRAW_SIDEBAR)
922     menu_redraw_sidebar(menu);
923 #endif
924
925   if (menu->redraw & REDRAW_INDEX)
926     menu_redraw_index(menu);
927   else if (menu->redraw & (REDRAW_MOTION | REDRAW_MOTION_RESYNC))
928     menu_redraw_motion(menu);
929   else if (menu->redraw == REDRAW_CURRENT)
930     menu_redraw_current(menu);
931 }
932
933 /**
934  * compose_attach_swap - Swap two adjacent entries in the attachment list
935  * @param[in]  msg   Body of email
936  * @param[out] idx   Array of Attachments
937  * @param[in]  first Index of first attachment to swap
938  */
939 static void compose_attach_swap(struct Body *msg, struct AttachPtr **idx, short first)
940 {
941   /* Reorder Body pointers.
942    * Must traverse msg from top since Body has no previous ptr.  */
943   for (struct Body *part = msg; part; part = part->next)
944   {
945     if (part->next == idx[first]->content)
946     {
947       idx[first]->content->next = idx[first + 1]->content->next;
948       idx[first + 1]->content->next = idx[first]->content;
949       part->next = idx[first + 1]->content;
950       break;
951     }
952   }
953
954   /* Reorder index */
955   struct AttachPtr *saved = idx[first];
956   idx[first] = idx[first + 1];
957   idx[first + 1] = saved;
958
959   /* Swap ptr->num */
960   int i = idx[first]->num;
961   idx[first]->num = idx[first + 1]->num;
962   idx[first + 1]->num = i;
963 }
964
965 /**
966  * cum_attachs_size - Cumulative Attachments Size
967  * @param menu Menu listing attachments
968  * @retval num Bytes in attachments
969  *
970  * Returns the total number of bytes used by the attachments in the attachment
971  * list _after_ content-transfer-encodings have been applied.
972  */
973 static unsigned long cum_attachs_size(struct Menu *menu)
974 {
975   size_t s = 0;
976   struct AttachCtx *actx = menu->data;
977   struct AttachPtr **idx = actx->idx;
978   struct Content *info = NULL;
979   struct Body *b = NULL;
980
981   for (unsigned short i = 0; i < actx->idxlen; i++)
982   {
983     b = idx[i]->content;
984
985     if (!b->content)
986       b->content = mutt_get_content_info(b->filename, b);
987
988     info = b->content;
989     if (info)
990     {
991       switch (b->encoding)
992       {
993         case ENC_QUOTED_PRINTABLE:
994           s += 3 * (info->lobin + info->hibin) + info->ascii + info->crlf;
995           break;
996         case ENC_BASE64:
997           s += (4 * (info->lobin + info->hibin + info->ascii + info->crlf)) / 3;
998           break;
999         default:
1000           s += info->lobin + info->hibin + info->ascii + info->crlf;
1001           break;
1002       }
1003     }
1004   }
1005
1006   return s;
1007 }
1008
1009 /**
1010  * compose_format_str - Create the status bar string for compose mode - Implements ::format_t
1011  *
1012  * | Expando | Description
1013  * |:--------|:--------------------------------------------------------
1014  * | \%a     | Total number of attachments
1015  * | \%h     | Local hostname
1016  * | \%l     | Approximate size (in bytes) of the current message
1017  * | \%v     | NeoMutt version string
1018  */
1019 static const char *compose_format_str(char *buf, size_t buflen, size_t col, int cols,
1020                                       char op, const char *src, const char *prec,
1021                                       const char *if_str, const char *else_str,
1022                                       unsigned long data, MuttFormatFlags flags)
1023 {
1024   char fmt[128], tmp[128];
1025   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
1026   struct Menu *menu = (struct Menu *) data;
1027
1028   *buf = '\0';
1029   switch (op)
1030   {
1031     case 'a': /* total number of attachments */
1032       snprintf(fmt, sizeof(fmt), "%%%sd", prec);
1033       snprintf(buf, buflen, fmt, menu->max);
1034       break;
1035
1036     case 'h': /* hostname */
1037       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
1038       snprintf(buf, buflen, fmt, NONULL(ShortHostname));
1039       break;
1040
1041     case 'l': /* approx length of current message in bytes */
1042       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
1043       mutt_str_pretty_size(tmp, sizeof(tmp), menu ? cum_attachs_size(menu) : 0);
1044       snprintf(buf, buflen, fmt, tmp);
1045       break;
1046
1047     case 'v':
1048       snprintf(buf, buflen, "%s", mutt_make_version());
1049       break;
1050
1051     case 0:
1052       *buf = '\0';
1053       return src;
1054
1055     default:
1056       snprintf(buf, buflen, "%%%s%c", prec, op);
1057       break;
1058   }
1059
1060   if (optional)
1061     compose_status_line(buf, buflen, col, cols, menu, if_str);
1062   else if (flags & MUTT_FORMAT_OPTIONAL)
1063     compose_status_line(buf, buflen, col, cols, menu, else_str);
1064
1065   return src;
1066 }
1067
1068 /**
1069  * compose_status_line - Compose the string for the status bar
1070  * @param[out] buf    Buffer in which to save string
1071  * @param[in]  buflen Buffer length
1072  * @param[in]  col    Starting column
1073  * @param[in]  cols   Number of screen columns
1074  * @param[in]  menu   Current menu
1075  * @param[in]  src    Printf-like format string
1076  */
1077 static void compose_status_line(char *buf, size_t buflen, size_t col, int cols,
1078                                 struct Menu *menu, const char *src)
1079 {
1080   mutt_expando_format(buf, buflen, col, cols, src, compose_format_str,
1081                       (unsigned long) menu, MUTT_FORMAT_NO_FLAGS);
1082 }
1083
1084 /**
1085  * mutt_compose_menu - Allow the user to edit the message envelope
1086  * @param e      Email to fill
1087  * @param fcc    Buffer to save FCC
1088  * @param fcclen Length of FCC buffer
1089  * @param e_cur  Current message
1090  * @param flags  Flags, e.g. #MUTT_COMPOSE_NOFREEHEADER
1091  * @retval  1 Message should be postponed
1092  * @retval  0 Normal exit
1093  * @retval -1 Abort message
1094  */
1095 int mutt_compose_menu(struct Email *e, char *fcc, size_t fcclen, struct Email *e_cur, int flags)
1096 {
1097   char helpstr[1024]; // This isn't copied by the help bar
1098   char buf[PATH_MAX];
1099   int op_close = OP_NULL;
1100   int rc = -1;
1101   bool loop = true;
1102   bool fcc_set = false; /* has the user edited the Fcc: field ? */
1103   struct ComposeRedrawData redraw = { 0 };
1104   struct ComposeRedrawData *rd = &redraw;
1105 #ifdef USE_NNTP
1106   bool news = OptNewsSend; /* is it a news article ? */
1107 #endif
1108
1109   init_header_padding();
1110
1111   rd->email = e;
1112   rd->fcc = fcc;
1113   rd->win = MuttIndexWindow;
1114
1115   struct Menu *menu = mutt_menu_new(MENU_COMPOSE);
1116   menu->offset = HDR_ATTACH;
1117   menu->menu_make_entry = snd_make_entry;
1118   menu->menu_tag = attach_tag;
1119 #ifdef USE_NNTP
1120   if (news)
1121     menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_COMPOSE, ComposeNewsHelp);
1122   else
1123 #endif
1124     menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_COMPOSE, ComposeHelp);
1125   menu->menu_custom_redraw = compose_custom_redraw;
1126   menu->redraw_data = rd;
1127   mutt_menu_push_current(menu);
1128
1129   struct AttachCtx *actx = mutt_actx_new();
1130   actx->email = e;
1131   mutt_update_compose_menu(actx, menu, true);
1132
1133   update_crypt_info(rd);
1134
1135   /* Since this is rather long lived, we don't use the pool */
1136   struct Buffer fname = mutt_buffer_make(PATH_MAX);
1137
1138   while (loop)
1139   {
1140 #ifdef USE_NNTP
1141     OptNews = false; /* for any case */
1142 #endif
1143     const int op = mutt_menu_loop(menu);
1144     switch (op)
1145     {
1146       case OP_COMPOSE_EDIT_FROM:
1147         edit_address_list(HDR_FROM, &e->env->from, rd);
1148         update_crypt_info(rd);
1149         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1150         break;
1151
1152       case OP_COMPOSE_EDIT_TO:
1153 #ifdef USE_NNTP
1154         if (news)
1155           break;
1156 #endif
1157         edit_address_list(HDR_TO, &e->env->to, rd);
1158         update_crypt_info(rd);
1159         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1160         break;
1161
1162       case OP_COMPOSE_EDIT_BCC:
1163 #ifdef USE_NNTP
1164         if (news)
1165           break;
1166 #endif
1167         edit_address_list(HDR_BCC, &e->env->bcc, rd);
1168         update_crypt_info(rd);
1169         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1170         break;
1171
1172       case OP_COMPOSE_EDIT_CC:
1173 #ifdef USE_NNTP
1174         if (news)
1175           break;
1176 #endif
1177         edit_address_list(HDR_CC, &e->env->cc, rd);
1178         update_crypt_info(rd);
1179         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1180         break;
1181 #ifdef USE_NNTP
1182       case OP_COMPOSE_EDIT_NEWSGROUPS:
1183         if (!news)
1184           break;
1185         if (e->env->newsgroups)
1186           mutt_str_strfcpy(buf, e->env->newsgroups, sizeof(buf));
1187         else
1188           buf[0] = '\0';
1189         if (mutt_get_field("Newsgroups: ", buf, sizeof(buf), 0) == 0)
1190         {
1191           mutt_str_replace(&e->env->newsgroups, buf);
1192           mutt_window_move(menu->indexwin, HDR_TO, HDR_XOFFSET);
1193           if (e->env->newsgroups)
1194             mutt_paddstr(W, e->env->newsgroups);
1195           else
1196             mutt_window_clrtoeol(menu->indexwin);
1197         }
1198         break;
1199
1200       case OP_COMPOSE_EDIT_FOLLOWUP_TO:
1201         if (!news)
1202           break;
1203         if (e->env->followup_to)
1204           mutt_str_strfcpy(buf, e->env->followup_to, sizeof(buf));
1205         else
1206           buf[0] = '\0';
1207         if (mutt_get_field("Followup-To: ", buf, sizeof(buf), 0) == 0)
1208         {
1209           mutt_str_replace(&e->env->followup_to, buf);
1210           mutt_window_move(menu->indexwin, HDR_CC, HDR_XOFFSET);
1211           if (e->env->followup_to)
1212             mutt_paddstr(W, e->env->followup_to);
1213           else
1214             mutt_window_clrtoeol(menu->indexwin);
1215         }
1216         break;
1217
1218       case OP_COMPOSE_EDIT_X_COMMENT_TO:
1219         if (!(news && C_XCommentTo))
1220           break;
1221         if (e->env->x_comment_to)
1222           mutt_str_strfcpy(buf, e->env->x_comment_to, sizeof(buf));
1223         else
1224           buf[0] = '\0';
1225         if (mutt_get_field("X-Comment-To: ", buf, sizeof(buf), 0) == 0)
1226         {
1227           mutt_str_replace(&e->env->x_comment_to, buf);
1228           mutt_window_move(menu->indexwin, HDR_BCC, HDR_XOFFSET);
1229           if (e->env->x_comment_to)
1230             mutt_paddstr(W, e->env->x_comment_to);
1231           else
1232             mutt_window_clrtoeol(menu->indexwin);
1233         }
1234         break;
1235 #endif
1236
1237       case OP_COMPOSE_EDIT_SUBJECT:
1238         if (e->env->subject)
1239           mutt_str_strfcpy(buf, e->env->subject, sizeof(buf));
1240         else
1241           buf[0] = '\0';
1242         if (mutt_get_field(_("Subject: "), buf, sizeof(buf), 0) == 0)
1243         {
1244           mutt_str_replace(&e->env->subject, buf);
1245           mutt_window_move(menu->indexwin, HDR_SUBJECT, HDR_XOFFSET);
1246           if (e->env->subject)
1247             mutt_paddstr(W, e->env->subject);
1248           else
1249             mutt_window_clrtoeol(menu->indexwin);
1250         }
1251         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1252         break;
1253
1254       case OP_COMPOSE_EDIT_REPLY_TO:
1255         edit_address_list(HDR_REPLYTO, &e->env->reply_to, rd);
1256         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1257         break;
1258
1259       case OP_COMPOSE_EDIT_FCC:
1260         mutt_str_strfcpy(buf, fcc, sizeof(buf));
1261         if (mutt_get_field(_("Fcc: "), buf, sizeof(buf), MUTT_FILE | MUTT_CLEAR) == 0)
1262         {
1263           mutt_str_strfcpy(fcc, buf, fcclen);
1264           mutt_pretty_mailbox(fcc, fcclen);
1265           mutt_window_move(menu->indexwin, HDR_FCC, HDR_XOFFSET);
1266           mutt_paddstr(W, fcc);
1267           fcc_set = true;
1268         }
1269         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1270         break;
1271
1272       case OP_COMPOSE_EDIT_MESSAGE:
1273         if (C_Editor && (mutt_str_strcmp("builtin", C_Editor) != 0) && !C_EditHeaders)
1274         {
1275           mutt_rfc3676_space_unstuff(e);
1276           mutt_edit_file(C_Editor, e->content->filename);
1277           mutt_rfc3676_space_stuff(e);
1278           mutt_update_encoding(e->content);
1279           menu->redraw = REDRAW_FULL;
1280           mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1281           break;
1282         }
1283         /* fallthrough */
1284
1285       case OP_COMPOSE_EDIT_HEADERS:
1286         mutt_rfc3676_space_unstuff(e);
1287         if ((mutt_str_strcmp("builtin", C_Editor) != 0) &&
1288             ((op == OP_COMPOSE_EDIT_HEADERS) || ((op == OP_COMPOSE_EDIT_MESSAGE) && C_EditHeaders)))
1289         {
1290           const char *tag = NULL;
1291           char *err = NULL;
1292           mutt_env_to_local(e->env);
1293           mutt_edit_headers(NONULL(C_Editor), e->content->filename, e, fcc, fcclen);
1294           if (mutt_env_to_intl(e->env, &tag, &err))
1295           {
1296             mutt_error(_("Bad IDN in '%s': '%s'"), tag, err);
1297             FREE(&err);
1298           }
1299           update_crypt_info(rd);
1300         }
1301         else
1302         {
1303           /* this is grouped with OP_COMPOSE_EDIT_HEADERS because the
1304            * attachment list could change if the user invokes ~v to edit
1305            * the message with headers, in which we need to execute the
1306            * code below to regenerate the index array */
1307           mutt_builtin_editor(e->content->filename, e, e_cur);
1308         }
1309
1310         mutt_rfc3676_space_stuff(e);
1311         mutt_update_encoding(e->content);
1312
1313         /* attachments may have been added */
1314         if (actx->idxlen && actx->idx[actx->idxlen - 1]->content->next)
1315         {
1316           mutt_actx_entries_free(actx);
1317           mutt_update_compose_menu(actx, menu, true);
1318         }
1319
1320         menu->redraw = REDRAW_FULL;
1321         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1322         break;
1323
1324       case OP_COMPOSE_ATTACH_KEY:
1325       {
1326         if (!(WithCrypto & APPLICATION_PGP))
1327           break;
1328         struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1329         ap->content = crypt_pgp_make_key_attachment();
1330         if (ap->content)
1331         {
1332           update_idx(menu, actx, ap);
1333           menu->redraw |= REDRAW_INDEX;
1334         }
1335         else
1336           FREE(&ap);
1337
1338         menu->redraw |= REDRAW_STATUS;
1339
1340         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1341         break;
1342       }
1343
1344       case OP_COMPOSE_MOVE_UP:
1345         if (menu->current == 0)
1346         {
1347           mutt_error(_("Attachment is already at top"));
1348           break;
1349         }
1350         if (menu->current == 1)
1351         {
1352           mutt_error(_("The fundamental part can't be moved"));
1353           break;
1354         }
1355         compose_attach_swap(e->content, actx->idx, menu->current - 1);
1356         menu->redraw = REDRAW_INDEX;
1357         menu->current--;
1358         break;
1359
1360       case OP_COMPOSE_MOVE_DOWN:
1361         if (menu->current == (actx->idxlen - 1))
1362         {
1363           mutt_error(_("Attachment is already at bottom"));
1364           break;
1365         }
1366         if (menu->current == 0)
1367         {
1368           mutt_error(_("The fundamental part can't be moved"));
1369           break;
1370         }
1371         compose_attach_swap(e->content, actx->idx, menu->current);
1372         menu->redraw = REDRAW_INDEX;
1373         menu->current++;
1374         break;
1375
1376       case OP_COMPOSE_GROUP_ALTS:
1377       {
1378         if (menu->tagged < 2)
1379         {
1380           mutt_error(
1381               _("Grouping 'alternatives' requires at least 2 tagged messages"));
1382           break;
1383         }
1384
1385         struct Body *group = mutt_body_new();
1386         group->type = TYPE_MULTIPART;
1387         group->subtype = mutt_str_strdup("alternative");
1388         group->disposition = DISP_INLINE;
1389
1390         struct Body *alts = NULL;
1391         /* group tagged message into a multipart/alternative */
1392         struct Body *bptr = e->content;
1393         for (int i = 0; bptr;)
1394         {
1395           if (bptr->tagged)
1396           {
1397             bptr->tagged = false;
1398             bptr->disposition = DISP_INLINE;
1399
1400             /* for first match, set group desc according to match */
1401 #define ALTS_TAG "Alternatives for \"%s\""
1402             if (!group->description)
1403             {
1404               char *p = bptr->description ? bptr->description : bptr->filename;
1405               if (p)
1406               {
1407                 group->description =
1408                     mutt_mem_calloc(1, strlen(p) + strlen(ALTS_TAG) + 1);
1409                 sprintf(group->description, ALTS_TAG, p);
1410               }
1411             }
1412
1413             /* append bptr to the alts list,
1414              * and remove from the e->content list */
1415             if (!alts)
1416             {
1417               group->parts = bptr;
1418               alts = bptr;
1419               bptr = bptr->next;
1420               alts->next = NULL;
1421             }
1422             else
1423             {
1424               alts->next = bptr;
1425               bptr = bptr->next;
1426               alts = alts->next;
1427               alts->next = NULL;
1428             }
1429
1430             for (int j = i; j < actx->idxlen - 1; j++)
1431             {
1432               actx->idx[j] = actx->idx[j + 1];
1433               actx->idx[j + 1] = NULL; /* for debug reason */
1434             }
1435             actx->idxlen--;
1436           }
1437           else
1438           {
1439             bptr = bptr->next;
1440             i++;
1441           }
1442         }
1443
1444         group->next = NULL;
1445         mutt_generate_boundary(&group->parameter);
1446
1447         /* if no group desc yet, make one up */
1448         if (!group->description)
1449           group->description = mutt_str_strdup("unknown alternative group");
1450
1451         struct AttachPtr *gptr = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1452         gptr->content = group;
1453         update_idx(menu, actx, gptr);
1454         menu->redraw = REDRAW_INDEX;
1455         break;
1456       }
1457
1458       case OP_COMPOSE_GROUP_LINGUAL:
1459       {
1460         if (menu->tagged < 2)
1461         {
1462           mutt_error(
1463               _("Grouping 'multilingual' requires at least 2 tagged messages"));
1464           break;
1465         }
1466
1467         /* traverse to see whether all the parts have Content-Language: set */
1468         int tagged_with_lang_num = 0;
1469         for (struct Body *b = e->content; b; b = b->next)
1470           if (b->tagged && b->language && *b->language)
1471             tagged_with_lang_num++;
1472
1473         if (menu->tagged != tagged_with_lang_num)
1474         {
1475           if (mutt_yesorno(
1476                   _("Not all parts have 'Content-Language' set, continue?"), MUTT_YES) != MUTT_YES)
1477           {
1478             mutt_message(_("Not sending this message"));
1479             break;
1480           }
1481         }
1482
1483         struct Body *group = mutt_body_new();
1484         group->type = TYPE_MULTIPART;
1485         group->subtype = mutt_str_strdup("multilingual");
1486         group->disposition = DISP_INLINE;
1487
1488         struct Body *alts = NULL;
1489         /* group tagged message into a multipart/multilingual */
1490         struct Body *bptr = e->content;
1491         for (int i = 0; bptr;)
1492         {
1493           if (bptr->tagged)
1494           {
1495             bptr->tagged = false;
1496             bptr->disposition = DISP_INLINE;
1497
1498             /* for first match, set group desc according to match */
1499 #define LINGUAL_TAG "Multilingual part for \"%s\""
1500             if (!group->description)
1501             {
1502               char *p = bptr->description ? bptr->description : bptr->filename;
1503               if (p)
1504               {
1505                 group->description =
1506                     mutt_mem_calloc(1, strlen(p) + strlen(LINGUAL_TAG) + 1);
1507                 sprintf(group->description, LINGUAL_TAG, p);
1508               }
1509             }
1510
1511             /* append bptr to the alts list,
1512              * and remove from the e->content list */
1513             if (!alts)
1514             {
1515               group->parts = bptr;
1516               alts = bptr;
1517               bptr = bptr->next;
1518               alts->next = NULL;
1519             }
1520             else
1521             {
1522               alts->next = bptr;
1523               bptr = bptr->next;
1524               alts = alts->next;
1525               alts->next = NULL;
1526             }
1527
1528             for (int j = i; j < actx->idxlen - 1; j++)
1529             {
1530               actx->idx[j] = actx->idx[j + 1];
1531               actx->idx[j + 1] = NULL; /* for debug reason */
1532             }
1533             actx->idxlen--;
1534           }
1535           else
1536           {
1537             bptr = bptr->next;
1538             i++;
1539           }
1540         }
1541
1542         group->next = NULL;
1543         mutt_generate_boundary(&group->parameter);
1544
1545         /* if no group desc yet, make one up */
1546         if (!group->description)
1547           group->description = mutt_str_strdup("unknown multilingual group");
1548
1549         struct AttachPtr *gptr = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1550         gptr->content = group;
1551         update_idx(menu, actx, gptr);
1552         menu->redraw = REDRAW_INDEX;
1553         break;
1554       }
1555
1556       case OP_COMPOSE_ATTACH_FILE:
1557       {
1558         char *prompt = _("Attach file");
1559         int numfiles = 0;
1560         char **files = NULL;
1561
1562         mutt_buffer_reset(&fname);
1563         if ((mutt_buffer_enter_fname_full(prompt, &fname, false, true, &files,
1564                                           &numfiles, MUTT_SEL_MULTI) == -1) ||
1565             mutt_buffer_is_empty(&fname))
1566         {
1567           break;
1568         }
1569
1570         bool error = false;
1571         if (numfiles > 1)
1572         {
1573           mutt_message(ngettext("Attaching selected file...",
1574                                 "Attaching selected files...", numfiles));
1575         }
1576         for (int i = 0; i < numfiles; i++)
1577         {
1578           char *att = files[i];
1579           struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1580           ap->unowned = true;
1581           ap->content = mutt_make_file_attach(att);
1582           if (ap->content)
1583             update_idx(menu, actx, ap);
1584           else
1585           {
1586             error = true;
1587             mutt_error(_("Unable to attach %s"), att);
1588             FREE(&ap);
1589           }
1590           FREE(&files[i]);
1591         }
1592
1593         FREE(&files);
1594         if (!error)
1595           mutt_clear_error();
1596
1597         menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
1598         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1599         break;
1600       }
1601
1602       case OP_COMPOSE_ATTACH_MESSAGE:
1603 #ifdef USE_NNTP
1604       case OP_COMPOSE_ATTACH_NEWS_MESSAGE:
1605 #endif
1606       {
1607         mutt_buffer_reset(&fname);
1608         char *prompt = _("Open mailbox to attach message from");
1609
1610 #ifdef USE_NNTP
1611         OptNews = false;
1612         if (Context && (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE))
1613         {
1614           CurrentNewsSrv = nntp_select_server(Context->mailbox, C_NewsServer, false);
1615           if (!CurrentNewsSrv)
1616             break;
1617
1618           prompt = _("Open newsgroup to attach message from");
1619           OptNews = true;
1620         }
1621 #endif
1622
1623         if (Context)
1624 #ifdef USE_NNTP
1625           if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->mailbox->magic == MUTT_NNTP))
1626 #endif
1627           {
1628             mutt_buffer_strcpy(&fname, mailbox_path(Context->mailbox));
1629             mutt_buffer_pretty_mailbox(&fname);
1630           }
1631
1632         if ((mutt_buffer_enter_fname(prompt, &fname, true) == -1) ||
1633             mutt_buffer_is_empty(&fname))
1634         {
1635           break;
1636         }
1637
1638 #ifdef USE_NNTP
1639         if (OptNews)
1640           nntp_expand_path(fname.data, fname.dsize, &CurrentNewsSrv->conn->account);
1641         else
1642 #endif
1643           mutt_buffer_expand_path(&fname);
1644 #ifdef USE_IMAP
1645         if (imap_path_probe(mutt_b2s(&fname), NULL) != MUTT_IMAP)
1646 #endif
1647 #ifdef USE_POP
1648           if (pop_path_probe(mutt_b2s(&fname), NULL) != MUTT_POP)
1649 #endif
1650 #ifdef USE_NNTP
1651             if (!OptNews && (nntp_path_probe(mutt_b2s(&fname), NULL) != MUTT_NNTP))
1652 #endif
1653               /* check to make sure the file exists and is readable */
1654               if (access(mutt_b2s(&fname), R_OK) == -1)
1655               {
1656                 mutt_perror(mutt_b2s(&fname));
1657                 break;
1658               }
1659
1660         menu->redraw = REDRAW_FULL;
1661
1662         struct Mailbox *m = mx_path_resolve(mutt_b2s(&fname));
1663         struct Context *ctx = mx_mbox_open(m, MUTT_READONLY);
1664         if (!ctx)
1665         {
1666           mutt_error(_("Unable to open mailbox %s"), mutt_b2s(&fname));
1667           mailbox_free(&m);
1668           break;
1669         }
1670
1671         if (ctx->mailbox->msg_count == 0)
1672         {
1673           mx_mbox_close(&ctx);
1674           mutt_error(_("No messages in that folder"));
1675           break;
1676         }
1677
1678         struct Context *ctx_cur = Context; /* remember current folder and sort methods */
1679         int old_sort = C_Sort; /* C_Sort, SortAux could be changed in mutt_index_menu() */
1680         int old_sort_aux = C_SortAux;
1681
1682         Context = ctx;
1683         OptAttachMsg = true;
1684         mutt_message(_("Tag the messages you want to attach"));
1685         op_close = mutt_index_menu();
1686         OptAttachMsg = false;
1687
1688         if (!Context)
1689         {
1690           /* go back to the folder we started from */
1691           Context = ctx_cur;
1692           /* Restore old $sort and $sort_aux */
1693           C_Sort = old_sort;
1694           C_SortAux = old_sort_aux;
1695           menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
1696           break;
1697         }
1698
1699         for (int i = 0; i < Context->mailbox->msg_count; i++)
1700         {
1701           if (!message_is_tagged(Context, i))
1702             continue;
1703
1704           struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1705           ap->content = mutt_make_message_attach(Context->mailbox,
1706                                                  Context->mailbox->emails[i], true);
1707           if (ap->content)
1708             update_idx(menu, actx, ap);
1709           else
1710           {
1711             mutt_error(_("Unable to attach"));
1712             FREE(&ap);
1713           }
1714         }
1715         menu->redraw |= REDRAW_FULL;
1716
1717         if (op_close == OP_QUIT)
1718           mx_mbox_close(&Context);
1719         else
1720         {
1721           mx_fastclose_mailbox(Context->mailbox);
1722           ctx_free(&Context);
1723         }
1724
1725         /* go back to the folder we started from */
1726         Context = ctx_cur;
1727         /* Restore old $sort and $sort_aux */
1728         C_Sort = old_sort;
1729         C_SortAux = old_sort_aux;
1730         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1731         break;
1732       }
1733
1734       case OP_DELETE:
1735         CHECK_COUNT;
1736         if (CUR_ATTACH->unowned)
1737           CUR_ATTACH->content->unlink = false;
1738         if (delete_attachment(actx, menu->current) == -1)
1739           break;
1740         mutt_update_compose_menu(actx, menu, false);
1741         if (menu->current == 0)
1742           e->content = actx->idx[0]->content;
1743
1744         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1745         break;
1746
1747       case OP_COMPOSE_TOGGLE_RECODE:
1748       {
1749         CHECK_COUNT;
1750         if (!mutt_is_text_part(CUR_ATTACH->content))
1751         {
1752           mutt_error(_("Recoding only affects text attachments"));
1753           break;
1754         }
1755         CUR_ATTACH->content->noconv = !CUR_ATTACH->content->noconv;
1756         if (CUR_ATTACH->content->noconv)
1757           mutt_message(_("The current attachment won't be converted"));
1758         else
1759           mutt_message(_("The current attachment will be converted"));
1760         menu->redraw = REDRAW_CURRENT;
1761         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1762         break;
1763       }
1764
1765       case OP_COMPOSE_EDIT_DESCRIPTION:
1766         CHECK_COUNT;
1767         mutt_str_strfcpy(
1768             buf, CUR_ATTACH->content->description ? CUR_ATTACH->content->description : "",
1769             sizeof(buf));
1770         /* header names should not be translated */
1771         if (mutt_get_field("Description: ", buf, sizeof(buf), 0) == 0)
1772         {
1773           mutt_str_replace(&CUR_ATTACH->content->description, buf);
1774           menu->redraw = REDRAW_CURRENT;
1775         }
1776         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1777         break;
1778
1779       case OP_COMPOSE_UPDATE_ENCODING:
1780         CHECK_COUNT;
1781         if (menu->tagprefix)
1782         {
1783           struct Body *top = NULL;
1784           for (top = e->content; top; top = top->next)
1785           {
1786             if (top->tagged)
1787               mutt_update_encoding(top);
1788           }
1789           menu->redraw = REDRAW_FULL;
1790         }
1791         else
1792         {
1793           mutt_update_encoding(CUR_ATTACH->content);
1794           menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1795         }
1796         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1797         break;
1798
1799       case OP_COMPOSE_TOGGLE_DISPOSITION:
1800         /* toggle the content-disposition between inline/attachment */
1801         CUR_ATTACH->content->disposition =
1802             (CUR_ATTACH->content->disposition == DISP_INLINE) ? DISP_ATTACH : DISP_INLINE;
1803         menu->redraw = REDRAW_CURRENT;
1804         break;
1805
1806       case OP_EDIT_TYPE:
1807         CHECK_COUNT;
1808         {
1809           mutt_edit_content_type(NULL, CUR_ATTACH->content, NULL);
1810
1811           /* this may have been a change to text/something */
1812           mutt_update_encoding(CUR_ATTACH->content);
1813
1814           menu->redraw = REDRAW_CURRENT;
1815         }
1816         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1817         break;
1818
1819       case OP_COMPOSE_EDIT_LANGUAGE:
1820         CHECK_COUNT;
1821         buf[0] = '\0'; /* clear buffer first */
1822         if (CUR_ATTACH->content->language)
1823           mutt_str_strfcpy(buf, CUR_ATTACH->content->language, sizeof(buf));
1824         if (mutt_get_field("Content-Language: ", buf, sizeof(buf), 0) == 0)
1825         {
1826           CUR_ATTACH->content->language = mutt_str_strdup(buf);
1827           menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1828           mutt_clear_error();
1829         }
1830         else
1831           mutt_warning(_("Empty 'Content-Language'"));
1832         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1833         break;
1834
1835       case OP_COMPOSE_EDIT_ENCODING:
1836         CHECK_COUNT;
1837         mutt_str_strfcpy(buf, ENCODING(CUR_ATTACH->content->encoding), sizeof(buf));
1838         if ((mutt_get_field("Content-Transfer-Encoding: ", buf, sizeof(buf), 0) == 0) &&
1839             (buf[0] != '\0'))
1840         {
1841           int enc = mutt_check_encoding(buf);
1842           if ((enc != ENC_OTHER) && (enc != ENC_UUENCODED))
1843           {
1844             CUR_ATTACH->content->encoding = enc;
1845             menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1846             mutt_clear_error();
1847           }
1848           else
1849             mutt_error(_("Invalid encoding"));
1850         }
1851         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1852         break;
1853
1854       case OP_COMPOSE_SEND_MESSAGE:
1855         /* Note: We don't invoke send2-hook here, since we want to leave
1856          * users an opportunity to change settings from the ":" prompt.  */
1857         if (check_attachments(actx) != 0)
1858         {
1859           menu->redraw = REDRAW_FULL;
1860           break;
1861         }
1862
1863 #ifdef MIXMASTER
1864         if (!STAILQ_EMPTY(&e->chain) && (mix_check_message(e) != 0))
1865           break;
1866 #endif
1867
1868         if (!fcc_set && *fcc)
1869         {
1870           enum QuadOption ans =
1871               query_quadoption(C_Copy, _("Save a copy of this message?"));
1872           if (ans == MUTT_ABORT)
1873             break;
1874           else if (ans == MUTT_NO)
1875             *fcc = '\0';
1876         }
1877
1878         loop = false;
1879         rc = 0;
1880         break;
1881
1882       case OP_COMPOSE_EDIT_FILE:
1883         CHECK_COUNT;
1884         mutt_edit_file(NONULL(C_Editor), CUR_ATTACH->content->filename);
1885         mutt_update_encoding(CUR_ATTACH->content);
1886         menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1887         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1888         break;
1889
1890       case OP_COMPOSE_TOGGLE_UNLINK:
1891         CHECK_COUNT;
1892         CUR_ATTACH->content->unlink = !CUR_ATTACH->content->unlink;
1893
1894         menu->redraw = REDRAW_INDEX;
1895         /* No send2hook since this doesn't change the message. */
1896         break;
1897
1898       case OP_COMPOSE_GET_ATTACHMENT:
1899         CHECK_COUNT;
1900         if (menu->tagprefix)
1901         {
1902           for (struct Body *top = e->content; top; top = top->next)
1903           {
1904             if (top->tagged)
1905               mutt_get_tmp_attachment(top);
1906           }
1907           menu->redraw = REDRAW_FULL;
1908         }
1909         else if (mutt_get_tmp_attachment(CUR_ATTACH->content) == 0)
1910           menu->redraw = REDRAW_CURRENT;
1911
1912         /* No send2hook since this doesn't change the message. */
1913         break;
1914
1915       case OP_COMPOSE_RENAME_ATTACHMENT:
1916       {
1917         CHECK_COUNT;
1918         char *src = NULL;
1919         if (CUR_ATTACH->content->d_filename)
1920           src = CUR_ATTACH->content->d_filename;
1921         else
1922           src = CUR_ATTACH->content->filename;
1923         mutt_buffer_strcpy(&fname, mutt_path_basename(NONULL(src)));
1924         int ret = mutt_buffer_get_field(_("Send attachment with name: "), &fname, MUTT_FILE);
1925         if (ret == 0)
1926         {
1927           /* As opposed to RENAME_FILE, we don't check buf[0] because it's
1928            * valid to set an empty string here, to erase what was set */
1929           mutt_str_replace(&CUR_ATTACH->content->d_filename, mutt_b2s(&fname));
1930           menu->redraw = REDRAW_CURRENT;
1931         }
1932         break;
1933       }
1934
1935       case OP_COMPOSE_RENAME_FILE:
1936         CHECK_COUNT;
1937         mutt_buffer_strcpy(&fname, CUR_ATTACH->content->filename);
1938         mutt_buffer_pretty_mailbox(&fname);
1939         if ((mutt_buffer_get_field(_("Rename to: "), &fname, MUTT_FILE) == 0) &&
1940             !mutt_buffer_is_empty(&fname))
1941         {
1942           struct stat st;
1943           if (stat(CUR_ATTACH->content->filename, &st) == -1)
1944           {
1945             /* L10N: "stat" is a system call. Do "man 2 stat" for more information. */
1946             mutt_error(_("Can't stat %s: %s"), mutt_b2s(&fname), strerror(errno));
1947             break;
1948           }
1949
1950           mutt_buffer_expand_path(&fname);
1951           if (mutt_file_rename(CUR_ATTACH->content->filename, mutt_b2s(&fname)))
1952             break;
1953
1954           mutt_str_replace(&CUR_ATTACH->content->filename, mutt_b2s(&fname));
1955           menu->redraw = REDRAW_CURRENT;
1956
1957           if (CUR_ATTACH->content->stamp >= st.st_mtime)
1958             mutt_stamp_attachment(CUR_ATTACH->content);
1959         }
1960         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1961         break;
1962
1963       case OP_COMPOSE_NEW_MIME:
1964       {
1965         mutt_buffer_reset(&fname);
1966         if ((mutt_buffer_get_field(_("New file: "), &fname, MUTT_FILE) != 0) ||
1967             mutt_buffer_is_empty(&fname))
1968         {
1969           continue;
1970         }
1971         mutt_buffer_expand_path(&fname);
1972
1973         /* Call to lookup_mime_type () ?  maybe later */
1974         char type[256] = { 0 };
1975         if ((mutt_get_field("Content-Type: ", type, sizeof(type), 0) != 0) ||
1976             (type[0] == '\0'))
1977         {
1978           continue;
1979         }
1980
1981         char *p = strchr(type, '/');
1982         if (!p)
1983         {
1984           mutt_error(_("Content-Type is of the form base/sub"));
1985           continue;
1986         }
1987         *p++ = 0;
1988         enum ContentType itype = mutt_check_mime_type(type);
1989         if (itype == TYPE_OTHER)
1990         {
1991           mutt_error(_("Unknown Content-Type %s"), type);
1992           continue;
1993         }
1994         struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1995         /* Touch the file */
1996         FILE *fp = mutt_file_fopen(mutt_b2s(&fname), "w");
1997         if (!fp)
1998         {
1999           mutt_error(_("Can't create file %s"), mutt_b2s(&fname));
2000           FREE(&ap);
2001           continue;
2002         }
2003         mutt_file_fclose(&fp);
2004
2005         ap->content = mutt_make_file_attach(mutt_b2s(&fname));
2006         if (!ap->content)
2007         {
2008           mutt_error(_("What we have here is a failure to make an attachment"));
2009           FREE(&ap);
2010           continue;
2011         }
2012         update_idx(menu, actx, ap);
2013
2014         CUR_ATTACH->content->type = itype;
2015         mutt_str_replace(&CUR_ATTACH->content->subtype, p);
2016         CUR_ATTACH->content->unlink = true;
2017         menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
2018
2019         if (mutt_compose_attachment(CUR_ATTACH->content))
2020         {
2021           mutt_update_encoding(CUR_ATTACH->content);
2022           menu->redraw = REDRAW_FULL;
2023         }
2024         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2025         break;
2026       }
2027
2028       case OP_COMPOSE_EDIT_MIME:
2029         CHECK_COUNT;
2030         if (mutt_edit_attachment(CUR_ATTACH->content))
2031         {
2032           mutt_update_encoding(CUR_ATTACH->content);
2033           menu->redraw = REDRAW_FULL;
2034         }
2035         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2036         break;
2037
2038       case OP_VIEW_ATTACH:
2039       case OP_DISPLAY_HEADERS:
2040         CHECK_COUNT;
2041         mutt_attach_display_loop(menu, op, NULL, actx, false);
2042         menu->redraw = REDRAW_FULL;
2043         /* no send2hook, since this doesn't modify the message */
2044         break;
2045
2046       case OP_SAVE:
2047         CHECK_COUNT;
2048         mutt_save_attachment_list(actx, NULL, menu->tagprefix,
2049                                   CUR_ATTACH->content, NULL, menu);
2050         /* no send2hook, since this doesn't modify the message */
2051         break;
2052
2053       case OP_PRINT:
2054         CHECK_COUNT;
2055         mutt_print_attachment_list(actx, NULL, menu->tagprefix, CUR_ATTACH->content);
2056         /* no send2hook, since this doesn't modify the message */
2057         break;
2058
2059       case OP_PIPE:
2060       case OP_FILTER:
2061         CHECK_COUNT;
2062         mutt_pipe_attachment_list(actx, NULL, menu->tagprefix,
2063                                   CUR_ATTACH->content, (op == OP_FILTER));
2064         if (op == OP_FILTER) /* cte might have changed */
2065           menu->redraw = menu->tagprefix ? REDRAW_FULL : REDRAW_CURRENT;
2066         menu->redraw |= REDRAW_STATUS;
2067         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2068         break;
2069
2070       case OP_EXIT:
2071       {
2072         enum QuadOption ans =
2073             query_quadoption(C_Postpone, _("Save (postpone) draft message?"));
2074         if (ans == MUTT_NO)
2075         {
2076           for (int i = 0; i < actx->idxlen; i++)
2077             if (actx->idx[i]->unowned)
2078               actx->idx[i]->content->unlink = false;
2079
2080           if (!(flags & MUTT_COMPOSE_NOFREEHEADER))
2081           {
2082             for (int i = 0; i < actx->idxlen; i++)
2083             {
2084               /* avoid freeing other attachments */
2085               actx->idx[i]->content->next = NULL;
2086               /* See the comment in delete_attachment() */
2087               if (!actx->idx[i]->content->email)
2088                 actx->idx[i]->content->parts = NULL;
2089               mutt_body_free(&actx->idx[i]->content);
2090             }
2091           }
2092           rc = -1;
2093           loop = false;
2094           break;
2095         }
2096         else if (ans == MUTT_ABORT)
2097           break; /* abort */
2098       }
2099         /* fallthrough */
2100
2101       case OP_COMPOSE_POSTPONE_MESSAGE:
2102         if (check_attachments(actx) != 0)
2103         {
2104           menu->redraw = REDRAW_FULL;
2105           break;
2106         }
2107
2108         loop = false;
2109         rc = 1;
2110         break;
2111
2112       case OP_COMPOSE_ISPELL:
2113         endwin();
2114         snprintf(buf, sizeof(buf), "%s -x %s", NONULL(C_Ispell), e->content->filename);
2115         if (mutt_system(buf) == -1)
2116           mutt_error(_("Error running \"%s\""), buf);
2117         else
2118         {
2119           mutt_update_encoding(e->content);
2120           menu->redraw |= REDRAW_STATUS;
2121         }
2122         break;
2123
2124       case OP_COMPOSE_WRITE_MESSAGE:
2125         mutt_buffer_reset(&fname);
2126         if (Context)
2127         {
2128           mutt_buffer_strcpy(&fname, mailbox_path(Context->mailbox));
2129           mutt_buffer_pretty_mailbox(&fname);
2130         }
2131         if (actx->idxlen)
2132           e->content = actx->idx[0]->content;
2133         if ((mutt_buffer_enter_fname(_("Write message to mailbox"), &fname, true) != -1) &&
2134             !mutt_buffer_is_empty(&fname))
2135         {
2136           mutt_message(_("Writing message to %s ..."), mutt_b2s(&fname));
2137           mutt_buffer_expand_path(&fname);
2138
2139           if (e->content->next)
2140             e->content = mutt_make_multipart(e->content);
2141
2142           if (mutt_write_fcc(mutt_b2s(&fname), e, NULL, false, NULL, NULL) == 0)
2143             mutt_message(_("Message written"));
2144
2145           e->content = mutt_remove_multipart(e->content);
2146         }
2147         break;
2148
2149       case OP_COMPOSE_PGP_MENU:
2150         if (!(WithCrypto & APPLICATION_PGP))
2151           break;
2152         if (!crypt_has_module_backend(APPLICATION_PGP))
2153         {
2154           mutt_error(_("No PGP backend configured"));
2155           break;
2156         }
2157         if (((WithCrypto & APPLICATION_SMIME) != 0) && (e->security & APPLICATION_SMIME))
2158         {
2159           if (e->security & (SEC_ENCRYPT | SEC_SIGN))
2160           {
2161             if (mutt_yesorno(_("S/MIME already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
2162             {
2163               mutt_clear_error();
2164               break;
2165             }
2166             e->security &= ~(SEC_ENCRYPT | SEC_SIGN);
2167           }
2168           e->security &= ~APPLICATION_SMIME;
2169           e->security |= APPLICATION_PGP;
2170           update_crypt_info(rd);
2171         }
2172         e->security = crypt_pgp_send_menu(e);
2173         update_crypt_info(rd);
2174         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2175         break;
2176
2177       case OP_FORGET_PASSPHRASE:
2178         crypt_forget_passphrase();
2179         break;
2180
2181       case OP_COMPOSE_SMIME_MENU:
2182         if (!(WithCrypto & APPLICATION_SMIME))
2183           break;
2184         if (!crypt_has_module_backend(APPLICATION_SMIME))
2185         {
2186           mutt_error(_("No S/MIME backend configured"));
2187           break;
2188         }
2189
2190         if (((WithCrypto & APPLICATION_PGP) != 0) && (e->security & APPLICATION_PGP))
2191         {
2192           if (e->security & (SEC_ENCRYPT | SEC_SIGN))
2193           {
2194             if (mutt_yesorno(_("PGP already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
2195             {
2196               mutt_clear_error();
2197               break;
2198             }
2199             e->security &= ~(SEC_ENCRYPT | SEC_SIGN);
2200           }
2201           e->security &= ~APPLICATION_PGP;
2202           e->security |= APPLICATION_SMIME;
2203           update_crypt_info(rd);
2204         }
2205         e->security = crypt_smime_send_menu(e);
2206         update_crypt_info(rd);
2207         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2208         break;
2209
2210 #ifdef MIXMASTER
2211       case OP_COMPOSE_MIX:
2212         mix_make_chain(menu->indexwin, &e->chain, menu->indexwin->cols);
2213         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2214         break;
2215 #endif
2216 #ifdef USE_AUTOCRYPT
2217       case OP_COMPOSE_AUTOCRYPT_MENU:
2218         if (!C_Autocrypt)
2219           break;
2220
2221         if ((WithCrypto & APPLICATION_SMIME) && (e->security & APPLICATION_SMIME))
2222         {
2223           if (e->security & (SEC_ENCRYPT | SEC_SIGN))
2224           {
2225             if (mutt_yesorno(_("S/MIME already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
2226             {
2227               mutt_clear_error();
2228               break;
2229             }
2230             e->security &= ~(SEC_ENCRYPT | SEC_SIGN);
2231           }
2232           e->security &= ~APPLICATION_SMIME;
2233           e->security |= APPLICATION_PGP;
2234           update_crypt_info(rd);
2235         }
2236         autocrypt_compose_menu(e);
2237         update_crypt_info(rd);
2238         mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2239         break;
2240 #endif
2241     }
2242   }
2243
2244   mutt_buffer_dealloc(&fname);
2245
2246 #ifdef USE_AUTOCRYPT
2247   /* This is a fail-safe to make sure the bit isn't somehow turned
2248    * on.  The user could have disabled the option after setting SEC_AUTOCRYPT,
2249    * or perhaps resuming or replying to an autocrypt message.  */
2250   if (!C_Autocrypt)
2251     e->security &= ~SEC_AUTOCRYPT;
2252 #endif
2253
2254   mutt_menu_pop_current(menu);
2255   mutt_menu_free(&menu);
2256
2257   if (actx->idxlen)
2258     e->content = actx->idx[0]->content;
2259   else
2260     e->content = NULL;
2261
2262   mutt_actx_free(&actx);
2263
2264   return rc;
2265 }