3 * GUI editor for an email's headers
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>
11 * This program is free software: you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License as published by the Free Software
13 * Foundation, either version 2 of the License, or (at your option) any later
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
21 * You should have received a copy of the GNU General Public License along with
22 * this program. If not, see <http://www.gnu.org/licenses/>.
26 * @page compose GUI editor for an email's headers
28 * GUI editor for an email's headers
39 #include "mutt/mutt.h"
40 #include "address/lib.h"
41 #include "config/lib.h"
42 #include "email/lib.h"
44 #include "conn/conn.h"
54 #include "format_flags.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"
67 #include "ncrypt/ncrypt.h"
71 #include "recvattach.h"
82 #include "nntp/nntp.h"
88 #include "imap/imap.h"
91 #include "autocrypt/autocrypt.h"
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
99 static const char *There_are_no_attachments = N_("There are no attachments");
101 static void compose_status_line(char *buf, size_t buflen, size_t col, int cols,
102 struct Menu *menu, const char *p);
105 * struct ComposeRedrawData - Keep track when the compose screen needs redrawing
107 struct ComposeRedrawData
112 enum AutocryptRec autocrypt_rec;
113 int autocrypt_rec_override;
115 struct MuttWindow *win;
118 #define CHECK_COUNT \
119 if (actx->idxlen == 0) \
121 mutt_error(_(There_are_no_attachments)); \
125 #define CUR_ATTACH actx->idx[actx->v2r[menu->current]]
128 * enum HeaderField - Ordered list of headers for the compose screen
130 * The position of various fields on the compose screen.
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
142 HDR_MIX, ///< "Mix:" field (Mixmaster chain)
144 HDR_CRYPT, ///< "Security:" field (encryption/signing info)
145 HDR_CRYPTINFO, ///< "Sign as:" field (encryption/signing info)
150 HDR_NEWSGROUPS, ///< "Newsgroups:" field
151 HDR_FOLLOWUPTO, ///< "Followup-To:" field
152 HDR_XCOMMENTTO, ///< "X-Comment-To:" field
154 HDR_ATTACH_TITLE, ///< The "-- Attachments" line
155 HDR_ATTACH ///< Where to start printing the attachments
158 int HeaderPadding[HDR_ATTACH_TITLE] = { 0 };
159 int MaxHeaderWidth = 0;
161 #define HDR_XOFFSET MaxHeaderWidth
162 #define W (rd->win->cols - MaxHeaderWidth)
164 static const char *const Prompts[] = {
165 /* L10N: Compose menu field. May not want to translate. */
167 /* L10N: Compose menu field. May not want to translate. */
169 /* L10N: Compose menu field. May not want to translate. */
171 /* L10N: Compose menu field. May not want to translate. */
173 /* L10N: Compose menu field. May not want to translate. */
175 /* L10N: Compose menu field. May not want to translate. */
177 /* L10N: Compose menu field. May not want to translate. */
180 /* L10N: "Mix" refers to the MixMaster chain for anonymous email */
183 /* L10N: Compose menu field. Holds "Encrypt", "Sign" related information */
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. */
194 // L10N: The compose menu autocrypt line
198 /* L10N: Compose menu field. May not want to translate. */
200 /* L10N: Compose menu field. May not want to translate. */
202 /* L10N: Compose menu field. May not want to translate. */
203 N_("X-Comment-To: "),
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 },
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 },
236 static const char *AutocryptRecUiFlags[] = {
237 /* L10N: Autocrypt recommendation flag: off.
238 * This is displayed when Autocrypt is turned off. */
240 /* L10N: Autocrypt recommendation flag: no.
241 * This is displayed when Autocrypt cannot encrypt to the recipients. */
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. */
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. */
253 /* L10N: Autocrypt recommendation flag: yes.
254 * This is displayed when Autocrypt would normally enable encryption
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
266 static void calc_header_width_padding(int idx, const char *header, bool calc_max)
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;
278 * init_header_padding - Calculate how much padding the compose table will need
280 * The padding needed for each header is strlen() + max_width - strwidth().
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.
285 static void init_header_padding(void)
287 static bool done = false;
293 for (int i = 0; i < HDR_ATTACH_TITLE; i++)
295 if (i == HDR_CRYPTINFO)
297 calc_header_width_padding(i, _(Prompts[i]), true);
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);
305 for (int i = 0; i < HDR_ATTACH_TITLE; i++)
307 HeaderPadding[i] += MaxHeaderWidth;
308 if (HeaderPadding[i] < 0)
309 HeaderPadding[i] = 0;
314 * snd_make_entry - Format a menu item for the attachment list - Implements Menu::menu_make_entry()
316 static void snd_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
318 struct AttachCtx *actx = menu->data;
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);
327 * autocrypt_compose_menu - Autocrypt compose settings
330 static void autocrypt_compose_menu(struct Email *e)
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.
338 const char *prompt = _("Autocrypt: (e)ncrypt, (c)lear, (a)utomatic?");
341 The letter corresponding to the compose menu autocrypt prompt
342 (e)ncrypt, (c)lear, (a)utomatic
344 const char *letters = _("eca");
346 int choice = mutt_multi_choice(prompt, letters);
350 e->security |= (SEC_AUTOCRYPT | SEC_AUTOCRYPT_OVERRIDE);
351 e->security &= ~(SEC_ENCRYPT | SEC_SIGN | SEC_OPPENCRYPT | SEC_INLINE);
354 e->security &= ~SEC_AUTOCRYPT;
355 e->security |= SEC_AUTOCRYPT_OVERRIDE;
358 e->security &= ~SEC_AUTOCRYPT_OVERRIDE;
359 if (C_CryptOpportunisticEncrypt)
360 e->security |= SEC_OPPENCRYPT;
367 * redraw_crypt_lines - Update the encryption info in the compose window
368 * @param rd Email and other compose data
370 static void redraw_crypt_lines(struct ComposeRedrawData *rd)
372 struct Email *e = rd->email;
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);
379 if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0)
381 mutt_window_addstr(_("Not supported"));
385 if ((e->security & (SEC_ENCRYPT | SEC_SIGN)) == (SEC_ENCRYPT | SEC_SIGN))
387 mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_BOTH);
388 mutt_window_addstr(_("Sign, Encrypt"));
390 else if (e->security & SEC_ENCRYPT)
392 mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_ENCRYPT);
393 mutt_window_addstr(_("Encrypt"));
395 else if (e->security & SEC_SIGN)
397 mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_SIGN);
398 mutt_window_addstr(_("Sign"));
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"));
406 mutt_curses_set_color(MT_COLOR_NORMAL);
408 if ((e->security & (SEC_ENCRYPT | SEC_SIGN)))
410 if (((WithCrypto & APPLICATION_PGP) != 0) && (e->security & APPLICATION_PGP))
412 if ((e->security & SEC_INLINE))
413 mutt_window_addstr(_(" (inline PGP)"));
415 mutt_window_addstr(_(" (PGP/MIME)"));
417 else if (((WithCrypto & APPLICATION_SMIME) != 0) && (e->security & APPLICATION_SMIME))
418 mutt_window_addstr(_(" (S/MIME)"));
421 if (C_CryptOpportunisticEncrypt && (e->security & SEC_OPPENCRYPT))
422 mutt_window_addstr(_(" (OppEnc mode)"));
424 mutt_window_clrtoeol(rd->win);
425 mutt_window_move(rd->win, HDR_CRYPTINFO, 0);
426 mutt_window_clrtoeol(rd->win);
428 if (((WithCrypto & APPLICATION_PGP) != 0) &&
429 (e->security & APPLICATION_PGP) && (e->security & SEC_SIGN))
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>"));
437 if (((WithCrypto & APPLICATION_SMIME) != 0) &&
438 (e->security & APPLICATION_SMIME) && (e->security & SEC_SIGN))
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>"));
446 if (((WithCrypto & APPLICATION_SMIME) != 0) && (e->security & APPLICATION_SMIME) &&
447 (e->security & SEC_ENCRYPT) && C_SmimeEncryptWith)
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));
456 mutt_window_move(rd->win, HDR_AUTOCRYPT, 0);
457 mutt_window_clrtoeol(rd->win);
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)
465 mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_ENCRYPT);
466 mutt_window_addstr(_("Encrypt"));
470 mutt_curses_set_color(MT_COLOR_COMPOSE_SECURITY_NONE);
471 mutt_window_addstr(_("Off"));
474 mutt_curses_set_color(MT_COLOR_COMPOSE_HEADER);
475 mutt_window_mvprintw(rd->win, HDR_AUTOCRYPT, 40, "%s",
477 The autocrypt compose menu Recommendation field.
478 Displays the output of the recommendation engine
479 (Off, No, Discouraged, Available, Yes)
481 _("Recommendation: "));
482 mutt_curses_set_color(MT_COLOR_NORMAL);
483 mutt_window_printf("%s", _(AutocryptRecUiFlags[rd->autocrypt_rec]));
489 * update_crypt_info - Update the crypto info
490 * @param rd Email and other compose data
492 static void update_crypt_info(struct ComposeRedrawData *rd)
494 struct Email *e = rd->email;
496 if (C_CryptOpportunisticEncrypt)
497 crypt_opportunistic_encrypt(e);
502 rd->autocrypt_rec = mutt_autocrypt_ui_recommendation(e, NULL);
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);
511 if (!(e->security & SEC_AUTOCRYPT_OVERRIDE))
513 if (rd->autocrypt_rec == AUTOCRYPT_REC_YES)
515 e->security |= SEC_AUTOCRYPT;
516 e->security &= ~SEC_INLINE;
519 e->security &= ~SEC_AUTOCRYPT;
525 redraw_crypt_lines(rd);
530 * redraw_mix_line - Redraw the Mixmaster chain
531 * @param chain List of chain links
532 * @param rd Email and other compose data
534 static void redraw_mix_line(struct ListHead *chain, struct ComposeRedrawData *rd)
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);
543 if (STAILQ_EMPTY(chain))
545 mutt_window_addstr(_("<no chain defined>"));
546 mutt_window_clrtoeol(rd->win);
551 struct ListNode *np = NULL;
552 STAILQ_FOREACH(np, chain, entries)
555 if (t && (t[0] == '0') && (t[1] == '\0'))
558 if (c + mutt_str_strlen(t) + 2 >= rd->win->cols)
561 mutt_window_addstr(NONULL(t));
562 if (STAILQ_NEXT(np, entries))
563 mutt_window_addstr(", ");
565 c += mutt_str_strlen(t) + 2;
568 #endif /* MIXMASTER */
571 * check_attachments - Check if any attachments have changed or been deleted
572 * @param actx Attachment context
576 static int check_attachments(struct AttachCtx *actx)
580 struct Buffer *pretty = NULL, *msg = NULL;
582 for (int i = 0; i < actx->idxlen; i++)
584 if (actx->idx[i]->content->type == TYPE_MULTIPART)
586 if (stat(actx->idx[i]->content->filename, &st) != 0)
589 pretty = mutt_buffer_pool_get();
590 mutt_buffer_strcpy(pretty, actx->idx[i]->content->filename);
591 mutt_buffer_pretty_mailbox(pretty);
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
596 The filename is located last to avoid a long path hiding the
599 mutt_error(_("Attachment #%d no longer exists: %s"), i + 1, mutt_b2s(pretty));
603 if (actx->idx[i]->content->stamp < st.st_mtime)
606 pretty = mutt_buffer_pool_get();
607 mutt_buffer_strcpy(pretty, actx->idx[i]->content->filename);
608 mutt_buffer_pretty_mailbox(pretty);
611 msg = mutt_buffer_pool_get();
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
619 mutt_buffer_printf(msg, _("Attachment #%d modified. Update encoding for %s?"),
620 i + 1, mutt_b2s(pretty));
622 enum QuadOption ans = mutt_yesorno(mutt_b2s(msg), MUTT_YES);
624 mutt_update_encoding(actx->idx[i]->content);
625 else if (ans == MUTT_ABORT)
633 mutt_buffer_pool_release(&pretty);
634 mutt_buffer_pool_release(&msg);
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
644 static void draw_envelope_addr(int line, struct AddressList *al, struct ComposeRedrawData *rd)
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);
657 * draw_envelope - Write the email headers to the compose window
658 * @param rd Email and other compose data
660 static void draw_envelope(struct ComposeRedrawData *rd)
662 struct Email *e = rd->email;
665 draw_envelope_addr(HDR_FROM, &e->env->from, rd);
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);
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));
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));
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));
698 draw_envelope_addr(HDR_REPLYTO, &e->env->reply_to, rd);
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);
707 redraw_crypt_lines(rd);
710 redraw_mix_line(&e->chain, rd);
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);
717 mutt_curses_set_color(MT_COLOR_NORMAL);
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
726 static void edit_address_list(int line, struct AddressList *al, struct ComposeRedrawData *rd)
728 char buf[8192] = { 0 }; /* needs to be large for alias expansion */
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)
735 mutt_addrlist_clear(al);
736 mutt_addrlist_parse2(al, buf);
737 mutt_expand_aliases(al);
740 if (mutt_addrlist_to_intl(al, &err) != 0)
742 mutt_error(_("Bad IDN: '%s'"), err);
747 /* redraw the expanded list so the user can see the result */
749 mutt_addrlist_write(buf, sizeof(buf), al, true);
750 mutt_window_move(rd->win, line, HDR_XOFFSET);
751 mutt_paddstr(W, buf);
755 * delete_attachment - Delete an attachment
756 * @param actx Attachment context
757 * @param x Index number of attachment
761 static int delete_attachment(struct AttachCtx *actx, int x)
763 struct AttachPtr **idx = actx->idx;
764 int rindex = actx->v2r[x];
766 if ((rindex == 0) && (actx->idxlen == 1))
768 mutt_error(_("You may not delete the only attachment"));
769 idx[rindex]->content->tagged = false;
773 for (int y = 0; y < actx->idxlen; y++)
775 if (idx[y]->content->next == idx[rindex]->content)
777 idx[y]->content->next = idx[rindex]->content->next;
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.
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.
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.
797 if (!idx[rindex]->content->email)
798 idx[rindex]->content->parts = NULL;
799 mutt_body_free(&(idx[rindex]->content));
800 FREE(&idx[rindex]->tree);
802 for (; rindex < actx->idxlen - 1; rindex++)
803 idx[rindex] = idx[rindex + 1];
804 idx[actx->idxlen - 1] = NULL;
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
817 static void mutt_gen_compose_attach_list(struct AttachCtx *actx, struct Body *m,
818 int parent_type, int level)
820 for (; m; m = m->next)
822 if ((m->type == TYPE_MULTIPART) && m->parts &&
823 (!(WithCrypto & APPLICATION_PGP) || !mutt_is_multipart_encrypted(m)))
825 mutt_gen_compose_attach_list(actx, m->parts, m->type, level);
829 struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
830 mutt_actx_add_attach(actx, ap);
833 ap->parent_type = parent_type;
836 /* We don't support multipart messages in the compose menu yet */
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
847 static void mutt_update_compose_menu(struct AttachCtx *actx, struct Menu *menu, bool init)
851 mutt_gen_compose_attach_list(actx, actx->email->content, -1, 0);
852 mutt_attach_init(actx);
856 mutt_update_tree(actx);
858 menu->max = actx->vcount;
861 if (menu->current >= menu->max)
862 menu->current = menu->max - 1;
867 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
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
876 static void update_idx(struct Menu *menu, struct AttachCtx *actx, struct AttachPtr *ap)
878 ap->level = (actx->idxlen > 0) ? actx->idx[actx->idxlen - 1]->level : 0;
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;
888 * compose_custom_redraw - Redraw the compose menu - Implements Menu::menu_custom_redraw()
890 static void compose_custom_redraw(struct Menu *menu)
892 struct ComposeRedrawData *rd = menu->redraw_data;
897 if (menu->redraw & REDRAW_FULL)
899 menu_redraw_full(menu);
902 menu->offset = HDR_ATTACH;
903 menu->pagelen = menu->indexwin->rows - HDR_ATTACH;
906 menu_check_recenter(menu);
908 if (menu->redraw & REDRAW_STATUS)
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;
921 if (menu->redraw & REDRAW_SIDEBAR)
922 menu_redraw_sidebar(menu);
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);
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
939 static void compose_attach_swap(struct Body *msg, struct AttachPtr **idx, short first)
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)
945 if (part->next == idx[first]->content)
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;
955 struct AttachPtr *saved = idx[first];
956 idx[first] = idx[first + 1];
957 idx[first + 1] = saved;
960 int i = idx[first]->num;
961 idx[first]->num = idx[first + 1]->num;
962 idx[first + 1]->num = i;
966 * cum_attachs_size - Cumulative Attachments Size
967 * @param menu Menu listing attachments
968 * @retval num Bytes in attachments
970 * Returns the total number of bytes used by the attachments in the attachment
971 * list _after_ content-transfer-encodings have been applied.
973 static unsigned long cum_attachs_size(struct Menu *menu)
976 struct AttachCtx *actx = menu->data;
977 struct AttachPtr **idx = actx->idx;
978 struct Content *info = NULL;
979 struct Body *b = NULL;
981 for (unsigned short i = 0; i < actx->idxlen; i++)
986 b->content = mutt_get_content_info(b->filename, b);
993 case ENC_QUOTED_PRINTABLE:
994 s += 3 * (info->lobin + info->hibin) + info->ascii + info->crlf;
997 s += (4 * (info->lobin + info->hibin + info->ascii + info->crlf)) / 3;
1000 s += info->lobin + info->hibin + info->ascii + info->crlf;
1010 * compose_format_str - Create the status bar string for compose mode - Implements ::format_t
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
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)
1024 char fmt[128], tmp[128];
1025 bool optional = (flags & MUTT_FORMAT_OPTIONAL);
1026 struct Menu *menu = (struct Menu *) data;
1031 case 'a': /* total number of attachments */
1032 snprintf(fmt, sizeof(fmt), "%%%sd", prec);
1033 snprintf(buf, buflen, fmt, menu->max);
1036 case 'h': /* hostname */
1037 snprintf(fmt, sizeof(fmt), "%%%ss", prec);
1038 snprintf(buf, buflen, fmt, NONULL(ShortHostname));
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);
1048 snprintf(buf, buflen, "%s", mutt_make_version());
1056 snprintf(buf, buflen, "%%%s%c", prec, op);
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);
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
1077 static void compose_status_line(char *buf, size_t buflen, size_t col, int cols,
1078 struct Menu *menu, const char *src)
1080 mutt_expando_format(buf, buflen, col, cols, src, compose_format_str,
1081 (unsigned long) menu, MUTT_FORMAT_NO_FLAGS);
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
1095 int mutt_compose_menu(struct Email *e, char *fcc, size_t fcclen, struct Email *e_cur, int flags)
1097 char helpstr[1024]; // This isn't copied by the help bar
1099 int op_close = OP_NULL;
1102 bool fcc_set = false; /* has the user edited the Fcc: field ? */
1103 struct ComposeRedrawData redraw = { 0 };
1104 struct ComposeRedrawData *rd = &redraw;
1106 bool news = OptNewsSend; /* is it a news article ? */
1109 init_header_padding();
1113 rd->win = MuttIndexWindow;
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;
1121 menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_COMPOSE, ComposeNewsHelp);
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);
1129 struct AttachCtx *actx = mutt_actx_new();
1131 mutt_update_compose_menu(actx, menu, true);
1133 update_crypt_info(rd);
1135 /* Since this is rather long lived, we don't use the pool */
1136 struct Buffer fname = mutt_buffer_make(PATH_MAX);
1141 OptNews = false; /* for any case */
1143 const int op = mutt_menu_loop(menu);
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);
1152 case OP_COMPOSE_EDIT_TO:
1157 edit_address_list(HDR_TO, &e->env->to, rd);
1158 update_crypt_info(rd);
1159 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1162 case OP_COMPOSE_EDIT_BCC:
1167 edit_address_list(HDR_BCC, &e->env->bcc, rd);
1168 update_crypt_info(rd);
1169 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1172 case OP_COMPOSE_EDIT_CC:
1177 edit_address_list(HDR_CC, &e->env->cc, rd);
1178 update_crypt_info(rd);
1179 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1182 case OP_COMPOSE_EDIT_NEWSGROUPS:
1185 if (e->env->newsgroups)
1186 mutt_str_strfcpy(buf, e->env->newsgroups, sizeof(buf));
1189 if (mutt_get_field("Newsgroups: ", buf, sizeof(buf), 0) == 0)
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);
1196 mutt_window_clrtoeol(menu->indexwin);
1200 case OP_COMPOSE_EDIT_FOLLOWUP_TO:
1203 if (e->env->followup_to)
1204 mutt_str_strfcpy(buf, e->env->followup_to, sizeof(buf));
1207 if (mutt_get_field("Followup-To: ", buf, sizeof(buf), 0) == 0)
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);
1214 mutt_window_clrtoeol(menu->indexwin);
1218 case OP_COMPOSE_EDIT_X_COMMENT_TO:
1219 if (!(news && C_XCommentTo))
1221 if (e->env->x_comment_to)
1222 mutt_str_strfcpy(buf, e->env->x_comment_to, sizeof(buf));
1225 if (mutt_get_field("X-Comment-To: ", buf, sizeof(buf), 0) == 0)
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);
1232 mutt_window_clrtoeol(menu->indexwin);
1237 case OP_COMPOSE_EDIT_SUBJECT:
1238 if (e->env->subject)
1239 mutt_str_strfcpy(buf, e->env->subject, sizeof(buf));
1242 if (mutt_get_field(_("Subject: "), buf, sizeof(buf), 0) == 0)
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);
1249 mutt_window_clrtoeol(menu->indexwin);
1251 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
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);
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)
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);
1269 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1272 case OP_COMPOSE_EDIT_MESSAGE:
1273 if (C_Editor && (mutt_str_strcmp("builtin", C_Editor) != 0) && !C_EditHeaders)
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);
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)))
1290 const char *tag = 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))
1296 mutt_error(_("Bad IDN in '%s': '%s'"), tag, err);
1299 update_crypt_info(rd);
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);
1310 mutt_rfc3676_space_stuff(e);
1311 mutt_update_encoding(e->content);
1313 /* attachments may have been added */
1314 if (actx->idxlen && actx->idx[actx->idxlen - 1]->content->next)
1316 mutt_actx_entries_free(actx);
1317 mutt_update_compose_menu(actx, menu, true);
1320 menu->redraw = REDRAW_FULL;
1321 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1324 case OP_COMPOSE_ATTACH_KEY:
1326 if (!(WithCrypto & APPLICATION_PGP))
1328 struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1329 ap->content = crypt_pgp_make_key_attachment();
1332 update_idx(menu, actx, ap);
1333 menu->redraw |= REDRAW_INDEX;
1338 menu->redraw |= REDRAW_STATUS;
1340 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1344 case OP_COMPOSE_MOVE_UP:
1345 if (menu->current == 0)
1347 mutt_error(_("Attachment is already at top"));
1350 if (menu->current == 1)
1352 mutt_error(_("The fundamental part can't be moved"));
1355 compose_attach_swap(e->content, actx->idx, menu->current - 1);
1356 menu->redraw = REDRAW_INDEX;
1360 case OP_COMPOSE_MOVE_DOWN:
1361 if (menu->current == (actx->idxlen - 1))
1363 mutt_error(_("Attachment is already at bottom"));
1366 if (menu->current == 0)
1368 mutt_error(_("The fundamental part can't be moved"));
1371 compose_attach_swap(e->content, actx->idx, menu->current);
1372 menu->redraw = REDRAW_INDEX;
1376 case OP_COMPOSE_GROUP_ALTS:
1378 if (menu->tagged < 2)
1381 _("Grouping 'alternatives' requires at least 2 tagged messages"));
1385 struct Body *group = mutt_body_new();
1386 group->type = TYPE_MULTIPART;
1387 group->subtype = mutt_str_strdup("alternative");
1388 group->disposition = DISP_INLINE;
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;)
1397 bptr->tagged = false;
1398 bptr->disposition = DISP_INLINE;
1400 /* for first match, set group desc according to match */
1401 #define ALTS_TAG "Alternatives for \"%s\""
1402 if (!group->description)
1404 char *p = bptr->description ? bptr->description : bptr->filename;
1407 group->description =
1408 mutt_mem_calloc(1, strlen(p) + strlen(ALTS_TAG) + 1);
1409 sprintf(group->description, ALTS_TAG, p);
1413 /* append bptr to the alts list,
1414 * and remove from the e->content list */
1417 group->parts = bptr;
1430 for (int j = i; j < actx->idxlen - 1; j++)
1432 actx->idx[j] = actx->idx[j + 1];
1433 actx->idx[j + 1] = NULL; /* for debug reason */
1445 mutt_generate_boundary(&group->parameter);
1447 /* if no group desc yet, make one up */
1448 if (!group->description)
1449 group->description = mutt_str_strdup("unknown alternative group");
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;
1458 case OP_COMPOSE_GROUP_LINGUAL:
1460 if (menu->tagged < 2)
1463 _("Grouping 'multilingual' requires at least 2 tagged messages"));
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++;
1473 if (menu->tagged != tagged_with_lang_num)
1476 _("Not all parts have 'Content-Language' set, continue?"), MUTT_YES) != MUTT_YES)
1478 mutt_message(_("Not sending this message"));
1483 struct Body *group = mutt_body_new();
1484 group->type = TYPE_MULTIPART;
1485 group->subtype = mutt_str_strdup("multilingual");
1486 group->disposition = DISP_INLINE;
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;)
1495 bptr->tagged = false;
1496 bptr->disposition = DISP_INLINE;
1498 /* for first match, set group desc according to match */
1499 #define LINGUAL_TAG "Multilingual part for \"%s\""
1500 if (!group->description)
1502 char *p = bptr->description ? bptr->description : bptr->filename;
1505 group->description =
1506 mutt_mem_calloc(1, strlen(p) + strlen(LINGUAL_TAG) + 1);
1507 sprintf(group->description, LINGUAL_TAG, p);
1511 /* append bptr to the alts list,
1512 * and remove from the e->content list */
1515 group->parts = bptr;
1528 for (int j = i; j < actx->idxlen - 1; j++)
1530 actx->idx[j] = actx->idx[j + 1];
1531 actx->idx[j + 1] = NULL; /* for debug reason */
1543 mutt_generate_boundary(&group->parameter);
1545 /* if no group desc yet, make one up */
1546 if (!group->description)
1547 group->description = mutt_str_strdup("unknown multilingual group");
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;
1556 case OP_COMPOSE_ATTACH_FILE:
1558 char *prompt = _("Attach file");
1560 char **files = NULL;
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))
1573 mutt_message(ngettext("Attaching selected file...",
1574 "Attaching selected files...", numfiles));
1576 for (int i = 0; i < numfiles; i++)
1578 char *att = files[i];
1579 struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1581 ap->content = mutt_make_file_attach(att);
1583 update_idx(menu, actx, ap);
1587 mutt_error(_("Unable to attach %s"), att);
1597 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
1598 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1602 case OP_COMPOSE_ATTACH_MESSAGE:
1604 case OP_COMPOSE_ATTACH_NEWS_MESSAGE:
1607 mutt_buffer_reset(&fname);
1608 char *prompt = _("Open mailbox to attach message from");
1612 if (Context && (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE))
1614 CurrentNewsSrv = nntp_select_server(Context->mailbox, C_NewsServer, false);
1615 if (!CurrentNewsSrv)
1618 prompt = _("Open newsgroup to attach message from");
1625 if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->mailbox->magic == MUTT_NNTP))
1628 mutt_buffer_strcpy(&fname, mailbox_path(Context->mailbox));
1629 mutt_buffer_pretty_mailbox(&fname);
1632 if ((mutt_buffer_enter_fname(prompt, &fname, true) == -1) ||
1633 mutt_buffer_is_empty(&fname))
1640 nntp_expand_path(fname.data, fname.dsize, &CurrentNewsSrv->conn->account);
1643 mutt_buffer_expand_path(&fname);
1645 if (imap_path_probe(mutt_b2s(&fname), NULL) != MUTT_IMAP)
1648 if (pop_path_probe(mutt_b2s(&fname), NULL) != MUTT_POP)
1651 if (!OptNews && (nntp_path_probe(mutt_b2s(&fname), NULL) != MUTT_NNTP))
1653 /* check to make sure the file exists and is readable */
1654 if (access(mutt_b2s(&fname), R_OK) == -1)
1656 mutt_perror(mutt_b2s(&fname));
1660 menu->redraw = REDRAW_FULL;
1662 struct Mailbox *m = mx_path_resolve(mutt_b2s(&fname));
1663 struct Context *ctx = mx_mbox_open(m, MUTT_READONLY);
1666 mutt_error(_("Unable to open mailbox %s"), mutt_b2s(&fname));
1671 if (ctx->mailbox->msg_count == 0)
1673 mx_mbox_close(&ctx);
1674 mutt_error(_("No messages in that folder"));
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;
1683 OptAttachMsg = true;
1684 mutt_message(_("Tag the messages you want to attach"));
1685 op_close = mutt_index_menu();
1686 OptAttachMsg = false;
1690 /* go back to the folder we started from */
1692 /* Restore old $sort and $sort_aux */
1694 C_SortAux = old_sort_aux;
1695 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
1699 for (int i = 0; i < Context->mailbox->msg_count; i++)
1701 if (!message_is_tagged(Context, i))
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);
1708 update_idx(menu, actx, ap);
1711 mutt_error(_("Unable to attach"));
1715 menu->redraw |= REDRAW_FULL;
1717 if (op_close == OP_QUIT)
1718 mx_mbox_close(&Context);
1721 mx_fastclose_mailbox(Context->mailbox);
1725 /* go back to the folder we started from */
1727 /* Restore old $sort and $sort_aux */
1729 C_SortAux = old_sort_aux;
1730 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1736 if (CUR_ATTACH->unowned)
1737 CUR_ATTACH->content->unlink = false;
1738 if (delete_attachment(actx, menu->current) == -1)
1740 mutt_update_compose_menu(actx, menu, false);
1741 if (menu->current == 0)
1742 e->content = actx->idx[0]->content;
1744 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1747 case OP_COMPOSE_TOGGLE_RECODE:
1750 if (!mutt_is_text_part(CUR_ATTACH->content))
1752 mutt_error(_("Recoding only affects text attachments"));
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"));
1759 mutt_message(_("The current attachment will be converted"));
1760 menu->redraw = REDRAW_CURRENT;
1761 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1765 case OP_COMPOSE_EDIT_DESCRIPTION:
1768 buf, CUR_ATTACH->content->description ? CUR_ATTACH->content->description : "",
1770 /* header names should not be translated */
1771 if (mutt_get_field("Description: ", buf, sizeof(buf), 0) == 0)
1773 mutt_str_replace(&CUR_ATTACH->content->description, buf);
1774 menu->redraw = REDRAW_CURRENT;
1776 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1779 case OP_COMPOSE_UPDATE_ENCODING:
1781 if (menu->tagprefix)
1783 struct Body *top = NULL;
1784 for (top = e->content; top; top = top->next)
1787 mutt_update_encoding(top);
1789 menu->redraw = REDRAW_FULL;
1793 mutt_update_encoding(CUR_ATTACH->content);
1794 menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1796 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
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;
1809 mutt_edit_content_type(NULL, CUR_ATTACH->content, NULL);
1811 /* this may have been a change to text/something */
1812 mutt_update_encoding(CUR_ATTACH->content);
1814 menu->redraw = REDRAW_CURRENT;
1816 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1819 case OP_COMPOSE_EDIT_LANGUAGE:
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)
1826 CUR_ATTACH->content->language = mutt_str_strdup(buf);
1827 menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1831 mutt_warning(_("Empty 'Content-Language'"));
1832 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1835 case OP_COMPOSE_EDIT_ENCODING:
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) &&
1841 int enc = mutt_check_encoding(buf);
1842 if ((enc != ENC_OTHER) && (enc != ENC_UUENCODED))
1844 CUR_ATTACH->content->encoding = enc;
1845 menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
1849 mutt_error(_("Invalid encoding"));
1851 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
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)
1859 menu->redraw = REDRAW_FULL;
1864 if (!STAILQ_EMPTY(&e->chain) && (mix_check_message(e) != 0))
1868 if (!fcc_set && *fcc)
1870 enum QuadOption ans =
1871 query_quadoption(C_Copy, _("Save a copy of this message?"));
1872 if (ans == MUTT_ABORT)
1874 else if (ans == MUTT_NO)
1882 case OP_COMPOSE_EDIT_FILE:
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);
1890 case OP_COMPOSE_TOGGLE_UNLINK:
1892 CUR_ATTACH->content->unlink = !CUR_ATTACH->content->unlink;
1894 menu->redraw = REDRAW_INDEX;
1895 /* No send2hook since this doesn't change the message. */
1898 case OP_COMPOSE_GET_ATTACHMENT:
1900 if (menu->tagprefix)
1902 for (struct Body *top = e->content; top; top = top->next)
1905 mutt_get_tmp_attachment(top);
1907 menu->redraw = REDRAW_FULL;
1909 else if (mutt_get_tmp_attachment(CUR_ATTACH->content) == 0)
1910 menu->redraw = REDRAW_CURRENT;
1912 /* No send2hook since this doesn't change the message. */
1915 case OP_COMPOSE_RENAME_ATTACHMENT:
1919 if (CUR_ATTACH->content->d_filename)
1920 src = CUR_ATTACH->content->d_filename;
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);
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;
1935 case OP_COMPOSE_RENAME_FILE:
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))
1943 if (stat(CUR_ATTACH->content->filename, &st) == -1)
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));
1950 mutt_buffer_expand_path(&fname);
1951 if (mutt_file_rename(CUR_ATTACH->content->filename, mutt_b2s(&fname)))
1954 mutt_str_replace(&CUR_ATTACH->content->filename, mutt_b2s(&fname));
1955 menu->redraw = REDRAW_CURRENT;
1957 if (CUR_ATTACH->content->stamp >= st.st_mtime)
1958 mutt_stamp_attachment(CUR_ATTACH->content);
1960 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
1963 case OP_COMPOSE_NEW_MIME:
1965 mutt_buffer_reset(&fname);
1966 if ((mutt_buffer_get_field(_("New file: "), &fname, MUTT_FILE) != 0) ||
1967 mutt_buffer_is_empty(&fname))
1971 mutt_buffer_expand_path(&fname);
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) ||
1981 char *p = strchr(type, '/');
1984 mutt_error(_("Content-Type is of the form base/sub"));
1988 enum ContentType itype = mutt_check_mime_type(type);
1989 if (itype == TYPE_OTHER)
1991 mutt_error(_("Unknown Content-Type %s"), type);
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");
1999 mutt_error(_("Can't create file %s"), mutt_b2s(&fname));
2003 mutt_file_fclose(&fp);
2005 ap->content = mutt_make_file_attach(mutt_b2s(&fname));
2008 mutt_error(_("What we have here is a failure to make an attachment"));
2012 update_idx(menu, actx, ap);
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;
2019 if (mutt_compose_attachment(CUR_ATTACH->content))
2021 mutt_update_encoding(CUR_ATTACH->content);
2022 menu->redraw = REDRAW_FULL;
2024 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2028 case OP_COMPOSE_EDIT_MIME:
2030 if (mutt_edit_attachment(CUR_ATTACH->content))
2032 mutt_update_encoding(CUR_ATTACH->content);
2033 menu->redraw = REDRAW_FULL;
2035 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2038 case OP_VIEW_ATTACH:
2039 case OP_DISPLAY_HEADERS:
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 */
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 */
2055 mutt_print_attachment_list(actx, NULL, menu->tagprefix, CUR_ATTACH->content);
2056 /* no send2hook, since this doesn't modify the message */
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);
2072 enum QuadOption ans =
2073 query_quadoption(C_Postpone, _("Save (postpone) draft message?"));
2076 for (int i = 0; i < actx->idxlen; i++)
2077 if (actx->idx[i]->unowned)
2078 actx->idx[i]->content->unlink = false;
2080 if (!(flags & MUTT_COMPOSE_NOFREEHEADER))
2082 for (int i = 0; i < actx->idxlen; i++)
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);
2096 else if (ans == MUTT_ABORT)
2101 case OP_COMPOSE_POSTPONE_MESSAGE:
2102 if (check_attachments(actx) != 0)
2104 menu->redraw = REDRAW_FULL;
2112 case OP_COMPOSE_ISPELL:
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);
2119 mutt_update_encoding(e->content);
2120 menu->redraw |= REDRAW_STATUS;
2124 case OP_COMPOSE_WRITE_MESSAGE:
2125 mutt_buffer_reset(&fname);
2128 mutt_buffer_strcpy(&fname, mailbox_path(Context->mailbox));
2129 mutt_buffer_pretty_mailbox(&fname);
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))
2136 mutt_message(_("Writing message to %s ..."), mutt_b2s(&fname));
2137 mutt_buffer_expand_path(&fname);
2139 if (e->content->next)
2140 e->content = mutt_make_multipart(e->content);
2142 if (mutt_write_fcc(mutt_b2s(&fname), e, NULL, false, NULL, NULL) == 0)
2143 mutt_message(_("Message written"));
2145 e->content = mutt_remove_multipart(e->content);
2149 case OP_COMPOSE_PGP_MENU:
2150 if (!(WithCrypto & APPLICATION_PGP))
2152 if (!crypt_has_module_backend(APPLICATION_PGP))
2154 mutt_error(_("No PGP backend configured"));
2157 if (((WithCrypto & APPLICATION_SMIME) != 0) && (e->security & APPLICATION_SMIME))
2159 if (e->security & (SEC_ENCRYPT | SEC_SIGN))
2161 if (mutt_yesorno(_("S/MIME already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
2166 e->security &= ~(SEC_ENCRYPT | SEC_SIGN);
2168 e->security &= ~APPLICATION_SMIME;
2169 e->security |= APPLICATION_PGP;
2170 update_crypt_info(rd);
2172 e->security = crypt_pgp_send_menu(e);
2173 update_crypt_info(rd);
2174 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2177 case OP_FORGET_PASSPHRASE:
2178 crypt_forget_passphrase();
2181 case OP_COMPOSE_SMIME_MENU:
2182 if (!(WithCrypto & APPLICATION_SMIME))
2184 if (!crypt_has_module_backend(APPLICATION_SMIME))
2186 mutt_error(_("No S/MIME backend configured"));
2190 if (((WithCrypto & APPLICATION_PGP) != 0) && (e->security & APPLICATION_PGP))
2192 if (e->security & (SEC_ENCRYPT | SEC_SIGN))
2194 if (mutt_yesorno(_("PGP already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
2199 e->security &= ~(SEC_ENCRYPT | SEC_SIGN);
2201 e->security &= ~APPLICATION_PGP;
2202 e->security |= APPLICATION_SMIME;
2203 update_crypt_info(rd);
2205 e->security = crypt_smime_send_menu(e);
2206 update_crypt_info(rd);
2207 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
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);
2216 #ifdef USE_AUTOCRYPT
2217 case OP_COMPOSE_AUTOCRYPT_MENU:
2221 if ((WithCrypto & APPLICATION_SMIME) && (e->security & APPLICATION_SMIME))
2223 if (e->security & (SEC_ENCRYPT | SEC_SIGN))
2225 if (mutt_yesorno(_("S/MIME already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
2230 e->security &= ~(SEC_ENCRYPT | SEC_SIGN);
2232 e->security &= ~APPLICATION_SMIME;
2233 e->security |= APPLICATION_PGP;
2234 update_crypt_info(rd);
2236 autocrypt_compose_menu(e);
2237 update_crypt_info(rd);
2238 mutt_message_hook(NULL, e, MUTT_SEND2_HOOK);
2244 mutt_buffer_dealloc(&fname);
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. */
2251 e->security &= ~SEC_AUTOCRYPT;
2254 mutt_menu_pop_current(menu);
2255 mutt_menu_free(&menu);
2258 e->content = actx->idx[0]->content;
2262 mutt_actx_free(&actx);