1 /**
2  * @file
3  * Compose functions
4  *
5  * @authors
6  * Copyright (C) 2021 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page compose_functions Compose functions
25  *
26  * Compose functions
27  */
28 
29 #include "config.h"
30 #include <errno.h>
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include "private.h"
38 #include "mutt/lib.h"
39 #include "address/lib.h"
40 #include "config/lib.h"
41 #include "email/lib.h"
42 #include "core/lib.h"
43 #include "alias/lib.h"
44 #include "conn/lib.h"
45 #include "gui/lib.h"
46 #include "mutt.h"
47 #include "functions.h"
48 #include "lib.h"
49 #include "attach/lib.h"
50 #ifdef USE_AUTOCRYPT
51 #include "autocrypt/lib.h"
52 #endif
53 #include "index/lib.h"
54 #include "menu/lib.h"
55 #include "ncrypt/lib.h"
56 #include "question/lib.h"
57 #include "send/lib.h"
58 #include "attach_data.h"
59 #include "browser.h"
60 #include "commands.h"
61 #include "context.h"
62 #include "env_data.h"
63 #include "hook.h"
64 #include "mutt_header.h"
65 #include "mutt_logging.h"
66 #include "muttlib.h"
67 #include "mx.h"
68 #include "opcodes.h"
69 #include "options.h"
70 #include "protos.h"
71 #include "rfc3676.h"
72 #include "shared_data.h"
73 #ifdef ENABLE_NLS
74 #include <libintl.h>
75 #endif
76 #ifdef MIXMASTER
77 #include "remailer.h"
78 #endif
79 #ifdef USE_NNTP
80 #include "nntp/lib.h"
81 #include "nntp/adata.h" // IWYU pragma: keep
82 #endif
83 #ifdef USE_POP
84 #include "pop/lib.h"
85 #endif
86 #ifdef USE_IMAP
87 #include "imap/lib.h"
88 #endif
89 
90 static const char *Not_available_in_this_menu =
91     N_("Not available in this menu");
92 
93 /**
94  * check_count - Check if there are any attachments
95  * @param actx Attachment context
96  * @retval true There are attachments
97  */
check_count(struct AttachCtx * actx)98 static bool check_count(struct AttachCtx *actx)
99 {
100   if (actx->idxlen == 0)
101   {
102     mutt_error(_("There are no attachments"));
103     return false;
104   }
105 
106   return true;
107 }
108 
109 #ifdef USE_AUTOCRYPT
110 /**
111  * autocrypt_compose_menu - Autocrypt compose settings
112  * @param e Email
113  * @param sub ConfigSubset
114  */
autocrypt_compose_menu(struct Email * e,const struct ConfigSubset * sub)115 static void autocrypt_compose_menu(struct Email *e, const struct ConfigSubset *sub)
116 {
117   /* L10N: The compose menu autocrypt prompt.
118      (e)ncrypt enables encryption via autocrypt.
119      (c)lear sets cleartext.
120      (a)utomatic defers to the recommendation.  */
121   const char *prompt = _("Autocrypt: (e)ncrypt, (c)lear, (a)utomatic?");
122 
123   e->security |= APPLICATION_PGP;
124 
125   /* L10N: The letter corresponding to the compose menu autocrypt prompt
126      (e)ncrypt, (c)lear, (a)utomatic */
127   const char *letters = _("eca");
128 
129   int choice = mutt_multi_choice(prompt, letters);
130   switch (choice)
131   {
132     case 1:
133       e->security |= (SEC_AUTOCRYPT | SEC_AUTOCRYPT_OVERRIDE);
134       e->security &= ~(SEC_ENCRYPT | SEC_SIGN | SEC_OPPENCRYPT | SEC_INLINE);
135       break;
136     case 2:
137       e->security &= ~SEC_AUTOCRYPT;
138       e->security |= SEC_AUTOCRYPT_OVERRIDE;
139       break;
140     case 3:
141     {
142       e->security &= ~SEC_AUTOCRYPT_OVERRIDE;
143       const bool c_crypt_opportunistic_encrypt =
144           cs_subset_bool(sub, "crypt_opportunistic_encrypt");
145       if (c_crypt_opportunistic_encrypt)
146         e->security |= SEC_OPPENCRYPT;
147       break;
148     }
149   }
150 }
151 #endif
152 
153 /**
154  * update_crypt_info - Update the crypto info
155  * @param shared Shared compose data
156  */
update_crypt_info(struct ComposeSharedData * shared)157 void update_crypt_info(struct ComposeSharedData *shared)
158 {
159   struct Email *e = shared->email;
160 
161   const bool c_crypt_opportunistic_encrypt =
162       cs_subset_bool(shared->sub, "crypt_opportunistic_encrypt");
163   if (c_crypt_opportunistic_encrypt)
164     crypt_opportunistic_encrypt(e);
165 
166 #ifdef USE_AUTOCRYPT
167   const bool c_autocrypt = cs_subset_bool(shared->sub, "autocrypt");
168   if (c_autocrypt)
169   {
170     struct ComposeEnvelopeData *edata = shared->edata;
171     edata->autocrypt_rec = mutt_autocrypt_ui_recommendation(e, NULL);
172 
173     /* Anything that enables SEC_ENCRYPT or SEC_SIGN, or turns on SMIME
174      * overrides autocrypt, be it oppenc or the user having turned on
175      * those flags manually. */
176     if (e->security & (SEC_ENCRYPT | SEC_SIGN | APPLICATION_SMIME))
177       e->security &= ~(SEC_AUTOCRYPT | SEC_AUTOCRYPT_OVERRIDE);
178     else
179     {
180       if (!(e->security & SEC_AUTOCRYPT_OVERRIDE))
181       {
182         if (edata->autocrypt_rec == AUTOCRYPT_REC_YES)
183         {
184           e->security |= (SEC_AUTOCRYPT | APPLICATION_PGP);
185           e->security &= ~(SEC_INLINE | APPLICATION_SMIME);
186         }
187         else
188           e->security &= ~SEC_AUTOCRYPT;
189       }
190     }
191   }
192 #endif
193 }
194 
195 /**
196  * check_attachments - Check if any attachments have changed or been deleted
197  * @param actx Attachment context
198  * @param sub  ConfigSubset
199  * @retval  0 Success
200  * @retval -1 Error
201  */
check_attachments(struct AttachCtx * actx,struct ConfigSubset * sub)202 static int check_attachments(struct AttachCtx *actx, struct ConfigSubset *sub)
203 {
204   int rc = -1;
205   struct stat st = { 0 };
206   struct Buffer *pretty = NULL, *msg = NULL;
207 
208   for (int i = 0; i < actx->idxlen; i++)
209   {
210     if (actx->idx[i]->body->type == TYPE_MULTIPART)
211       continue;
212     if (stat(actx->idx[i]->body->filename, &st) != 0)
213     {
214       if (!pretty)
215         pretty = mutt_buffer_pool_get();
216       mutt_buffer_strcpy(pretty, actx->idx[i]->body->filename);
217       mutt_buffer_pretty_mailbox(pretty);
218       /* L10N: This message is displayed in the compose menu when an attachment
219          doesn't stat.  %d is the attachment number and %s is the attachment
220          filename.  The filename is located last to avoid a long path hiding
221          the error message.  */
222       mutt_error(_("Attachment #%d no longer exists: %s"), i + 1,
223                  mutt_buffer_string(pretty));
224       goto cleanup;
225     }
226 
227     if (actx->idx[i]->body->stamp < st.st_mtime)
228     {
229       if (!pretty)
230         pretty = mutt_buffer_pool_get();
231       mutt_buffer_strcpy(pretty, actx->idx[i]->body->filename);
232       mutt_buffer_pretty_mailbox(pretty);
233 
234       if (!msg)
235         msg = mutt_buffer_pool_get();
236       /* L10N: This message is displayed in the compose menu when an attachment
237          is modified behind the scenes.  %d is the attachment number and %s is
238          the attachment filename.  The filename is located last to avoid a long
239          path hiding the prompt question.  */
240       mutt_buffer_printf(msg, _("Attachment #%d modified. Update encoding for %s?"),
241                          i + 1, mutt_buffer_string(pretty));
242 
243       enum QuadOption ans = mutt_yesorno(mutt_buffer_string(msg), MUTT_YES);
244       if (ans == MUTT_YES)
245         mutt_update_encoding(actx->idx[i]->body, sub);
246       else if (ans == MUTT_ABORT)
247         goto cleanup;
248     }
249   }
250 
251   rc = 0;
252 
253 cleanup:
254   mutt_buffer_pool_release(&pretty);
255   mutt_buffer_pool_release(&msg);
256   return rc;
257 }
258 
259 /**
260  * edit_address_list - Let the user edit the address list
261  * @param[in]     field Field to edit, e.g. #HDR_FROM
262  * @param[in,out] al    AddressList to edit
263  * @retval true The address list was changed
264  */
edit_address_list(int field,struct AddressList * al)265 static bool edit_address_list(int field, struct AddressList *al)
266 {
267   char buf[8192] = { 0 }; /* needs to be large for alias expansion */
268   char old_list[8192] = { 0 };
269 
270   mutt_addrlist_to_local(al);
271   mutt_addrlist_write(al, buf, sizeof(buf), false);
272   mutt_str_copy(old_list, buf, sizeof(buf));
273   if (mutt_get_field(_(Prompts[field]), buf, sizeof(buf), MUTT_ALIAS, false, NULL, NULL) == 0)
274   {
275     mutt_addrlist_clear(al);
276     mutt_addrlist_parse2(al, buf);
277     mutt_expand_aliases(al);
278   }
279 
280   char *err = NULL;
281   if (mutt_addrlist_to_intl(al, &err) != 0)
282   {
283     mutt_error(_("Bad IDN: '%s'"), err);
284     mutt_refresh();
285     FREE(&err);
286   }
287 
288   return !mutt_str_equal(buf, old_list);
289 }
290 
291 /**
292  * delete_attachment - Delete an attachment
293  * @param actx Attachment context
294  * @param x    Index number of attachment
295  * @retval  0 Success
296  * @retval -1 Error
297  */
delete_attachment(struct AttachCtx * actx,int x)298 static int delete_attachment(struct AttachCtx *actx, int x)
299 {
300   if (!actx || (x < 0) || (x >= actx->idxlen))
301     return -1;
302 
303   struct AttachPtr **idx = actx->idx;
304   int rindex = actx->v2r[x];
305 
306   if ((rindex == 0) && (actx->idxlen == 1))
307   {
308     mutt_error(_("You may not delete the only attachment"));
309     idx[rindex]->body->tagged = false;
310     return -1;
311   }
312 
313   for (int y = 0; y < actx->idxlen; y++)
314   {
315     if (idx[y]->body->next == idx[rindex]->body)
316     {
317       idx[y]->body->next = idx[rindex]->body->next;
318       break;
319     }
320   }
321 
322   idx[rindex]->body->next = NULL;
323   /* mutt_make_message_attach() creates body->parts, shared by
324    * body->email->body.  If we NULL out that, it creates a memory
325    * leak because mutt_free_body() frees body->parts, not
326    * body->email->body.
327    *
328    * Other mutt_send_message() message constructors are careful to free
329    * any body->parts, removing depth:
330    *  - mutt_prepare_template() used by postponed, resent, and draft files
331    *  - mutt_copy_body() used by the recvattach menu and $forward_attachments.
332    *
333    * I believe it is safe to completely remove the "body->parts =
334    * NULL" statement.  But for safety, am doing so only for the case
335    * it must be avoided: message attachments.
336    */
337   if (!idx[rindex]->body->email)
338     idx[rindex]->body->parts = NULL;
339   mutt_body_free(&(idx[rindex]->body));
340   FREE(&idx[rindex]->tree);
341   FREE(&idx[rindex]);
342   for (; rindex < actx->idxlen - 1; rindex++)
343     idx[rindex] = idx[rindex + 1];
344   idx[actx->idxlen - 1] = NULL;
345   actx->idxlen--;
346 
347   return 0;
348 }
349 
350 /**
351  * update_idx - Add a new attachment to the message
352  * @param menu Current menu
353  * @param actx Attachment context
354  * @param ap   Attachment to add
355  */
update_idx(struct Menu * menu,struct AttachCtx * actx,struct AttachPtr * ap)356 static void update_idx(struct Menu *menu, struct AttachCtx *actx, struct AttachPtr *ap)
357 {
358   ap->level = (actx->idxlen > 0) ? actx->idx[actx->idxlen - 1]->level : 0;
359   if (actx->idxlen)
360     actx->idx[actx->idxlen - 1]->body->next = ap->body;
361   ap->body->aptr = ap;
362   mutt_actx_add_attach(actx, ap);
363   update_menu(actx, menu, false);
364   menu_set_index(menu, actx->vcount - 1);
365 }
366 
367 /**
368  * compose_attach_swap - Swap two adjacent entries in the attachment list
369  * @param[in]  msg   Body of email
370  * @param[out] idx   Array of Attachments
371  * @param[in]  first Index of first attachment to swap
372  */
compose_attach_swap(struct Body * msg,struct AttachPtr ** idx,short first)373 static void compose_attach_swap(struct Body *msg, struct AttachPtr **idx, short first)
374 {
375   /* Reorder Body pointers.
376    * Must traverse msg from top since Body has no previous ptr.  */
377   for (struct Body *part = msg; part; part = part->next)
378   {
379     if (part->next == idx[first]->body)
380     {
381       idx[first]->body->next = idx[first + 1]->body->next;
382       idx[first + 1]->body->next = idx[first]->body;
383       part->next = idx[first + 1]->body;
384       break;
385     }
386   }
387 
388   /* Reorder index */
389   struct AttachPtr *saved = idx[first];
390   idx[first] = idx[first + 1];
391   idx[first + 1] = saved;
392 
393   /* Swap ptr->num */
394   int i = idx[first]->num;
395   idx[first]->num = idx[first + 1]->num;
396   idx[first + 1]->num = i;
397 }
398 
399 // -----------------------------------------------------------------------------
400 
401 /**
402  * op_compose_attach_file - Attach files to this message - Implements ::compose_function_t - @ingroup compose_function_api
403  */
op_compose_attach_file(struct ComposeSharedData * shared,int op)404 static int op_compose_attach_file(struct ComposeSharedData *shared, int op)
405 {
406   char *prompt = _("Attach file");
407   int numfiles = 0;
408   char **files = NULL;
409 
410   struct Buffer *fname = mutt_buffer_pool_get();
411   if ((mutt_buffer_enter_fname(prompt, fname, false, NULL, true, &files,
412                                &numfiles, MUTT_SEL_MULTI) == -1) ||
413       mutt_buffer_is_empty(fname))
414   {
415     for (int i = 0; i < numfiles; i++)
416       FREE(&files[i]);
417 
418     FREE(&files);
419     mutt_buffer_pool_release(&fname);
420     return IR_NO_ACTION;
421   }
422 
423   bool error = false;
424   bool added_attachment = false;
425   if (numfiles > 1)
426   {
427     mutt_message(ngettext("Attaching selected file...",
428                           "Attaching selected files...", numfiles));
429   }
430   for (int i = 0; i < numfiles; i++)
431   {
432     char *att = files[i];
433     if (!att)
434       continue;
435 
436     struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
437     ap->unowned = true;
438     ap->body = mutt_make_file_attach(att, shared->sub);
439     if (ap->body)
440     {
441       added_attachment = true;
442       update_idx(shared->adata->menu, shared->adata->actx, ap);
443     }
444     else
445     {
446       error = true;
447       mutt_error(_("Unable to attach %s"), att);
448       FREE(&ap);
449     }
450     FREE(&files[i]);
451   }
452 
453   FREE(&files);
454   mutt_buffer_pool_release(&fname);
455 
456   if (!error)
457     mutt_clear_error();
458 
459   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
460   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
461   if (added_attachment)
462     mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
463   return IR_SUCCESS;
464 }
465 
466 /**
467  * op_compose_attach_key - Attach a PGP public key - Implements ::compose_function_t - @ingroup compose_function_api
468  */
op_compose_attach_key(struct ComposeSharedData * shared,int op)469 static int op_compose_attach_key(struct ComposeSharedData *shared, int op)
470 {
471   if (!(WithCrypto & APPLICATION_PGP))
472     return IR_NOT_IMPL;
473   struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
474   ap->body = crypt_pgp_make_key_attachment();
475   if (ap->body)
476   {
477     update_idx(shared->adata->menu, shared->adata->actx, ap);
478     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
479     mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
480   }
481   else
482     FREE(&ap);
483 
484   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
485   return IR_SUCCESS;
486 }
487 
488 /**
489  * op_compose_attach_message - Attach messages to this message - Implements ::compose_function_t - @ingroup compose_function_api
490  */
op_compose_attach_message(struct ComposeSharedData * shared,int op)491 static int op_compose_attach_message(struct ComposeSharedData *shared, int op)
492 {
493   char *prompt = _("Open mailbox to attach message from");
494 
495 #ifdef USE_NNTP
496   OptNews = false;
497   if (shared->mailbox && (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE))
498   {
499     const char *const c_news_server =
500         cs_subset_string(shared->sub, "news_server");
501     CurrentNewsSrv = nntp_select_server(shared->mailbox, c_news_server, false);
502     if (!CurrentNewsSrv)
503       return IR_NO_ACTION;
504 
505     prompt = _("Open newsgroup to attach message from");
506     OptNews = true;
507   }
508 #endif
509 
510   struct Buffer *fname = mutt_buffer_pool_get();
511   if (shared->mailbox)
512   {
513 #ifdef USE_NNTP
514     if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (shared->mailbox->type == MUTT_NNTP))
515 #endif
516     {
517       mutt_buffer_strcpy(fname, mailbox_path(shared->mailbox));
518       mutt_buffer_pretty_mailbox(fname);
519     }
520   }
521 
522   if ((mutt_buffer_enter_fname(prompt, fname, true, shared->mailbox, false,
523                                NULL, NULL, MUTT_SEL_NO_FLAGS) == -1) ||
524       mutt_buffer_is_empty(fname))
525   {
526     mutt_buffer_pool_release(&fname);
527     return IR_NO_ACTION;
528   }
529 
530 #ifdef USE_NNTP
531   if (OptNews)
532     nntp_expand_path(fname->data, fname->dsize, &CurrentNewsSrv->conn->account);
533   else
534 #endif
535     mutt_buffer_expand_path(fname);
536 #ifdef USE_IMAP
537   if (imap_path_probe(mutt_buffer_string(fname), NULL) != MUTT_IMAP)
538 #endif
539 #ifdef USE_POP
540     if (pop_path_probe(mutt_buffer_string(fname), NULL) != MUTT_POP)
541 #endif
542 #ifdef USE_NNTP
543       if (!OptNews && (nntp_path_probe(mutt_buffer_string(fname), NULL) != MUTT_NNTP))
544 #endif
545         if (mx_path_probe(mutt_buffer_string(fname)) != MUTT_NOTMUCH)
546         {
547           /* check to make sure the file exists and is readable */
548           if (access(mutt_buffer_string(fname), R_OK) == -1)
549           {
550             mutt_perror(mutt_buffer_string(fname));
551             mutt_buffer_pool_release(&fname);
552             return IR_ERROR;
553           }
554         }
555 
556   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
557 
558   struct Mailbox *m_attach = mx_path_resolve(mutt_buffer_string(fname));
559   const bool old_readonly = m_attach->readonly;
560   if (!mx_mbox_open(m_attach, MUTT_READONLY))
561   {
562     mutt_error(_("Unable to open mailbox %s"), mutt_buffer_string(fname));
563     mx_fastclose_mailbox(m_attach);
564     m_attach = NULL;
565     mutt_buffer_pool_release(&fname);
566     return IR_ERROR;
567   }
568   mutt_buffer_pool_release(&fname);
569 
570   if (m_attach->msg_count == 0)
571   {
572     mx_mbox_close(m_attach);
573     mutt_error(_("No messages in that folder"));
574     return IR_NO_ACTION;
575   }
576 
577   /* `$sort`, `$sort_aux`, `$use_threads` could be changed in mutt_index_menu() */
578   const enum SortType old_sort = cs_subset_sort(shared->sub, "sort");
579   const enum SortType old_sort_aux = cs_subset_sort(shared->sub, "sort_aux");
580   const unsigned char old_use_threads =
581       cs_subset_enum(shared->sub, "use_threads");
582 
583   OptAttachMsg = true;
584   mutt_message(_("Tag the messages you want to attach"));
585   struct MuttWindow *dlg_index = index_pager_init();
586   dialog_push(dlg_index);
587   struct Mailbox *m_attach_new = mutt_index_menu(dlg_index, m_attach);
588   dialog_pop();
589   mutt_window_free(&dlg_index);
590   OptAttachMsg = false;
591 
592   if (!shared->mailbox)
593   {
594     /* Restore old $sort variables */
595     cs_subset_str_native_set(shared->sub, "sort", old_sort, NULL);
596     cs_subset_str_native_set(shared->sub, "sort_aux", old_sort_aux, NULL);
597     cs_subset_str_native_set(shared->sub, "use_threads", old_use_threads, NULL);
598     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
599     notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
600     return IR_SUCCESS;
601   }
602 
603   bool added_attachment = false;
604   for (int i = 0; i < m_attach_new->msg_count; i++)
605   {
606     if (!m_attach_new->emails[i])
607       break;
608     if (!message_is_tagged(m_attach_new->emails[i]))
609       continue;
610 
611     struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
612     ap->body = mutt_make_message_attach(m_attach_new, m_attach_new->emails[i],
613                                         true, shared->sub);
614     if (ap->body)
615     {
616       added_attachment = true;
617       update_idx(shared->adata->menu, shared->adata->actx, ap);
618     }
619     else
620     {
621       mutt_error(_("Unable to attach"));
622       FREE(&ap);
623     }
624   }
625   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
626 
627   if (m_attach_new == m_attach)
628   {
629     m_attach->readonly = old_readonly;
630   }
631   mx_fastclose_mailbox(m_attach_new);
632 
633   /* Restore old $sort variables */
634   cs_subset_str_native_set(shared->sub, "sort", old_sort, NULL);
635   cs_subset_str_native_set(shared->sub, "sort_aux", old_sort_aux, NULL);
636   cs_subset_str_native_set(shared->sub, "use_threads", old_use_threads, NULL);
637   if (added_attachment)
638     mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
639   return IR_SUCCESS;
640 }
641 
642 /**
643  * op_compose_edit_bcc - Edit the BCC list - Implements ::compose_function_t - @ingroup compose_function_api
644  */
op_compose_edit_bcc(struct ComposeSharedData * shared,int op)645 static int op_compose_edit_bcc(struct ComposeSharedData *shared, int op)
646 {
647 #ifdef USE_NNTP
648   if (shared->news)
649     return IR_NO_ACTION;
650 #endif
651   if (!edit_address_list(HDR_BCC, &shared->email->env->bcc))
652     return IR_NO_ACTION;
653 
654   update_crypt_info(shared);
655   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
656   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
657   return IR_SUCCESS;
658 }
659 
660 /**
661  * op_compose_edit_cc - Edit the CC list - Implements ::compose_function_t - @ingroup compose_function_api
662  */
op_compose_edit_cc(struct ComposeSharedData * shared,int op)663 static int op_compose_edit_cc(struct ComposeSharedData *shared, int op)
664 {
665 #ifdef USE_NNTP
666   if (shared->news)
667     return IR_NO_ACTION;
668 #endif
669   if (!edit_address_list(HDR_CC, &shared->email->env->cc))
670     return IR_NO_ACTION;
671 
672   update_crypt_info(shared);
673   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
674   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
675   return IR_SUCCESS;
676 }
677 
678 /**
679  * op_compose_edit_description - Edit attachment description - Implements ::compose_function_t - @ingroup compose_function_api
680  */
op_compose_edit_description(struct ComposeSharedData * shared,int op)681 static int op_compose_edit_description(struct ComposeSharedData *shared, int op)
682 {
683   if (!check_count(shared->adata->actx))
684     return IR_NO_ACTION;
685   char buf[PATH_MAX];
686   struct AttachPtr *cur_att =
687       current_attachment(shared->adata->actx, shared->adata->menu);
688   mutt_str_copy(buf, cur_att->body->description, sizeof(buf));
689   /* header names should not be translated */
690   if (mutt_get_field("Description: ", buf, sizeof(buf), MUTT_COMP_NO_FLAGS,
691                      false, NULL, NULL) == 0)
692   {
693     if (!mutt_str_equal(cur_att->body->description, buf))
694     {
695       mutt_str_replace(&cur_att->body->description, buf);
696       menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
697       mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
698       return IR_SUCCESS;
699     }
700   }
701   return IR_NO_ACTION;
702 }
703 
704 /**
705  * op_compose_edit_encoding - Edit attachment transfer-encoding - Implements ::compose_function_t - @ingroup compose_function_api
706  */
op_compose_edit_encoding(struct ComposeSharedData * shared,int op)707 static int op_compose_edit_encoding(struct ComposeSharedData *shared, int op)
708 {
709   if (!check_count(shared->adata->actx))
710     return IR_NO_ACTION;
711   struct AttachPtr *cur_att =
712       current_attachment(shared->adata->actx, shared->adata->menu);
713   char buf[PATH_MAX];
714   mutt_str_copy(buf, ENCODING(cur_att->body->encoding), sizeof(buf));
715   if ((mutt_get_field("Content-Transfer-Encoding: ", buf, sizeof(buf),
716                       MUTT_COMP_NO_FLAGS, false, NULL, NULL) == 0) &&
717       (buf[0] != '\0'))
718   {
719     int enc = mutt_check_encoding(buf);
720     if ((enc != ENC_OTHER) && (enc != ENC_UUENCODED))
721     {
722       if (enc != cur_att->body->encoding)
723       {
724         cur_att->body->encoding = enc;
725         menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
726         notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
727         mutt_clear_error();
728         mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
729         return IR_SUCCESS;
730       }
731     }
732     else
733     {
734       mutt_error(_("Invalid encoding"));
735       return IR_ERROR;
736     }
737   }
738   return IR_NO_ACTION;
739 }
740 
741 /**
742  * op_compose_edit_fcc - Enter a file to save a copy of this message in - Implements ::compose_function_t - @ingroup compose_function_api
743  */
op_compose_edit_fcc(struct ComposeSharedData * shared,int op)744 static int op_compose_edit_fcc(struct ComposeSharedData *shared, int op)
745 {
746   int rc = IR_NO_ACTION;
747   struct Buffer *fname = mutt_buffer_pool_get();
748   mutt_buffer_copy(fname, shared->fcc);
749   if (mutt_buffer_get_field(Prompts[HDR_FCC], fname, MUTT_FILE | MUTT_CLEAR,
750                             false, NULL, NULL, NULL) == 0)
751   {
752     if (!mutt_str_equal(shared->fcc->data, fname->data))
753     {
754       mutt_buffer_copy(shared->fcc, fname);
755       mutt_buffer_pretty_mailbox(shared->fcc);
756       shared->fcc_set = true;
757       notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
758       mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
759       rc = IR_SUCCESS;
760     }
761   }
762   mutt_buffer_pool_release(&fname);
763   return rc;
764 }
765 
766 /**
767  * op_compose_edit_file - Edit the file to be attached - Implements ::compose_function_t - @ingroup compose_function_api
768  */
op_compose_edit_file(struct ComposeSharedData * shared,int op)769 static int op_compose_edit_file(struct ComposeSharedData *shared, int op)
770 {
771   if (!check_count(shared->adata->actx))
772     return IR_NO_ACTION;
773   struct AttachPtr *cur_att =
774       current_attachment(shared->adata->actx, shared->adata->menu);
775   const char *const c_editor = cs_subset_string(shared->sub, "editor");
776   mutt_edit_file(NONULL(c_editor), cur_att->body->filename);
777   mutt_update_encoding(cur_att->body, shared->sub);
778   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
779   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
780   /* Unconditional hook since editor was invoked */
781   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
782   return IR_SUCCESS;
783 }
784 
785 /**
786  * op_compose_edit_from - Edit the from field - Implements ::compose_function_t - @ingroup compose_function_api
787  */
op_compose_edit_from(struct ComposeSharedData * shared,int op)788 static int op_compose_edit_from(struct ComposeSharedData *shared, int op)
789 {
790   if (!edit_address_list(HDR_FROM, &shared->email->env->from))
791     return IR_NO_ACTION;
792 
793   update_crypt_info(shared);
794   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
795   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
796   return IR_SUCCESS;
797 }
798 
799 /**
800  * op_compose_edit_headers - Edit the message with headers - Implements ::compose_function_t - @ingroup compose_function_api
801  */
op_compose_edit_headers(struct ComposeSharedData * shared,int op)802 static int op_compose_edit_headers(struct ComposeSharedData *shared, int op)
803 {
804   mutt_rfc3676_space_unstuff(shared->email);
805   const char *tag = NULL;
806   char *err = NULL;
807   mutt_env_to_local(shared->email->env);
808   const char *const c_editor = cs_subset_string(shared->sub, "editor");
809   mutt_edit_headers(NONULL(c_editor), shared->email->body->filename,
810                     shared->email, shared->fcc);
811   if (mutt_env_to_intl(shared->email->env, &tag, &err))
812   {
813     mutt_error(_("Bad IDN in '%s': '%s'"), tag, err);
814     FREE(&err);
815   }
816   update_crypt_info(shared);
817   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
818 
819   mutt_rfc3676_space_stuff(shared->email);
820   mutt_update_encoding(shared->email->body, shared->sub);
821 
822   /* attachments may have been added */
823   if (shared->adata->actx->idxlen &&
824       shared->adata->actx->idx[shared->adata->actx->idxlen - 1]->body->next)
825   {
826     mutt_actx_entries_free(shared->adata->actx);
827     update_menu(shared->adata->actx, shared->adata->menu, true);
828   }
829 
830   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
831   /* Unconditional hook since editor was invoked */
832   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
833   return IR_SUCCESS;
834 }
835 
836 /**
837  * op_compose_edit_language - Edit the 'Content-Language' of the attachment - Implements ::compose_function_t - @ingroup compose_function_api
838  */
op_compose_edit_language(struct ComposeSharedData * shared,int op)839 static int op_compose_edit_language(struct ComposeSharedData *shared, int op)
840 {
841   if (!check_count(shared->adata->actx))
842     return IR_NO_ACTION;
843 
844   int rc = IR_NO_ACTION;
845   struct AttachPtr *cur_att =
846       current_attachment(shared->adata->actx, shared->adata->menu);
847   char buf[PATH_MAX];
848   mutt_str_copy(buf, cur_att->body->language, sizeof(buf));
849   if (mutt_get_field("Content-Language: ", buf, sizeof(buf), MUTT_COMP_NO_FLAGS,
850                      false, NULL, NULL) == 0)
851   {
852     if (!mutt_str_equal(cur_att->body->language, buf))
853     {
854       cur_att->body->language = mutt_str_dup(buf);
855       menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
856       notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
857       mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
858       rc = IR_SUCCESS;
859     }
860     mutt_clear_error();
861   }
862   else
863   {
864     mutt_warning(_("Empty 'Content-Language'"));
865     rc = IR_ERROR;
866   }
867   return rc;
868 }
869 
870 /**
871  * op_compose_edit_message - Edit the message - Implements ::compose_function_t - @ingroup compose_function_api
872  */
op_compose_edit_message(struct ComposeSharedData * shared,int op)873 static int op_compose_edit_message(struct ComposeSharedData *shared, int op)
874 {
875   const bool c_edit_headers = cs_subset_bool(shared->sub, "edit_headers");
876   if (!c_edit_headers)
877   {
878     mutt_rfc3676_space_unstuff(shared->email);
879     const char *const c_editor = cs_subset_string(shared->sub, "editor");
880     mutt_edit_file(c_editor, shared->email->body->filename);
881     mutt_rfc3676_space_stuff(shared->email);
882     mutt_update_encoding(shared->email->body, shared->sub);
883     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
884     /* Unconditional hook since editor was invoked */
885     mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
886     return IR_SUCCESS;
887   }
888 
889   return op_compose_edit_headers(shared, op);
890 }
891 
892 /**
893  * op_compose_edit_mime - Edit attachment using mailcap entry - Implements ::compose_function_t - @ingroup compose_function_api
894  */
op_compose_edit_mime(struct ComposeSharedData * shared,int op)895 static int op_compose_edit_mime(struct ComposeSharedData *shared, int op)
896 {
897   if (!check_count(shared->adata->actx))
898     return IR_NO_ACTION;
899   struct AttachPtr *cur_att =
900       current_attachment(shared->adata->actx, shared->adata->menu);
901   if (!mutt_edit_attachment(cur_att->body))
902     return IR_NO_ACTION;
903 
904   mutt_update_encoding(cur_att->body, shared->sub);
905   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
906   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
907   return IR_SUCCESS;
908 }
909 
910 /**
911  * op_compose_edit_reply_to - Edit the Reply-To field - Implements ::compose_function_t - @ingroup compose_function_api
912  */
op_compose_edit_reply_to(struct ComposeSharedData * shared,int op)913 static int op_compose_edit_reply_to(struct ComposeSharedData *shared, int op)
914 {
915   if (!edit_address_list(HDR_REPLYTO, &shared->email->env->reply_to))
916     return IR_NO_ACTION;
917 
918   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
919   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
920   return IR_SUCCESS;
921 }
922 
923 /**
924  * op_compose_edit_subject - Edit the subject of this message - Implements ::compose_function_t - @ingroup compose_function_api
925  */
op_compose_edit_subject(struct ComposeSharedData * shared,int op)926 static int op_compose_edit_subject(struct ComposeSharedData *shared, int op)
927 {
928   char buf[PATH_MAX];
929   mutt_str_copy(buf, shared->email->env->subject, sizeof(buf));
930   if (mutt_get_field(Prompts[HDR_SUBJECT], buf, sizeof(buf), MUTT_COMP_NO_FLAGS,
931                      false, NULL, NULL) == 0)
932   {
933     if (!mutt_str_equal(shared->email->env->subject, buf))
934     {
935       mutt_str_replace(&shared->email->env->subject, buf);
936       notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
937       mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
938       return IR_SUCCESS;
939     }
940   }
941   return IR_NO_ACTION;
942 }
943 
944 /**
945  * op_compose_edit_to - Edit the TO list - Implements ::compose_function_t - @ingroup compose_function_api
946  */
op_compose_edit_to(struct ComposeSharedData * shared,int op)947 static int op_compose_edit_to(struct ComposeSharedData *shared, int op)
948 {
949 #ifdef USE_NNTP
950   if (shared->news)
951     return IR_NO_ACTION;
952 #endif
953   if (!edit_address_list(HDR_TO, &shared->email->env->to))
954     return IR_NO_ACTION;
955 
956   update_crypt_info(shared);
957   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
958   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
959   return IR_SUCCESS;
960 }
961 
962 /**
963  * op_compose_get_attachment - Get a temporary copy of an attachment - Implements ::compose_function_t - @ingroup compose_function_api
964  */
op_compose_get_attachment(struct ComposeSharedData * shared,int op)965 static int op_compose_get_attachment(struct ComposeSharedData *shared, int op)
966 {
967   if (!check_count(shared->adata->actx))
968     return IR_NO_ACTION;
969   struct AttachPtr *cur_att =
970       current_attachment(shared->adata->actx, shared->adata->menu);
971   if (shared->adata->menu->tagprefix)
972   {
973     for (struct Body *top = shared->email->body; top; top = top->next)
974     {
975       if (top->tagged)
976         mutt_get_tmp_attachment(top);
977     }
978     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
979   }
980   else if (mutt_get_tmp_attachment(cur_att->body) == 0)
981     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
982 
983   /* No send2hook since this doesn't change the message. */
984   return IR_SUCCESS;
985 }
986 
987 /**
988  * op_compose_group_alts - Group tagged attachments as 'multipart/alternative' - Implements ::compose_function_t - @ingroup compose_function_api
989  */
op_compose_group_alts(struct ComposeSharedData * shared,int op)990 static int op_compose_group_alts(struct ComposeSharedData *shared, int op)
991 {
992   static const char *ALTS_TAG = "Alternatives for \"%s\"";
993   if (shared->adata->menu->tagged < 2)
994   {
995     mutt_error(
996         _("Grouping 'alternatives' requires at least 2 tagged messages"));
997     return IR_ERROR;
998   }
999 
1000   struct Body *group = mutt_body_new();
1001   group->type = TYPE_MULTIPART;
1002   group->subtype = mutt_str_dup("alternative");
1003   group->disposition = DISP_INLINE;
1004 
1005   struct Body *alts = NULL;
1006   /* group tagged message into a multipart/alternative */
1007   struct Body *bptr = shared->email->body;
1008   for (int i = 0; bptr;)
1009   {
1010     if (bptr->tagged)
1011     {
1012       bptr->tagged = false;
1013       bptr->disposition = DISP_INLINE;
1014 
1015       /* for first match, set group desc according to match */
1016       if (!group->description)
1017       {
1018         char *p = bptr->description ? bptr->description : bptr->filename;
1019         if (p)
1020         {
1021           group->description = mutt_mem_calloc(1, strlen(p) + strlen(ALTS_TAG) + 1);
1022           sprintf(group->description, ALTS_TAG, p);
1023         }
1024       }
1025 
1026       // append bptr to the alts list, and remove from the shared->email->body list
1027       if (alts)
1028       {
1029         alts->next = bptr;
1030         bptr = bptr->next;
1031         alts = alts->next;
1032         alts->next = NULL;
1033       }
1034       else
1035       {
1036         group->parts = bptr;
1037         alts = bptr;
1038         bptr = bptr->next;
1039         alts->next = NULL;
1040       }
1041 
1042       for (int j = i; j < shared->adata->actx->idxlen - 1; j++)
1043       {
1044         shared->adata->actx->idx[j] = shared->adata->actx->idx[j + 1];
1045         shared->adata->actx->idx[j + 1] = NULL; /* for debug reason */
1046       }
1047       shared->adata->actx->idxlen--;
1048     }
1049     else
1050     {
1051       bptr = bptr->next;
1052       i++;
1053     }
1054   }
1055 
1056   group->next = NULL;
1057   mutt_generate_boundary(&group->parameter);
1058 
1059   /* if no group desc yet, make one up */
1060   if (!group->description)
1061     group->description = mutt_str_dup("unknown alternative group");
1062 
1063   struct AttachPtr *gptr = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1064   gptr->body = group;
1065   update_idx(shared->adata->menu, shared->adata->actx, gptr);
1066   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
1067   return IR_SUCCESS;
1068 }
1069 
1070 /**
1071  * op_compose_group_lingual - Group tagged attachments as 'multipart/multilingual' - Implements ::compose_function_t - @ingroup compose_function_api
1072  */
op_compose_group_lingual(struct ComposeSharedData * shared,int op)1073 static int op_compose_group_lingual(struct ComposeSharedData *shared, int op)
1074 {
1075   static const char *LINGUAL_TAG = "Multilingual part for \"%s\"";
1076   if (shared->adata->menu->tagged < 2)
1077   {
1078     mutt_error(
1079         _("Grouping 'multilingual' requires at least 2 tagged messages"));
1080     return IR_ERROR;
1081   }
1082 
1083   /* traverse to see whether all the parts have Content-Language: set */
1084   int tagged_with_lang_num = 0;
1085   for (struct Body *b = shared->email->body; b; b = b->next)
1086     if (b->tagged && b->language && *b->language)
1087       tagged_with_lang_num++;
1088 
1089   if (shared->adata->menu->tagged != tagged_with_lang_num)
1090   {
1091     if (mutt_yesorno(_("Not all parts have 'Content-Language' set, continue?"), MUTT_YES) != MUTT_YES)
1092     {
1093       mutt_message(_("Not sending this message"));
1094       return IR_ERROR;
1095     }
1096   }
1097 
1098   struct Body *group = mutt_body_new();
1099   group->type = TYPE_MULTIPART;
1100   group->subtype = mutt_str_dup("multilingual");
1101   group->disposition = DISP_INLINE;
1102 
1103   struct Body *alts = NULL;
1104   /* group tagged message into a multipart/multilingual */
1105   struct Body *bptr = shared->email->body;
1106   for (int i = 0; bptr;)
1107   {
1108     if (bptr->tagged)
1109     {
1110       bptr->tagged = false;
1111       bptr->disposition = DISP_INLINE;
1112 
1113       /* for first match, set group desc according to match */
1114       if (!group->description)
1115       {
1116         char *p = bptr->description ? bptr->description : bptr->filename;
1117         if (p)
1118         {
1119           group->description = mutt_mem_calloc(1, strlen(p) + strlen(LINGUAL_TAG) + 1);
1120           sprintf(group->description, LINGUAL_TAG, p);
1121         }
1122       }
1123 
1124       // append bptr to the alts list, and remove from the shared->email->body list
1125       if (alts)
1126       {
1127         alts->next = bptr;
1128         bptr = bptr->next;
1129         alts = alts->next;
1130         alts->next = NULL;
1131       }
1132       else
1133       {
1134         group->parts = bptr;
1135         alts = bptr;
1136         bptr = bptr->next;
1137         alts->next = NULL;
1138       }
1139 
1140       for (int j = i; j < shared->adata->actx->idxlen - 1; j++)
1141       {
1142         shared->adata->actx->idx[j] = shared->adata->actx->idx[j + 1];
1143         shared->adata->actx->idx[j + 1] = NULL; /* for debug reason */
1144       }
1145       shared->adata->actx->idxlen--;
1146     }
1147     else
1148     {
1149       bptr = bptr->next;
1150       i++;
1151     }
1152   }
1153 
1154   group->next = NULL;
1155   mutt_generate_boundary(&group->parameter);
1156 
1157   /* if no group desc yet, make one up */
1158   if (!group->description)
1159     group->description = mutt_str_dup("unknown multilingual group");
1160 
1161   struct AttachPtr *gptr = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1162   gptr->body = group;
1163   update_idx(shared->adata->menu, shared->adata->actx, gptr);
1164   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
1165   return IR_SUCCESS;
1166 }
1167 
1168 /**
1169  * op_compose_ispell - Run ispell on the message - Implements ::compose_function_t - @ingroup compose_function_api
1170  */
op_compose_ispell(struct ComposeSharedData * shared,int op)1171 static int op_compose_ispell(struct ComposeSharedData *shared, int op)
1172 {
1173   endwin();
1174   const char *const c_ispell = cs_subset_string(shared->sub, "ispell");
1175   char buf[PATH_MAX];
1176   snprintf(buf, sizeof(buf), "%s -x %s", NONULL(c_ispell), shared->email->body->filename);
1177   if (mutt_system(buf) == -1)
1178   {
1179     mutt_error(_("Error running \"%s\""), buf);
1180     return IR_ERROR;
1181   }
1182 
1183   mutt_update_encoding(shared->email->body, shared->sub);
1184   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
1185   return IR_SUCCESS;
1186 }
1187 
1188 /**
1189  * op_compose_move_down - Move an attachment down in the attachment list - Implements ::compose_function_t - @ingroup compose_function_api
1190  */
op_compose_move_down(struct ComposeSharedData * shared,int op)1191 static int op_compose_move_down(struct ComposeSharedData *shared, int op)
1192 {
1193   int index = menu_get_index(shared->adata->menu);
1194   if (index < 0)
1195     return IR_ERROR;
1196 
1197   if (index == (shared->adata->actx->idxlen - 1))
1198   {
1199     mutt_error(_("Attachment is already at bottom"));
1200     return IR_NO_ACTION;
1201   }
1202   if (index == 0)
1203   {
1204     mutt_error(_("The fundamental part can't be moved"));
1205     return IR_ERROR;
1206   }
1207   compose_attach_swap(shared->email->body, shared->adata->actx->idx, index);
1208   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
1209   menu_set_index(shared->adata->menu, index + 1);
1210   return IR_SUCCESS;
1211 }
1212 
1213 /**
1214  * op_compose_move_up - Move an attachment up in the attachment list - Implements ::compose_function_t - @ingroup compose_function_api
1215  */
op_compose_move_up(struct ComposeSharedData * shared,int op)1216 static int op_compose_move_up(struct ComposeSharedData *shared, int op)
1217 {
1218   int index = menu_get_index(shared->adata->menu);
1219   if (index == 0)
1220   {
1221     mutt_error(_("Attachment is already at top"));
1222     return IR_NO_ACTION;
1223   }
1224   if (index == 1)
1225   {
1226     mutt_error(_("The fundamental part can't be moved"));
1227     return IR_ERROR;
1228   }
1229   compose_attach_swap(shared->email->body, shared->adata->actx->idx, index - 1);
1230   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
1231   menu_set_index(shared->adata->menu, index - 1);
1232   return IR_SUCCESS;
1233 }
1234 
1235 /**
1236  * op_compose_new_mime - Compose new attachment using mailcap entry - Implements ::compose_function_t - @ingroup compose_function_api
1237  */
op_compose_new_mime(struct ComposeSharedData * shared,int op)1238 static int op_compose_new_mime(struct ComposeSharedData *shared, int op)
1239 {
1240   struct Buffer *fname = mutt_buffer_pool_get();
1241   if ((mutt_buffer_get_field(_("New file: "), fname, MUTT_FILE, false, NULL, NULL, NULL) != 0) ||
1242       mutt_buffer_is_empty(fname))
1243   {
1244     mutt_buffer_pool_release(&fname);
1245     return IR_NO_ACTION;
1246   }
1247   mutt_buffer_expand_path(fname);
1248 
1249   /* Call to lookup_mime_type () ?  maybe later */
1250   char type[256] = { 0 };
1251   if ((mutt_get_field("Content-Type: ", type, sizeof(type), MUTT_COMP_NO_FLAGS,
1252                       false, NULL, NULL) != 0) ||
1253       (type[0] == '\0'))
1254   {
1255     mutt_buffer_pool_release(&fname);
1256     return IR_NO_ACTION;
1257   }
1258 
1259   char *p = strchr(type, '/');
1260   if (!p)
1261   {
1262     mutt_error(_("Content-Type is of the form base/sub"));
1263     mutt_buffer_pool_release(&fname);
1264     return IR_ERROR;
1265   }
1266   *p++ = 0;
1267   enum ContentType itype = mutt_check_mime_type(type);
1268   if (itype == TYPE_OTHER)
1269   {
1270     mutt_error(_("Unknown Content-Type %s"), type);
1271     mutt_buffer_pool_release(&fname);
1272     return IR_ERROR;
1273   }
1274   struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1275   /* Touch the file */
1276   FILE *fp = mutt_file_fopen(mutt_buffer_string(fname), "w");
1277   if (!fp)
1278   {
1279     mutt_error(_("Can't create file %s"), mutt_buffer_string(fname));
1280     FREE(&ap);
1281     mutt_buffer_pool_release(&fname);
1282     return IR_ERROR;
1283   }
1284   mutt_file_fclose(&fp);
1285 
1286   ap->body = mutt_make_file_attach(mutt_buffer_string(fname), shared->sub);
1287   if (!ap->body)
1288   {
1289     mutt_error(_("What we have here is a failure to make an attachment"));
1290     FREE(&ap);
1291     mutt_buffer_pool_release(&fname);
1292     return IR_ERROR;
1293   }
1294   update_idx(shared->adata->menu, shared->adata->actx, ap);
1295 
1296   struct AttachPtr *cur_att =
1297       current_attachment(shared->adata->actx, shared->adata->menu);
1298   cur_att->body->type = itype;
1299   mutt_str_replace(&cur_att->body->subtype, p);
1300   cur_att->body->unlink = true;
1301   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
1302   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
1303 
1304   if (mutt_compose_attachment(cur_att->body))
1305   {
1306     mutt_update_encoding(cur_att->body, shared->sub);
1307     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
1308   }
1309   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1310   mutt_buffer_pool_release(&fname);
1311   return IR_SUCCESS;
1312 }
1313 
1314 /**
1315  * op_compose_pgp_menu - Show PGP options - Implements ::compose_function_t - @ingroup compose_function_api
1316  */
op_compose_pgp_menu(struct ComposeSharedData * shared,int op)1317 static int op_compose_pgp_menu(struct ComposeSharedData *shared, int op)
1318 {
1319   const SecurityFlags old_flags = shared->email->security;
1320   if (!(WithCrypto & APPLICATION_PGP))
1321     return IR_NOT_IMPL;
1322   if (!crypt_has_module_backend(APPLICATION_PGP))
1323   {
1324     mutt_error(_("No PGP backend configured"));
1325     return IR_ERROR;
1326   }
1327   if (((WithCrypto & APPLICATION_SMIME) != 0) && (shared->email->security & APPLICATION_SMIME))
1328   {
1329     if (shared->email->security & (SEC_ENCRYPT | SEC_SIGN))
1330     {
1331       if (mutt_yesorno(_("S/MIME already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
1332       {
1333         mutt_clear_error();
1334         return IR_NO_ACTION;
1335       }
1336       shared->email->security &= ~(SEC_ENCRYPT | SEC_SIGN);
1337     }
1338     shared->email->security &= ~APPLICATION_SMIME;
1339     shared->email->security |= APPLICATION_PGP;
1340     update_crypt_info(shared);
1341   }
1342   shared->email->security = crypt_pgp_send_menu(shared->email);
1343   update_crypt_info(shared);
1344   if (old_flags == shared->email->security)
1345     return IR_NO_ACTION;
1346 
1347   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1348   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1349   return IR_SUCCESS;
1350 }
1351 
1352 /**
1353  * op_compose_postpone_message - Save this message to send later - Implements ::compose_function_t - @ingroup compose_function_api
1354  */
op_compose_postpone_message(struct ComposeSharedData * shared,int op)1355 static int op_compose_postpone_message(struct ComposeSharedData *shared, int op)
1356 {
1357   if (check_attachments(shared->adata->actx, shared->sub) != 0)
1358   {
1359     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
1360     return IR_ERROR;
1361   }
1362 
1363   shared->rc = 1;
1364   return IR_DONE;
1365 }
1366 
1367 /**
1368  * op_compose_rename_attachment - Send attachment with a different name - Implements ::compose_function_t - @ingroup compose_function_api
1369  */
op_compose_rename_attachment(struct ComposeSharedData * shared,int op)1370 static int op_compose_rename_attachment(struct ComposeSharedData *shared, int op)
1371 {
1372   if (!check_count(shared->adata->actx))
1373     return IR_NO_ACTION;
1374   char *src = NULL;
1375   struct AttachPtr *cur_att =
1376       current_attachment(shared->adata->actx, shared->adata->menu);
1377   if (cur_att->body->d_filename)
1378     src = cur_att->body->d_filename;
1379   else
1380     src = cur_att->body->filename;
1381   struct Buffer *fname = mutt_buffer_pool_get();
1382   mutt_buffer_strcpy(fname, mutt_path_basename(NONULL(src)));
1383   int ret = mutt_buffer_get_field(_("Send attachment with name: "), fname,
1384                                   MUTT_FILE, false, NULL, NULL, NULL);
1385   if (ret == 0)
1386   {
1387     // It's valid to set an empty string here, to erase what was set
1388     mutt_str_replace(&cur_att->body->d_filename, mutt_buffer_string(fname));
1389     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
1390   }
1391   mutt_buffer_pool_release(&fname);
1392   return IR_SUCCESS;
1393 }
1394 
1395 /**
1396  * op_compose_rename_file - Rename/move an attached file - Implements ::compose_function_t - @ingroup compose_function_api
1397  */
op_compose_rename_file(struct ComposeSharedData * shared,int op)1398 static int op_compose_rename_file(struct ComposeSharedData *shared, int op)
1399 {
1400   if (!check_count(shared->adata->actx))
1401     return IR_NO_ACTION;
1402   struct AttachPtr *cur_att =
1403       current_attachment(shared->adata->actx, shared->adata->menu);
1404   struct Buffer *fname = mutt_buffer_pool_get();
1405   mutt_buffer_strcpy(fname, cur_att->body->filename);
1406   mutt_buffer_pretty_mailbox(fname);
1407   if ((mutt_buffer_get_field(_("Rename to: "), fname, MUTT_FILE, false, NULL, NULL, NULL) == 0) &&
1408       !mutt_buffer_is_empty(fname))
1409   {
1410     struct stat st = { 0 };
1411     if (stat(cur_att->body->filename, &st) == -1)
1412     {
1413       /* L10N: "stat" is a system call. Do "man 2 stat" for more information. */
1414       mutt_error(_("Can't stat %s: %s"), mutt_buffer_string(fname), strerror(errno));
1415       mutt_buffer_pool_release(&fname);
1416       return IR_ERROR;
1417     }
1418 
1419     mutt_buffer_expand_path(fname);
1420     if (mutt_file_rename(cur_att->body->filename, mutt_buffer_string(fname)))
1421     {
1422       mutt_buffer_pool_release(&fname);
1423       return IR_ERROR;
1424     }
1425 
1426     mutt_str_replace(&cur_att->body->filename, mutt_buffer_string(fname));
1427     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
1428 
1429     if (cur_att->body->stamp >= st.st_mtime)
1430       mutt_stamp_attachment(cur_att->body);
1431     mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1432   }
1433   mutt_buffer_pool_release(&fname);
1434   return IR_SUCCESS;
1435 }
1436 
1437 /**
1438  * op_compose_send_message - Send the message - Implements ::compose_function_t - @ingroup compose_function_api
1439  */
op_compose_send_message(struct ComposeSharedData * shared,int op)1440 static int op_compose_send_message(struct ComposeSharedData *shared, int op)
1441 {
1442   /* Note: We don't invoke send2-hook here, since we want to leave
1443    * users an opportunity to change settings from the ":" prompt.  */
1444   if (check_attachments(shared->adata->actx, shared->sub) != 0)
1445   {
1446     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
1447     return IR_NO_ACTION;
1448   }
1449 
1450 #ifdef MIXMASTER
1451   if (!STAILQ_EMPTY(&shared->email->chain) && (mix_check_message(shared->email) != 0))
1452     return IR_NO_ACTION;
1453 #endif
1454 
1455   if (!shared->fcc_set && !mutt_buffer_is_empty(shared->fcc))
1456   {
1457     const enum QuadOption c_copy = cs_subset_quad(shared->sub, "copy");
1458     enum QuadOption ans =
1459         query_quadoption(c_copy, _("Save a copy of this message?"));
1460     if (ans == MUTT_ABORT)
1461       return IR_NO_ACTION;
1462     else if (ans == MUTT_NO)
1463       mutt_buffer_reset(shared->fcc);
1464   }
1465 
1466   shared->rc = 0;
1467   return IR_DONE;
1468 }
1469 
1470 /**
1471  * op_compose_smime_menu - Show S/MIME options - Implements ::compose_function_t - @ingroup compose_function_api
1472  */
op_compose_smime_menu(struct ComposeSharedData * shared,int op)1473 static int op_compose_smime_menu(struct ComposeSharedData *shared, int op)
1474 {
1475   const SecurityFlags old_flags = shared->email->security;
1476   if (!(WithCrypto & APPLICATION_SMIME))
1477     return IR_NOT_IMPL;
1478   if (!crypt_has_module_backend(APPLICATION_SMIME))
1479   {
1480     mutt_error(_("No S/MIME backend configured"));
1481     return IR_ERROR;
1482   }
1483 
1484   if (((WithCrypto & APPLICATION_PGP) != 0) && (shared->email->security & APPLICATION_PGP))
1485   {
1486     if (shared->email->security & (SEC_ENCRYPT | SEC_SIGN))
1487     {
1488       if (mutt_yesorno(_("PGP already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
1489       {
1490         mutt_clear_error();
1491         return IR_NO_ACTION;
1492       }
1493       shared->email->security &= ~(SEC_ENCRYPT | SEC_SIGN);
1494     }
1495     shared->email->security &= ~APPLICATION_PGP;
1496     shared->email->security |= APPLICATION_SMIME;
1497     update_crypt_info(shared);
1498   }
1499   shared->email->security = crypt_smime_send_menu(shared->email);
1500   update_crypt_info(shared);
1501   if (old_flags == shared->email->security)
1502     return IR_NO_ACTION;
1503 
1504   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1505   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1506   return IR_SUCCESS;
1507 }
1508 
1509 /**
1510  * op_compose_toggle_disposition - Toggle disposition between inline/attachment - Implements ::compose_function_t - @ingroup compose_function_api
1511  */
op_compose_toggle_disposition(struct ComposeSharedData * shared,int op)1512 static int op_compose_toggle_disposition(struct ComposeSharedData *shared, int op)
1513 {
1514   /* toggle the content-disposition between inline/attachment */
1515   struct AttachPtr *cur_att =
1516       current_attachment(shared->adata->actx, shared->adata->menu);
1517   cur_att->body->disposition =
1518       (cur_att->body->disposition == DISP_INLINE) ? DISP_ATTACH : DISP_INLINE;
1519   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
1520   return IR_SUCCESS;
1521 }
1522 
1523 /**
1524  * op_compose_toggle_recode - Toggle recoding of this attachment - Implements ::compose_function_t - @ingroup compose_function_api
1525  */
op_compose_toggle_recode(struct ComposeSharedData * shared,int op)1526 static int op_compose_toggle_recode(struct ComposeSharedData *shared, int op)
1527 {
1528   if (!check_count(shared->adata->actx))
1529     return IR_NO_ACTION;
1530   struct AttachPtr *cur_att =
1531       current_attachment(shared->adata->actx, shared->adata->menu);
1532   if (!mutt_is_text_part(cur_att->body))
1533   {
1534     mutt_error(_("Recoding only affects text attachments"));
1535     return IR_ERROR;
1536   }
1537   cur_att->body->noconv = !cur_att->body->noconv;
1538   if (cur_att->body->noconv)
1539     mutt_message(_("The current attachment won't be converted"));
1540   else
1541     mutt_message(_("The current attachment will be converted"));
1542   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
1543   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1544   return IR_SUCCESS;
1545 }
1546 
1547 /**
1548  * op_compose_toggle_unlink - Toggle whether to delete file after sending it - Implements ::compose_function_t - @ingroup compose_function_api
1549  */
op_compose_toggle_unlink(struct ComposeSharedData * shared,int op)1550 static int op_compose_toggle_unlink(struct ComposeSharedData *shared, int op)
1551 {
1552   if (!check_count(shared->adata->actx))
1553     return IR_NO_ACTION;
1554   struct AttachPtr *cur_att =
1555       current_attachment(shared->adata->actx, shared->adata->menu);
1556   cur_att->body->unlink = !cur_att->body->unlink;
1557 
1558   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_INDEX);
1559   /* No send2hook since this doesn't change the message. */
1560   return IR_SUCCESS;
1561 }
1562 
1563 /**
1564  * op_compose_update_encoding - Update an attachment's encoding info - Implements ::compose_function_t - @ingroup compose_function_api
1565  */
op_compose_update_encoding(struct ComposeSharedData * shared,int op)1566 static int op_compose_update_encoding(struct ComposeSharedData *shared, int op)
1567 {
1568   if (!check_count(shared->adata->actx))
1569     return IR_NO_ACTION;
1570   bool encoding_updated = false;
1571   if (shared->adata->menu->tagprefix)
1572   {
1573     struct Body *top = NULL;
1574     for (top = shared->email->body; top; top = top->next)
1575     {
1576       if (top->tagged)
1577       {
1578         encoding_updated = true;
1579         mutt_update_encoding(top, shared->sub);
1580       }
1581     }
1582     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
1583   }
1584   else
1585   {
1586     struct AttachPtr *cur_att =
1587         current_attachment(shared->adata->actx, shared->adata->menu);
1588     mutt_update_encoding(cur_att->body, shared->sub);
1589     encoding_updated = true;
1590     menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
1591     notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
1592   }
1593   if (encoding_updated)
1594     mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1595   return IR_SUCCESS;
1596 }
1597 
1598 /**
1599  * op_compose_write_message - Write the message to a folder - Implements ::compose_function_t - @ingroup compose_function_api
1600  */
op_compose_write_message(struct ComposeSharedData * shared,int op)1601 static int op_compose_write_message(struct ComposeSharedData *shared, int op)
1602 {
1603   int rc = IR_NO_ACTION;
1604   struct Buffer *fname = mutt_buffer_pool_get();
1605   if (shared->mailbox)
1606   {
1607     mutt_buffer_strcpy(fname, mailbox_path(shared->mailbox));
1608     mutt_buffer_pretty_mailbox(fname);
1609   }
1610   if (shared->adata->actx->idxlen)
1611     shared->email->body = shared->adata->actx->idx[0]->body;
1612   if ((mutt_buffer_enter_fname(_("Write message to mailbox"), fname, true,
1613                                shared->mailbox, false, NULL, NULL, MUTT_SEL_NO_FLAGS) != -1) &&
1614       !mutt_buffer_is_empty(fname))
1615   {
1616     mutt_message(_("Writing message to %s ..."), mutt_buffer_string(fname));
1617     mutt_buffer_expand_path(fname);
1618 
1619     if (shared->email->body->next)
1620       shared->email->body = mutt_make_multipart(shared->email->body);
1621 
1622     if (mutt_write_fcc(mutt_buffer_string(fname), shared->email, NULL, false,
1623                        NULL, NULL, shared->sub) == 0)
1624       mutt_message(_("Message written"));
1625 
1626     shared->email->body = mutt_remove_multipart(shared->email->body);
1627     rc = IR_SUCCESS;
1628   }
1629   mutt_buffer_pool_release(&fname);
1630   return rc;
1631 }
1632 
1633 /**
1634  * op_delete - Delete the current entry - Implements ::compose_function_t - @ingroup compose_function_api
1635  */
op_delete(struct ComposeSharedData * shared,int op)1636 static int op_delete(struct ComposeSharedData *shared, int op)
1637 {
1638   if (!check_count(shared->adata->actx))
1639     return IR_NO_ACTION;
1640   struct AttachPtr *cur_att =
1641       current_attachment(shared->adata->actx, shared->adata->menu);
1642   if (cur_att->unowned)
1643     cur_att->body->unlink = false;
1644   int index = menu_get_index(shared->adata->menu);
1645   if (delete_attachment(shared->adata->actx, index) == -1)
1646     return IR_ERROR;
1647   update_menu(shared->adata->actx, shared->adata->menu, false);
1648   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
1649   index = menu_get_index(shared->adata->menu);
1650   if (index == 0)
1651     shared->email->body = shared->adata->actx->idx[0]->body;
1652 
1653   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1654   return IR_SUCCESS;
1655 }
1656 
1657 /**
1658  * op_display_headers - Display message and toggle header weeding - Implements ::compose_function_t - @ingroup compose_function_api
1659  */
op_display_headers(struct ComposeSharedData * shared,int op)1660 static int op_display_headers(struct ComposeSharedData *shared, int op)
1661 {
1662   if (!check_count(shared->adata->actx))
1663     return IR_NO_ACTION;
1664   mutt_attach_display_loop(shared->sub, shared->adata->menu, op, shared->email,
1665                            shared->adata->actx, false);
1666   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_FULL);
1667   /* no send2hook, since this doesn't modify the message */
1668   return IR_SUCCESS;
1669 }
1670 
1671 /**
1672  * op_edit_type - Edit attachment content type - Implements ::compose_function_t - @ingroup compose_function_api
1673  */
op_edit_type(struct ComposeSharedData * shared,int op)1674 static int op_edit_type(struct ComposeSharedData *shared, int op)
1675 {
1676   if (!check_count(shared->adata->actx))
1677     return IR_NO_ACTION;
1678 
1679   struct AttachPtr *cur_att =
1680       current_attachment(shared->adata->actx, shared->adata->menu);
1681   if (!mutt_edit_content_type(NULL, cur_att->body, NULL))
1682     return IR_NO_ACTION;
1683 
1684   /* this may have been a change to text/something */
1685   mutt_update_encoding(cur_att->body, shared->sub);
1686   menu_queue_redraw(shared->adata->menu, MENU_REDRAW_CURRENT);
1687   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1688   return IR_SUCCESS;
1689 }
1690 
1691 /**
1692  * op_exit - Exit this menu - Implements ::compose_function_t - @ingroup compose_function_api
1693  */
op_exit(struct ComposeSharedData * shared,int op)1694 static int op_exit(struct ComposeSharedData *shared, int op)
1695 {
1696   const enum QuadOption c_postpone = cs_subset_quad(shared->sub, "postpone");
1697   enum QuadOption ans =
1698       query_quadoption(c_postpone, _("Save (postpone) draft message?"));
1699   if (ans == MUTT_NO)
1700   {
1701     for (int i = 0; i < shared->adata->actx->idxlen; i++)
1702       if (shared->adata->actx->idx[i]->unowned)
1703         shared->adata->actx->idx[i]->body->unlink = false;
1704 
1705     if (!(shared->flags & MUTT_COMPOSE_NOFREEHEADER))
1706     {
1707       for (int i = 0; i < shared->adata->actx->idxlen; i++)
1708       {
1709         /* avoid freeing other attachments */
1710         shared->adata->actx->idx[i]->body->next = NULL;
1711         /* See the comment in delete_attachment() */
1712         if (!shared->adata->actx->idx[i]->body->email)
1713           shared->adata->actx->idx[i]->body->parts = NULL;
1714         mutt_body_free(&shared->adata->actx->idx[i]->body);
1715       }
1716     }
1717     shared->rc = -1;
1718     return IR_DONE;
1719   }
1720   else if (ans == MUTT_ABORT)
1721     return IR_NO_ACTION;
1722 
1723   return op_compose_postpone_message(shared, op);
1724 }
1725 
1726 /**
1727  * op_filter - Filter attachment through a shell command - Implements ::compose_function_t - @ingroup compose_function_api
1728  */
op_filter(struct ComposeSharedData * shared,int op)1729 static int op_filter(struct ComposeSharedData *shared, int op)
1730 {
1731   if (!check_count(shared->adata->actx))
1732     return IR_NO_ACTION;
1733   struct AttachPtr *cur_att =
1734       current_attachment(shared->adata->actx, shared->adata->menu);
1735   mutt_pipe_attachment_list(shared->adata->actx, NULL, shared->adata->menu->tagprefix,
1736                             cur_att->body, (op == OP_FILTER));
1737   if (op == OP_FILTER) /* cte might have changed */
1738     menu_queue_redraw(shared->adata->menu,
1739                       shared->adata->menu->tagprefix ? MENU_REDRAW_FULL : MENU_REDRAW_CURRENT);
1740   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ATTACH, NULL);
1741   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1742   return IR_SUCCESS;
1743 }
1744 
1745 /**
1746  * op_forget_passphrase - Wipe passphrases from memory - Implements ::compose_function_t - @ingroup compose_function_api
1747  */
op_forget_passphrase(struct ComposeSharedData * shared,int op)1748 static int op_forget_passphrase(struct ComposeSharedData *shared, int op)
1749 {
1750   crypt_forget_passphrase();
1751   return IR_SUCCESS;
1752 }
1753 
1754 /**
1755  * op_print - Print the current entry - Implements ::compose_function_t - @ingroup compose_function_api
1756  */
op_print(struct ComposeSharedData * shared,int op)1757 static int op_print(struct ComposeSharedData *shared, int op)
1758 {
1759   if (!check_count(shared->adata->actx))
1760     return IR_NO_ACTION;
1761   struct AttachPtr *cur_att =
1762       current_attachment(shared->adata->actx, shared->adata->menu);
1763   mutt_print_attachment_list(shared->adata->actx, NULL,
1764                              shared->adata->menu->tagprefix, cur_att->body);
1765   /* no send2hook, since this doesn't modify the message */
1766   return IR_SUCCESS;
1767 }
1768 
1769 /**
1770  * op_save - Save message/attachment to a mailbox/file - Implements ::compose_function_t - @ingroup compose_function_api
1771  */
op_save(struct ComposeSharedData * shared,int op)1772 static int op_save(struct ComposeSharedData *shared, int op)
1773 {
1774   if (!check_count(shared->adata->actx))
1775     return IR_NO_ACTION;
1776   struct AttachPtr *cur_att =
1777       current_attachment(shared->adata->actx, shared->adata->menu);
1778   mutt_save_attachment_list(shared->adata->actx, NULL, shared->adata->menu->tagprefix,
1779                             cur_att->body, NULL, shared->adata->menu);
1780   /* no send2hook, since this doesn't modify the message */
1781   return IR_SUCCESS;
1782 }
1783 
1784 // -----------------------------------------------------------------------------
1785 
1786 #ifdef USE_AUTOCRYPT
1787 /**
1788  * op_compose_autocrypt_menu - Show autocrypt compose menu options - Implements ::compose_function_t - @ingroup compose_function_api
1789  */
op_compose_autocrypt_menu(struct ComposeSharedData * shared,int op)1790 static int op_compose_autocrypt_menu(struct ComposeSharedData *shared, int op)
1791 {
1792   const SecurityFlags old_flags = shared->email->security;
1793   const bool c_autocrypt = cs_subset_bool(shared->sub, "autocrypt");
1794   if (!c_autocrypt)
1795     return IR_NO_ACTION;
1796 
1797   if ((WithCrypto & APPLICATION_SMIME) && (shared->email->security & APPLICATION_SMIME))
1798   {
1799     if (shared->email->security & (SEC_ENCRYPT | SEC_SIGN))
1800     {
1801       if (mutt_yesorno(_("S/MIME already selected. Clear and continue?"), MUTT_YES) != MUTT_YES)
1802       {
1803         mutt_clear_error();
1804         return IR_NO_ACTION;
1805       }
1806       shared->email->security &= ~(SEC_ENCRYPT | SEC_SIGN);
1807     }
1808     shared->email->security &= ~APPLICATION_SMIME;
1809     shared->email->security |= APPLICATION_PGP;
1810     update_crypt_info(shared);
1811   }
1812   autocrypt_compose_menu(shared->email, shared->sub);
1813   update_crypt_info(shared);
1814   if (old_flags == shared->email->security)
1815     return IR_NO_ACTION;
1816 
1817   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1818   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1819   return IR_SUCCESS;
1820 }
1821 #endif
1822 
1823 #ifdef USE_NNTP
1824 /**
1825  * op_compose_edit_followup_to - Edit the Followup-To field - Implements ::compose_function_t - @ingroup compose_function_api
1826  */
op_compose_edit_followup_to(struct ComposeSharedData * shared,int op)1827 static int op_compose_edit_followup_to(struct ComposeSharedData *shared, int op)
1828 {
1829   if (!shared->news)
1830     return IR_NO_ACTION;
1831   char buf[PATH_MAX];
1832   mutt_str_copy(buf, shared->email->env->followup_to, sizeof(buf));
1833   if (mutt_get_field(Prompts[HDR_FOLLOWUPTO], buf, sizeof(buf),
1834                      MUTT_COMP_NO_FLAGS, false, NULL, NULL) == 0)
1835   {
1836     mutt_str_replace(&shared->email->env->followup_to, buf);
1837     notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1838     return IR_SUCCESS;
1839   }
1840   return IR_NO_ACTION;
1841 }
1842 
1843 /**
1844  * op_compose_edit_newsgroups - Edit the newsgroups list - Implements ::compose_function_t - @ingroup compose_function_api
1845  */
op_compose_edit_newsgroups(struct ComposeSharedData * shared,int op)1846 static int op_compose_edit_newsgroups(struct ComposeSharedData *shared, int op)
1847 {
1848   if (!shared->news)
1849     return IR_NO_ACTION;
1850   char buf[PATH_MAX];
1851   mutt_str_copy(buf, shared->email->env->newsgroups, sizeof(buf));
1852   if (mutt_get_field(Prompts[HDR_NEWSGROUPS], buf, sizeof(buf),
1853                      MUTT_COMP_NO_FLAGS, false, NULL, NULL) == 0)
1854   {
1855     mutt_str_replace(&shared->email->env->newsgroups, buf);
1856     notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1857     return IR_SUCCESS;
1858   }
1859   return IR_NO_ACTION;
1860 }
1861 
1862 /**
1863  * op_compose_edit_x_comment_to - Edit the X-Comment-To field - Implements ::compose_function_t - @ingroup compose_function_api
1864  */
op_compose_edit_x_comment_to(struct ComposeSharedData * shared,int op)1865 static int op_compose_edit_x_comment_to(struct ComposeSharedData *shared, int op)
1866 {
1867   const bool c_x_comment_to = cs_subset_bool(shared->sub, "x_comment_to");
1868   if (!(shared->news && c_x_comment_to))
1869     return IR_NO_ACTION;
1870   char buf[PATH_MAX];
1871   mutt_str_copy(buf, shared->email->env->x_comment_to, sizeof(buf));
1872   if (mutt_get_field(Prompts[HDR_XCOMMENTTO], buf, sizeof(buf),
1873                      MUTT_COMP_NO_FLAGS, false, NULL, NULL) == 0)
1874   {
1875     mutt_str_replace(&shared->email->env->x_comment_to, buf);
1876     notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1877     return IR_SUCCESS;
1878   }
1879   return IR_NO_ACTION;
1880 }
1881 #endif
1882 
1883 #ifdef MIXMASTER
1884 /**
1885  * op_compose_mix - Send the message through a mixmaster remailer chain - Implements ::compose_function_t - @ingroup compose_function_api
1886  */
op_compose_mix(struct ComposeSharedData * shared,int op)1887 static int op_compose_mix(struct ComposeSharedData *shared, int op)
1888 {
1889   dlg_select_mixmaster_chain(&shared->email->chain);
1890   mutt_message_hook(NULL, shared->email, MUTT_SEND2_HOOK);
1891   notify_send(shared->notify, NT_COMPOSE, NT_COMPOSE_ENVELOPE, NULL);
1892   return IR_SUCCESS;
1893 }
1894 #endif
1895 
1896 // -----------------------------------------------------------------------------
1897 
1898 /**
1899  * ComposeFunctions - All the NeoMutt functions that the Compose supports
1900  */
1901 struct ComposeFunction ComposeFunctions[] = {
1902   // clang-format off
1903   { OP_COMPOSE_ATTACH_FILE,         op_compose_attach_file },
1904   { OP_COMPOSE_ATTACH_KEY,          op_compose_attach_key },
1905   { OP_COMPOSE_ATTACH_MESSAGE,      op_compose_attach_message },
1906   { OP_COMPOSE_ATTACH_NEWS_MESSAGE, op_compose_attach_message },
1907 #ifdef USE_AUTOCRYPT
1908   { OP_COMPOSE_AUTOCRYPT_MENU,      op_compose_autocrypt_menu },
1909 #endif
1910   { OP_COMPOSE_EDIT_BCC,            op_compose_edit_bcc },
1911   { OP_COMPOSE_EDIT_CC,             op_compose_edit_cc },
1912   { OP_COMPOSE_EDIT_DESCRIPTION,    op_compose_edit_description },
1913   { OP_COMPOSE_EDIT_ENCODING,       op_compose_edit_encoding },
1914   { OP_COMPOSE_EDIT_FCC,            op_compose_edit_fcc },
1915   { OP_COMPOSE_EDIT_FILE,           op_compose_edit_file },
1916 #ifdef USE_NNTP
1917   { OP_COMPOSE_EDIT_FOLLOWUP_TO,    op_compose_edit_followup_to },
1918 #endif
1919   { OP_COMPOSE_EDIT_FROM,           op_compose_edit_from },
1920   { OP_COMPOSE_EDIT_HEADERS,        op_compose_edit_headers },
1921   { OP_COMPOSE_EDIT_LANGUAGE,       op_compose_edit_language },
1922   { OP_COMPOSE_EDIT_MESSAGE,        op_compose_edit_message },
1923   { OP_COMPOSE_EDIT_MIME,           op_compose_edit_mime },
1924 #ifdef USE_NNTP
1925   { OP_COMPOSE_EDIT_NEWSGROUPS,     op_compose_edit_newsgroups },
1926 #endif
1927   { OP_COMPOSE_EDIT_REPLY_TO,       op_compose_edit_reply_to },
1928   { OP_COMPOSE_EDIT_SUBJECT,        op_compose_edit_subject },
1929   { OP_COMPOSE_EDIT_TO,             op_compose_edit_to },
1930 #ifdef USE_NNTP
1931   { OP_COMPOSE_EDIT_X_COMMENT_TO,   op_compose_edit_x_comment_to },
1932 #endif
1933   { OP_COMPOSE_GET_ATTACHMENT,      op_compose_get_attachment },
1934   { OP_COMPOSE_GROUP_ALTS,          op_compose_group_alts },
1935   { OP_COMPOSE_GROUP_LINGUAL,       op_compose_group_lingual },
1936   { OP_COMPOSE_ISPELL,              op_compose_ispell },
1937 #ifdef MIXMASTER
1938   { OP_COMPOSE_MIX,                 op_compose_mix },
1939 #endif
1940   { OP_COMPOSE_MOVE_DOWN,           op_compose_move_down },
1941   { OP_COMPOSE_MOVE_UP,             op_compose_move_up },
1942   { OP_COMPOSE_NEW_MIME,            op_compose_new_mime },
1943   { OP_COMPOSE_PGP_MENU,            op_compose_pgp_menu },
1944   { OP_COMPOSE_POSTPONE_MESSAGE,    op_compose_postpone_message },
1945   { OP_COMPOSE_RENAME_ATTACHMENT,   op_compose_rename_attachment },
1946   { OP_COMPOSE_RENAME_FILE,         op_compose_rename_file },
1947   { OP_COMPOSE_SEND_MESSAGE,        op_compose_send_message },
1948   { OP_COMPOSE_SMIME_MENU,          op_compose_smime_menu },
1949   { OP_COMPOSE_TOGGLE_DISPOSITION,  op_compose_toggle_disposition },
1950   { OP_COMPOSE_TOGGLE_RECODE,       op_compose_toggle_recode },
1951   { OP_COMPOSE_TOGGLE_UNLINK,       op_compose_toggle_unlink },
1952   { OP_COMPOSE_UPDATE_ENCODING,     op_compose_update_encoding },
1953   { OP_COMPOSE_WRITE_MESSAGE,       op_compose_write_message },
1954   { OP_DELETE,                      op_delete },
1955   { OP_DISPLAY_HEADERS,             op_display_headers },
1956   { OP_EDIT_TYPE,                   op_edit_type },
1957   { OP_EXIT,                        op_exit },
1958   { OP_FILTER,                      op_filter },
1959   { OP_FORGET_PASSPHRASE,           op_forget_passphrase },
1960   { OP_PIPE,                        op_filter },
1961   { OP_PRINT,                       op_print },
1962   { OP_SAVE,                        op_save },
1963   { OP_VIEW_ATTACH,                 op_display_headers },
1964   { 0, NULL },
1965   // clang-format on
1966 };
1967 
1968 /**
1969  * compose_function_dispatcher - Perform a Compose function
1970  * @param win_compose Window for Compose
1971  * @param op          Operation to perform, e.g. OP_MAIN_LIMIT
1972  * @retval num #IndexRetval, e.g. #IR_SUCCESS
1973  */
compose_function_dispatcher(struct MuttWindow * win_compose,int op)1974 int compose_function_dispatcher(struct MuttWindow *win_compose, int op)
1975 {
1976   if (!win_compose)
1977   {
1978     mutt_error(_(Not_available_in_this_menu));
1979     return IR_ERROR;
1980   }
1981 
1982   struct MuttWindow *dlg = dialog_find(win_compose);
1983   if (!dlg || !dlg->wdata)
1984     return IR_ERROR;
1985 
1986   int rc = IR_UNKNOWN;
1987   for (size_t i = 0; ComposeFunctions[i].op != OP_NULL; i++)
1988   {
1989     const struct ComposeFunction *fn = &ComposeFunctions[i];
1990     if (fn->op == op)
1991     {
1992       struct ComposeSharedData *shared = dlg->wdata;
1993       rc = fn->function(shared, op);
1994       break;
1995     }
1996   }
1997 
1998   return rc;
1999 }
2000