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