]> granicus.if.org Git - neomutt/blob - recvattach.c
Convert mutt_attach_reply() to use buffer pool
[neomutt] / recvattach.c
1 /**
2  * @file
3  * Routines for managing attachments
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2006 Thomas Roessler <roessler@does-not-exist.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 /**
25  * @page recvattach Routines for managing attachments
26  *
27  * Routines for managing attachments
28  */
29
30 #include "config.h"
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include "mutt/mutt.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
39 #include "core/lib.h"
40 #include "mutt.h"
41 #include "recvattach.h"
42 #include "commands.h"
43 #include "context.h"
44 #include "curs_lib.h"
45 #include "filter.h"
46 #include "format_flags.h"
47 #include "globals.h"
48 #include "handler.h"
49 #include "hdrline.h"
50 #include "hook.h"
51 #include "keymap.h"
52 #include "mailcap.h"
53 #include "mutt_attach.h"
54 #include "mutt_menu.h"
55 #include "mutt_parse.h"
56 #include "mutt_window.h"
57 #include "muttlib.h"
58 #include "mx.h"
59 #include "ncrypt/ncrypt.h"
60 #include "opcodes.h"
61 #include "options.h"
62 #include "recvcmd.h"
63 #include "send.h"
64 #include "sendlib.h"
65 #include "state.h"
66 #ifdef ENABLE_NLS
67 #include <libintl.h>
68 #endif
69
70 /* These Config Variables are only used in recvattach.c */
71 char *C_AttachSaveDir; ///< Config: Default directory where attachments are saved
72 char *C_AttachSaveWithoutPrompting; ///< Config: If true, then don't prompt to save
73 char *C_AttachSep; ///< Config: Separator to add between saved/printed/piped attachments
74 bool C_AttachSplit;    ///< Config: Save/print/pipe tagged messages individually
75 bool C_DigestCollapse; ///< Config: Hide the subparts of a multipart/digest
76 char *C_MessageFormat; ///< Config: printf-like format string for listing attached messages
77
78 static void mutt_update_recvattach_menu(struct AttachCtx *actx, struct Menu *menu, bool init);
79
80 static const char *Mailbox_is_read_only = N_("Mailbox is read-only");
81
82 #define CHECK_READONLY                                                         \
83   if (!Context || !Context->mailbox || Context->mailbox->readonly)             \
84   {                                                                            \
85     mutt_flushinp();                                                           \
86     mutt_error(_(Mailbox_is_read_only));                                       \
87     break;                                                                     \
88   }
89
90 #define CUR_ATTACH actx->idx[actx->v2r[menu->current]]
91
92 static const struct Mapping AttachHelp[] = {
93   { N_("Exit"), OP_EXIT },   { N_("Save"), OP_SAVE }, { N_("Pipe"), OP_PIPE },
94   { N_("Print"), OP_PRINT }, { N_("Help"), OP_HELP }, { NULL, 0 },
95 };
96
97 static const char *Function_not_permitted =
98     N_("Function not permitted in attach-message mode");
99
100 #define CHECK_ATTACH                                                           \
101   if (OptAttachMsg)                                                            \
102   {                                                                            \
103     mutt_flushinp();                                                           \
104     mutt_error(_(Function_not_permitted));                                     \
105     break;                                                                     \
106   }
107
108 /**
109  * mutt_update_v2r - Update the virtual list of attachments
110  * @param actx Attachment context
111  *
112  * Update the record of the number of attachments and the status of the tree.
113  */
114 static void mutt_update_v2r(struct AttachCtx *actx)
115 {
116   int vindex, rindex, curlevel;
117
118   vindex = 0;
119   rindex = 0;
120
121   while (rindex < actx->idxlen)
122   {
123     actx->v2r[vindex++] = rindex;
124     if (actx->idx[rindex]->content->collapsed)
125     {
126       curlevel = actx->idx[rindex]->level;
127       do
128       {
129         rindex++;
130       } while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel));
131     }
132     else
133       rindex++;
134   }
135
136   actx->vcount = vindex;
137 }
138
139 /**
140  * mutt_update_tree - Refresh the list of attachments
141  * @param actx Attachment context
142  */
143 void mutt_update_tree(struct AttachCtx *actx)
144 {
145   char buf[256];
146   char *s = NULL;
147
148   mutt_update_v2r(actx);
149
150   for (int vindex = 0; vindex < actx->vcount; vindex++)
151   {
152     const int rindex = actx->v2r[vindex];
153     actx->idx[rindex]->num = vindex;
154     if ((2 * (actx->idx[rindex]->level + 2)) < sizeof(buf))
155     {
156       if (actx->idx[rindex]->level)
157       {
158         s = buf + 2 * (actx->idx[rindex]->level - 1);
159         *s++ = (actx->idx[rindex]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER;
160         *s++ = MUTT_TREE_HLINE;
161         *s++ = MUTT_TREE_RARROW;
162       }
163       else
164         s = buf;
165       *s = '\0';
166     }
167
168     if (actx->idx[rindex]->tree)
169     {
170       if (mutt_str_strcmp(actx->idx[rindex]->tree, buf) != 0)
171         mutt_str_replace(&actx->idx[rindex]->tree, buf);
172     }
173     else
174       actx->idx[rindex]->tree = mutt_str_strdup(buf);
175
176     if (((2 * (actx->idx[rindex]->level + 2)) < sizeof(buf)) &&
177         actx->idx[rindex]->level)
178     {
179       s = buf + 2 * (actx->idx[rindex]->level - 1);
180       *s++ = (actx->idx[rindex]->content->next) ? '\005' : '\006';
181       *s++ = '\006';
182     }
183   }
184 }
185
186 /**
187  * attach_format_str - Format a string for the attachment menu - Implements ::format_t
188  *
189  * | Expando | Description
190  * |:--------|:--------------------------------------------------------
191  * | \%C     | Character set
192  * | \%c     | Character set: convert?
193  * | \%D     | Deleted flag
194  * | \%d     | Description
195  * | \%e     | MIME content-transfer-encoding
196  * | \%f     | Filename
197  * | \%F     | Filename for content-disposition header
198  * | \%I     | Content-disposition, either I (inline) or A (attachment)
199  * | \%m     | Major MIME type
200  * | \%M     | MIME subtype
201  * | \%n     | Attachment number
202  * | \%Q     | 'Q', if MIME part qualifies for attachment counting
203  * | \%s     | Size
204  * | \%t     | Tagged flag
205  * | \%T     | Tree chars
206  * | \%u     | Unlink
207  * | \%X     | Number of qualifying MIME parts in this part and its children
208  */
209 const char *attach_format_str(char *buf, size_t buflen, size_t col, int cols,
210                               char op, const char *src, const char *prec,
211                               const char *if_str, const char *else_str,
212                               unsigned long data, MuttFormatFlags flags)
213 {
214   char fmt[128];
215   char charset[128];
216   struct AttachPtr *aptr = (struct AttachPtr *) data;
217   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
218
219   switch (op)
220   {
221     case 'C':
222       if (!optional)
223       {
224         if (mutt_is_text_part(aptr->content) &&
225             mutt_body_get_charset(aptr->content, charset, sizeof(charset)))
226         {
227           mutt_format_s(buf, buflen, prec, charset);
228         }
229         else
230           mutt_format_s(buf, buflen, prec, "");
231       }
232       else if (!mutt_is_text_part(aptr->content) ||
233                !mutt_body_get_charset(aptr->content, charset, sizeof(charset)))
234       {
235         optional = false;
236       }
237       break;
238     case 'c':
239       /* XXX */
240       if (!optional)
241       {
242         snprintf(fmt, sizeof(fmt), "%%%sc", prec);
243         snprintf(buf, buflen, fmt,
244                  ((aptr->content->type != TYPE_TEXT) || aptr->content->noconv) ? 'n' : 'c');
245       }
246       else if ((aptr->content->type != TYPE_TEXT) || aptr->content->noconv)
247         optional = false;
248       break;
249     case 'd':
250       if (!optional)
251       {
252         if (aptr->content->description)
253         {
254           mutt_format_s(buf, buflen, prec, aptr->content->description);
255           break;
256         }
257         if (mutt_is_message_type(aptr->content->type, aptr->content->subtype) &&
258             C_MessageFormat && aptr->content->email)
259         {
260           char s[128];
261           mutt_make_string_flags(s, sizeof(s), cols, C_MessageFormat, NULL,
262                                  NULL, aptr->content->email,
263                                  MUTT_FORMAT_FORCESUBJ | MUTT_FORMAT_ARROWCURSOR);
264           if (*s)
265           {
266             mutt_format_s(buf, buflen, prec, s);
267             break;
268           }
269         }
270         if (!aptr->content->d_filename && !aptr->content->filename)
271         {
272           mutt_format_s(buf, buflen, prec, "<no description>");
273           break;
274         }
275       }
276       else if (aptr->content->description ||
277                (mutt_is_message_type(aptr->content->type, aptr->content->subtype) &&
278                 C_MessageFormat && aptr->content->email))
279       {
280         break;
281       }
282     /* fallthrough */
283     case 'F':
284       if (!optional)
285       {
286         if (aptr->content->d_filename)
287         {
288           mutt_format_s(buf, buflen, prec, aptr->content->d_filename);
289           break;
290         }
291       }
292       else if (!aptr->content->d_filename && !aptr->content->filename)
293       {
294         optional = false;
295         break;
296       }
297     /* fallthrough */
298     case 'f':
299       if (!optional)
300       {
301         if (aptr->content->filename && (*aptr->content->filename == '/'))
302         {
303           struct Buffer *path = mutt_buffer_pool_get();
304
305           mutt_buffer_strcpy(path, aptr->content->filename);
306           mutt_buffer_pretty_mailbox(path);
307           mutt_format_s(buf, buflen, prec, mutt_b2s(path));
308           mutt_buffer_pool_release(&path);
309         }
310         else
311           mutt_format_s(buf, buflen, prec, NONULL(aptr->content->filename));
312       }
313       else if (!aptr->content->filename)
314         optional = false;
315       break;
316     case 'D':
317       if (!optional)
318         snprintf(buf, buflen, "%c", aptr->content->deleted ? 'D' : ' ');
319       else if (!aptr->content->deleted)
320         optional = false;
321       break;
322     case 'e':
323       if (!optional)
324         mutt_format_s(buf, buflen, prec, ENCODING(aptr->content->encoding));
325       break;
326     case 'I':
327       if (!optional)
328       {
329         const char dispchar[] = { 'I', 'A', 'F', '-' };
330         char ch;
331
332         if (aptr->content->disposition < sizeof(dispchar))
333           ch = dispchar[aptr->content->disposition];
334         else
335         {
336           mutt_debug(LL_DEBUG1, "ERROR: invalid content-disposition %d\n",
337                      aptr->content->disposition);
338           ch = '!';
339         }
340         snprintf(buf, buflen, "%c", ch);
341       }
342       break;
343     case 'm':
344       if (!optional)
345         mutt_format_s(buf, buflen, prec, TYPE(aptr->content));
346       break;
347     case 'M':
348       if (!optional)
349         mutt_format_s(buf, buflen, prec, aptr->content->subtype);
350       else if (!aptr->content->subtype)
351         optional = false;
352       break;
353     case 'n':
354       if (!optional)
355       {
356         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
357         snprintf(buf, buflen, fmt, aptr->num + 1);
358       }
359       break;
360     case 'Q':
361       if (optional)
362         optional = aptr->content->attach_qualifies;
363       else
364       {
365         snprintf(fmt, sizeof(fmt), "%%%sc", prec);
366         mutt_format_s(buf, buflen, fmt, "Q");
367       }
368       break;
369     case 's':
370     {
371       size_t l;
372       if (flags & MUTT_FORMAT_STAT_FILE)
373       {
374         struct stat st;
375         stat(aptr->content->filename, &st);
376         l = st.st_size;
377       }
378       else
379         l = aptr->content->length;
380
381       if (!optional)
382       {
383         char tmp[128];
384         mutt_str_pretty_size(tmp, sizeof(tmp), l);
385         mutt_format_s(buf, buflen, prec, tmp);
386       }
387       else if (l == 0)
388         optional = false;
389
390       break;
391     }
392     case 't':
393       if (!optional)
394         snprintf(buf, buflen, "%c", aptr->content->tagged ? '*' : ' ');
395       else if (!aptr->content->tagged)
396         optional = false;
397       break;
398     case 'T':
399       if (!optional)
400         mutt_format_s_tree(buf, buflen, prec, NONULL(aptr->tree));
401       else if (!aptr->tree)
402         optional = false;
403       break;
404     case 'u':
405       if (!optional)
406         snprintf(buf, buflen, "%c", aptr->content->unlink ? '-' : ' ');
407       else if (!aptr->content->unlink)
408         optional = false;
409       break;
410     case 'X':
411       if (optional)
412         optional = ((aptr->content->attach_count + aptr->content->attach_qualifies) != 0);
413       else
414       {
415         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
416         snprintf(buf, buflen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
417       }
418       break;
419     default:
420       *buf = '\0';
421   }
422
423   if (optional)
424     mutt_expando_format(buf, buflen, col, cols, if_str, attach_format_str, data,
425                         MUTT_FORMAT_NO_FLAGS);
426   else if (flags & MUTT_FORMAT_OPTIONAL)
427     mutt_expando_format(buf, buflen, col, cols, else_str, attach_format_str,
428                         data, MUTT_FORMAT_NO_FLAGS);
429   return src;
430 }
431
432 /**
433  * attach_make_entry - Format a menu item for the attachment list - Implements Menu::menu_make_entry()
434  */
435 static void attach_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
436 {
437   struct AttachCtx *actx = menu->data;
438
439   mutt_expando_format(buf, buflen, 0, menu->indexwin->cols, NONULL(C_AttachFormat),
440                       attach_format_str, (unsigned long) (actx->idx[actx->v2r[line]]),
441                       MUTT_FORMAT_ARROWCURSOR);
442 }
443
444 /**
445  * attach_tag - Tag an attachment - Implements Menu::menu_tag()
446  */
447 int attach_tag(struct Menu *menu, int sel, int act)
448 {
449   struct AttachCtx *actx = menu->data;
450   struct Body *cur = actx->idx[actx->v2r[sel]]->content;
451   bool ot = cur->tagged;
452
453   cur->tagged = ((act >= 0) ? act : !cur->tagged);
454   return cur->tagged - ot;
455 }
456
457 /**
458  * prepend_savedir - Add #C_AttachSaveDir to the beginning of a path
459  * @param buf Buffer for the result
460  */
461 static void prepend_savedir(struct Buffer *buf)
462 {
463   if (!buf || !buf->data || (buf->data[0] == '/'))
464     return;
465
466   struct Buffer *tmp = mutt_buffer_pool_get();
467   if (C_AttachSaveDir)
468   {
469     mutt_buffer_addstr(tmp, C_AttachSaveDir);
470     if (tmp->dptr[-1] != '/')
471       mutt_buffer_addch(tmp, '/');
472   }
473   else
474     mutt_buffer_addstr(tmp, "./");
475
476   mutt_buffer_addstr(tmp, mutt_b2s(buf));
477   mutt_buffer_strcpy(buf, mutt_b2s(tmp));
478   mutt_buffer_pool_release(&tmp);
479 }
480
481 /**
482  * has_a_message - Determine if the Body has a message (to save)
483  * @param[in]  body Body of the message
484  * @retval true if suitable for saving
485  */
486 static bool has_a_message(struct Body *body)
487 {
488   return (body->email && (body->encoding != ENC_BASE64) &&
489           (body->encoding != ENC_QUOTED_PRINTABLE) &&
490           mutt_is_message_type(body->type, body->subtype));
491 }
492
493 /**
494  * query_save_attachment - Ask the user if we should save the attachment
495  * @param[in]  fp        File handle to the attachment (OPTIONAL)
496  * @param[in]  body      Attachment
497  * @param[in]  e       Email
498  * @param[out] directory Where the attachment was saved
499  * @retval  0 Success
500  * @retval -1 Failure
501  */
502 static int query_save_attachment(FILE *fp, struct Body *body, struct Email *e, char **directory)
503 {
504   char *prompt = NULL;
505   enum SaveAttach opt = MUTT_SAVE_NO_FLAGS;
506   int rc = -1;
507
508   struct Buffer *buf = mutt_buffer_pool_get();
509   struct Buffer *tfile = mutt_buffer_pool_get();
510
511   if (body->filename)
512   {
513     if (directory && *directory)
514     {
515       mutt_buffer_concat_path(buf, *directory, mutt_path_basename(body->filename));
516     }
517     else
518       mutt_buffer_strcpy(buf, body->filename);
519   }
520   else if (has_a_message(body))
521   {
522     mutt_default_save(buf->data, buf->dsize, body->email);
523     mutt_buffer_fix_dptr(buf);
524   }
525
526   prepend_savedir(buf);
527
528   prompt = _("Save to file: ");
529   while (prompt)
530   {
531     if ((mutt_buffer_get_field(prompt, buf, MUTT_FILE | MUTT_CLEAR) != 0) ||
532         mutt_buffer_is_empty(buf))
533     {
534       goto cleanup;
535     }
536
537     prompt = NULL;
538     mutt_buffer_expand_path(buf);
539
540     bool is_message = (fp && has_a_message(body));
541
542     if (is_message)
543     {
544       struct stat st;
545
546       /* check to make sure that this file is really the one the user wants */
547       rc = mutt_save_confirm(mutt_b2s(buf), &st);
548       if (rc == 1)
549       {
550         prompt = _("Save to file: ");
551         continue;
552       }
553       else if (rc == -1)
554         goto cleanup;
555       mutt_buffer_strcpy(tfile, mutt_b2s(buf));
556     }
557     else
558     {
559       rc = mutt_check_overwrite(body->filename, mutt_b2s(buf), tfile, &opt, directory);
560       if (rc == -1)
561         goto cleanup;
562       else if (rc == 1)
563       {
564         prompt = _("Save to file: ");
565         continue;
566       }
567     }
568
569     mutt_message(_("Saving..."));
570     if (mutt_save_attachment(fp, body, mutt_b2s(tfile), opt,
571                              (e || !is_message) ? e : body->email) == 0)
572     {
573       mutt_message(_("Attachment saved"));
574       rc = 0;
575       goto cleanup;
576     }
577     else
578     {
579       prompt = _("Save to file: ");
580       continue;
581     }
582   }
583
584 cleanup:
585   mutt_buffer_pool_release(&buf);
586   mutt_buffer_pool_release(&tfile);
587   return rc;
588 }
589
590 /**
591  * save_without_prompting - Save the attachment, without prompting each time.
592  * @param[in]  fp   File handle to the attachment (OPTIONAL)
593  * @param[in]  body Attachment
594  * @param[in]  e    Email
595  * @retval  0 Success
596  * @retval -1 Failure
597  */
598 static int save_without_prompting(FILE *fp, struct Body *body, struct Email *e)
599 {
600   enum SaveAttach opt = MUTT_SAVE_NO_FLAGS;
601   int rc = -1;
602   struct Buffer *buf = mutt_buffer_pool_get();
603   struct Buffer *tfile = mutt_buffer_pool_get();
604
605   if (body->filename)
606   {
607     mutt_buffer_strcpy(buf, body->filename);
608   }
609   else if (has_a_message(body))
610   {
611     mutt_default_save(buf->data, buf->dsize, body->email);
612   }
613
614   prepend_savedir(buf);
615   mutt_buffer_expand_path(buf);
616
617   bool is_message = (fp && has_a_message(body));
618
619   if (is_message)
620   {
621     mutt_buffer_strcpy(tfile, mutt_b2s(buf));
622   }
623   else
624   {
625     rc = mutt_check_overwrite(body->filename, mutt_b2s(buf), tfile, &opt, NULL);
626     if (rc == -1) // abort or cancel
627       goto cleanup;
628   }
629
630   rc = mutt_save_attachment(fp, body, mutt_b2s(tfile), opt,
631                             (e || !is_message) ? e : body->email);
632
633 cleanup:
634   mutt_buffer_pool_release(&buf);
635   mutt_buffer_pool_release(&tfile);
636   return rc;
637 }
638
639 /**
640  * mutt_save_attachment_list - Save a list of attachments
641  * @param actx Attachment context
642  * @param fp   File handle for the attachment (OPTIONAL)
643  * @param tag  If true, only save the tagged attachments
644  * @param top  First Attachment
645  * @param e  Email
646  * @param menu Menu listing attachments
647  */
648 void mutt_save_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag,
649                                struct Body *top, struct Email *e, struct Menu *menu)
650 {
651   char *directory = NULL;
652   int rc = 1;
653   int last = menu ? menu->current : -1;
654   FILE *fp_out = NULL;
655   int saved_attachments = 0;
656
657   struct Buffer *buf = mutt_buffer_pool_get();
658   struct Buffer *tfile = mutt_buffer_pool_get();
659
660   for (int i = 0; !tag || (i < actx->idxlen); i++)
661   {
662     if (tag)
663     {
664       fp = actx->idx[i]->fp;
665       top = actx->idx[i]->content;
666     }
667     if (!tag || top->tagged)
668     {
669       if (!C_AttachSplit)
670       {
671         if (mutt_buffer_is_empty(buf))
672         {
673           enum SaveAttach opt = MUTT_SAVE_NO_FLAGS;
674
675           mutt_buffer_strcpy(buf, mutt_path_basename(NONULL(top->filename)));
676           prepend_savedir(buf);
677
678           if ((mutt_buffer_get_field(_("Save to file: "), buf, MUTT_FILE | MUTT_CLEAR) != 0) ||
679               mutt_buffer_is_empty(buf))
680           {
681             goto cleanup;
682           }
683           mutt_buffer_expand_path(buf);
684           if (mutt_check_overwrite(top->filename, mutt_b2s(buf), tfile, &opt, NULL))
685             goto cleanup;
686           rc = mutt_save_attachment(fp, top, mutt_b2s(tfile), opt, e);
687           if ((rc == 0) && C_AttachSep && (fp_out = fopen(mutt_b2s(tfile), "a")))
688           {
689             fprintf(fp_out, "%s", C_AttachSep);
690             mutt_file_fclose(&fp_out);
691           }
692         }
693         else
694         {
695           rc = mutt_save_attachment(fp, top, mutt_b2s(tfile), MUTT_SAVE_APPEND, e);
696           if ((rc == 0) && C_AttachSep && (fp_out = fopen(mutt_b2s(tfile), "a")))
697           {
698             fprintf(fp_out, "%s", C_AttachSep);
699             mutt_file_fclose(&fp_out);
700           }
701         }
702       }
703       else
704       {
705         if (tag && menu && top->aptr)
706         {
707           menu->oldcurrent = menu->current;
708           menu->current = top->aptr->num;
709           menu_check_recenter(menu);
710           menu->redraw |= REDRAW_MOTION;
711
712           menu_redraw(menu);
713         }
714         if (C_AttachSaveWithoutPrompting)
715         {
716           // Save each file, with no prompting, using the configured 'AttachSaveDir'
717           rc = save_without_prompting(fp, top, e);
718           if (rc == 0)
719             saved_attachments++;
720         }
721         else
722         {
723           // Save each file, prompting the user for the location each time.
724           if (query_save_attachment(fp, top, e, &directory) == -1)
725             break;
726         }
727       }
728     }
729     if (!tag)
730       break;
731   }
732
733   FREE(&directory);
734
735   if (tag && menu)
736   {
737     menu->oldcurrent = menu->current;
738     menu->current = last;
739     menu_check_recenter(menu);
740     menu->redraw |= REDRAW_MOTION;
741   }
742
743   if (!C_AttachSplit && (rc == 0))
744     mutt_message(_("Attachment saved"));
745
746   if (C_AttachSaveWithoutPrompting && (rc == 0))
747   {
748     mutt_message(ngettext("Attachment saved", "%d attachments saved", saved_attachments),
749                  saved_attachments);
750   }
751
752 cleanup:
753   mutt_buffer_pool_release(&buf);
754   mutt_buffer_pool_release(&tfile);
755 }
756
757 /**
758  * query_pipe_attachment - Ask the user if we should pipe the attachment
759  * @param command Command to pipe the attachment to
760  * @param fp      File handle to the attachment (OPTIONAL)
761  * @param body    Attachment
762  * @param filter  Is this command a filter?
763  */
764 static void query_pipe_attachment(char *command, FILE *fp, struct Body *body, bool filter)
765 {
766   char tfile[PATH_MAX];
767
768   if (filter)
769   {
770     char warning[PATH_MAX + 256];
771     snprintf(warning, sizeof(warning),
772              _("WARNING!  You are about to overwrite %s, continue?"), body->filename);
773     if (mutt_yesorno(warning, MUTT_NO) != MUTT_YES)
774     {
775       mutt_window_clearline(MuttMessageWindow, 0);
776       return;
777     }
778     mutt_mktemp(tfile, sizeof(tfile));
779   }
780   else
781     tfile[0] = '\0';
782
783   if (mutt_pipe_attachment(fp, body, command, tfile))
784   {
785     if (filter)
786     {
787       mutt_file_unlink(body->filename);
788       mutt_file_rename(tfile, body->filename);
789       mutt_update_encoding(body);
790       mutt_message(_("Attachment filtered"));
791     }
792   }
793   else
794   {
795     if (filter && tfile[0])
796       mutt_file_unlink(tfile);
797   }
798 }
799
800 /**
801  * pipe_attachment - Pipe the attachment to a command
802  * @param fp    File handle to the attachment (OPTIONAL)
803  * @param b     Attachment
804  * @param state File state for decoding the attachment
805  */
806 static void pipe_attachment(FILE *fp, struct Body *b, struct State *state)
807 {
808   if (!state || !state->fp_out)
809     return;
810
811   if (fp)
812   {
813     state->fp_in = fp;
814     mutt_decode_attachment(b, state);
815     if (C_AttachSep)
816       state_puts(C_AttachSep, state);
817   }
818   else
819   {
820     FILE *fp_in = fopen(b->filename, "r");
821     if (!fp_in)
822     {
823       mutt_perror("fopen");
824       return;
825     }
826     mutt_file_copy_stream(fp_in, state->fp_out);
827     mutt_file_fclose(&fp_in);
828     if (C_AttachSep)
829       state_puts(C_AttachSep, state);
830   }
831 }
832
833 /**
834  * pipe_attachment_list - Pipe a list of attachments to a command
835  * @param command Command to pipe the attachment to
836  * @param actx    Attachment context
837  * @param fp      File handle to the attachment (OPTIONAL)
838  * @param tag     If true, only save the tagged attachments
839  * @param top     First Attachment
840  * @param filter  Is this command a filter?
841  * @param state   File state for decoding the attachments
842  */
843 static void pipe_attachment_list(char *command, struct AttachCtx *actx, FILE *fp, bool tag,
844                                  struct Body *top, bool filter, struct State *state)
845 {
846   for (int i = 0; !tag || (i < actx->idxlen); i++)
847   {
848     if (tag)
849     {
850       fp = actx->idx[i]->fp;
851       top = actx->idx[i]->content;
852     }
853     if (!tag || top->tagged)
854     {
855       if (!filter && !C_AttachSplit)
856         pipe_attachment(fp, top, state);
857       else
858         query_pipe_attachment(command, fp, top, filter);
859     }
860     if (!tag)
861       break;
862   }
863 }
864
865 /**
866  * mutt_pipe_attachment_list - Pipe a list of attachments to a command
867  * @param actx   Attachment context
868  * @param fp     File handle to the attachment (OPTIONAL)
869  * @param tag    If true, only save the tagged attachments
870  * @param top    First Attachment
871  * @param filter Is this command a filter?
872  */
873 void mutt_pipe_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag,
874                                struct Body *top, bool filter)
875 {
876   struct State state = { 0 };
877   char buf[PATH_MAX];
878
879   if (fp)
880     filter = false; /* sanity check: we can't filter in the recv case yet */
881
882   buf[0] = '\0';
883   /* perform charset conversion on text attachments when piping */
884   state.flags = MUTT_CHARCONV;
885
886   if ((mutt_get_field((filter ? _("Filter through: ") : _("Pipe to: ")), buf,
887                       sizeof(buf), MUTT_CMD) != 0) ||
888       (buf[0] == '\0'))
889   {
890     return;
891   }
892
893   mutt_expand_path(buf, sizeof(buf));
894
895   if (!filter && !C_AttachSplit)
896   {
897     mutt_endwin();
898     pid_t pid = mutt_create_filter(buf, &state.fp_out, NULL, NULL);
899     pipe_attachment_list(buf, actx, fp, tag, top, filter, &state);
900     mutt_file_fclose(&state.fp_out);
901     if ((mutt_wait_filter(pid) != 0) || C_WaitKey)
902       mutt_any_key_to_continue(NULL);
903   }
904   else
905     pipe_attachment_list(buf, actx, fp, tag, top, filter, &state);
906 }
907
908 /**
909  * can_print - Do we know how to print this attachment type?
910  * @param actx Attachment
911  * @param top  Body of email
912  * @param tag  Apply to all tagged Attachments
913  * @retval true If (all) the Attachment(s) are printable
914  */
915 static bool can_print(struct AttachCtx *actx, struct Body *top, bool tag)
916 {
917   char type[256];
918
919   for (int i = 0; !tag || (i < actx->idxlen); i++)
920   {
921     if (tag)
922       top = actx->idx[i]->content;
923     snprintf(type, sizeof(type), "%s/%s", TYPE(top), top->subtype);
924     if (!tag || top->tagged)
925     {
926       if (!mailcap_lookup(top, type, NULL, MUTT_MC_PRINT))
927       {
928         if ((mutt_str_strcasecmp("text/plain", top->subtype) != 0) &&
929             (mutt_str_strcasecmp("application/postscript", top->subtype) != 0))
930         {
931           if (!mutt_can_decode(top))
932           {
933             /* L10N: s gets replaced by a MIME type, e.g. "text/plain" or
934                application/octet-stream.  */
935             mutt_error(_("I don't know how to print %s attachments"), type);
936             return false;
937           }
938         }
939       }
940     }
941     if (!tag)
942       break;
943   }
944   return true;
945 }
946
947 /**
948  * print_attachment_list - Print a list of Attachments
949  * @param actx  Attachment context
950  * @param fp    File handle to the attachment (OPTIONAL)
951  * @param tag   Apply to all tagged Attachments
952  * @param top   First Attachment
953  * @param state File state for decoding the attachments
954  */
955 static void print_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag,
956                                   struct Body *top, struct State *state)
957 {
958   char type[256];
959
960   for (int i = 0; !tag || (i < actx->idxlen); i++)
961   {
962     if (tag)
963     {
964       fp = actx->idx[i]->fp;
965       top = actx->idx[i]->content;
966     }
967     if (!tag || top->tagged)
968     {
969       snprintf(type, sizeof(type), "%s/%s", TYPE(top), top->subtype);
970       if (!C_AttachSplit && !mailcap_lookup(top, type, NULL, MUTT_MC_PRINT))
971       {
972         if ((mutt_str_strcasecmp("text/plain", top->subtype) == 0) ||
973             (mutt_str_strcasecmp("application/postscript", top->subtype) == 0))
974         {
975           pipe_attachment(fp, top, state);
976         }
977         else if (mutt_can_decode(top))
978         {
979           /* decode and print */
980
981           FILE *fp_in = NULL;
982           struct Buffer *newfile = mutt_buffer_pool_get();
983
984           mutt_buffer_mktemp(newfile);
985           if (mutt_decode_save_attachment(fp, top, mutt_b2s(newfile),
986                                           MUTT_PRINTING, MUTT_SAVE_NO_FLAGS) == 0)
987           {
988             if (!state->fp_out)
989             {
990               mutt_error(
991                   "BUG in print_attachment_list().  Please report this. ");
992               return;
993             }
994
995             fp_in = fopen(mutt_b2s(newfile), "r");
996             if (fp_in)
997             {
998               mutt_file_copy_stream(fp_in, state->fp_out);
999               mutt_file_fclose(&fp_in);
1000               if (C_AttachSep)
1001                 state_puts(C_AttachSep, state);
1002             }
1003           }
1004           mutt_file_unlink(mutt_b2s(newfile));
1005           mutt_buffer_pool_release(&newfile);
1006         }
1007       }
1008       else
1009         mutt_print_attachment(fp, top);
1010     }
1011     if (!tag)
1012       break;
1013   }
1014 }
1015
1016 /**
1017  * mutt_print_attachment_list - Print a list of Attachments
1018  * @param actx Attachment context
1019  * @param fp   File handle to the attachment (OPTIONAL)
1020  * @param tag  Apply to all tagged Attachments
1021  * @param top  First Attachment
1022  */
1023 void mutt_print_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, struct Body *top)
1024 {
1025   char prompt[128];
1026   struct State state = { 0 };
1027   int tagmsgcount = 0;
1028
1029   if (tag)
1030     for (int i = 0; i < actx->idxlen; i++)
1031       if (actx->idx[i]->content->tagged)
1032         tagmsgcount++;
1033
1034   snprintf(prompt, sizeof(prompt),
1035            /* L10N: Although we now the precise number of tagged messages, we
1036               do not show it to the user.  So feel free to use a "generic
1037               plural" as plural translation if your language has one. */
1038            tag ? ngettext("Print tagged attachment?", "Print %d tagged attachments?", tagmsgcount) :
1039                  _("Print attachment?"),
1040            tagmsgcount);
1041   if (query_quadoption(C_Print, prompt) != MUTT_YES)
1042     return;
1043
1044   if (!C_AttachSplit)
1045   {
1046     if (!can_print(actx, top, tag))
1047       return;
1048     mutt_endwin();
1049     pid_t pid = mutt_create_filter(NONULL(C_PrintCommand), &state.fp_out, NULL, NULL);
1050     print_attachment_list(actx, fp, tag, top, &state);
1051     mutt_file_fclose(&state.fp_out);
1052     if ((mutt_wait_filter(pid) != 0) || C_WaitKey)
1053       mutt_any_key_to_continue(NULL);
1054   }
1055   else
1056     print_attachment_list(actx, fp, tag, top, &state);
1057 }
1058
1059 /**
1060  * recvattach_extract_pgp_keys - Extract PGP keys from attachments
1061  * @param actx Attachment context
1062  * @param menu Menu listing attachments
1063  */
1064 static void recvattach_extract_pgp_keys(struct AttachCtx *actx, struct Menu *menu)
1065 {
1066   if (!menu->tagprefix)
1067     crypt_pgp_extract_key_from_attachment(CUR_ATTACH->fp, CUR_ATTACH->content);
1068   else
1069   {
1070     for (int i = 0; i < actx->idxlen; i++)
1071     {
1072       if (actx->idx[i]->content->tagged)
1073       {
1074         crypt_pgp_extract_key_from_attachment(actx->idx[i]->fp, actx->idx[i]->content);
1075       }
1076     }
1077   }
1078 }
1079
1080 /**
1081  * recvattach_pgp_check_traditional - Is the Attachment inline PGP?
1082  * @param actx Attachment to check
1083  * @param menu Menu listing Attachments
1084  * @retval 1 If the (tagged) Attachment(s) are inline PGP
1085  *
1086  * @note If the menu->tagprefix is set, all the tagged attachments will be checked.
1087  */
1088 static int recvattach_pgp_check_traditional(struct AttachCtx *actx, struct Menu *menu)
1089 {
1090   int rc = 0;
1091
1092   if (!menu->tagprefix)
1093     rc = crypt_pgp_check_traditional(CUR_ATTACH->fp, CUR_ATTACH->content, true);
1094   else
1095   {
1096     for (int i = 0; i < actx->idxlen; i++)
1097       if (actx->idx[i]->content->tagged)
1098         rc = rc || crypt_pgp_check_traditional(actx->idx[i]->fp, actx->idx[i]->content, true);
1099   }
1100
1101   return rc;
1102 }
1103
1104 /**
1105  * recvattach_edit_content_type - Edit the content type of an attachment
1106  * @param actx Attachment context
1107  * @param menu Menu listing Attachments
1108  * @param e  Email
1109  */
1110 static void recvattach_edit_content_type(struct AttachCtx *actx,
1111                                          struct Menu *menu, struct Email *e)
1112 {
1113   if (!mutt_edit_content_type(e, CUR_ATTACH->content, CUR_ATTACH->fp))
1114     return;
1115
1116   /* The mutt_update_recvattach_menu() will overwrite any changes
1117    * made to a decrypted CUR_ATTACH->content, so warn the user. */
1118   if (CUR_ATTACH->decrypted)
1119   {
1120     mutt_message(
1121         _("Structural changes to decrypted attachments are not supported"));
1122     mutt_sleep(1);
1123   }
1124   /* Editing the content type can rewrite the body structure. */
1125   for (int i = 0; i < actx->idxlen; i++)
1126     actx->idx[i]->content = NULL;
1127   mutt_actx_entries_free(actx);
1128   mutt_update_recvattach_menu(actx, menu, true);
1129 }
1130
1131 /**
1132  * mutt_attach_display_loop - Event loop for the Attachment menu
1133  * @param menu Menu listing Attachments
1134  * @param op   Operation, e.g. OP_VIEW_ATTACH
1135  * @param e  Email
1136  * @param actx Attachment context
1137  * @param recv true if these are received attachments (rather than in compose)
1138  * @retval num Operation performed
1139  */
1140 int mutt_attach_display_loop(struct Menu *menu, int op, struct Email *e,
1141                              struct AttachCtx *actx, bool recv)
1142 {
1143   do
1144   {
1145     switch (op)
1146     {
1147       case OP_DISPLAY_HEADERS:
1148         bool_str_toggle(Config, "weed", NULL);
1149         /* fallthrough */
1150
1151       case OP_VIEW_ATTACH:
1152         op = mutt_view_attachment(CUR_ATTACH->fp, CUR_ATTACH->content,
1153                                   MUTT_VA_REGULAR, e, actx);
1154         break;
1155
1156       case OP_NEXT_ENTRY:
1157       case OP_MAIN_NEXT_UNDELETED: /* hack */
1158         if (menu->current < menu->max - 1)
1159         {
1160           menu->current++;
1161           op = OP_VIEW_ATTACH;
1162         }
1163         else
1164           op = OP_NULL;
1165         break;
1166       case OP_PREV_ENTRY:
1167       case OP_MAIN_PREV_UNDELETED: /* hack */
1168         if (menu->current > 0)
1169         {
1170           menu->current--;
1171           op = OP_VIEW_ATTACH;
1172         }
1173         else
1174           op = OP_NULL;
1175         break;
1176       case OP_EDIT_TYPE:
1177         /* when we edit the content-type, we should redisplay the attachment
1178          * immediately */
1179         mutt_edit_content_type(e, CUR_ATTACH->content, CUR_ATTACH->fp);
1180         if (recv)
1181           recvattach_edit_content_type(actx, menu, e);
1182         else
1183           mutt_edit_content_type(e, CUR_ATTACH->content, CUR_ATTACH->fp);
1184
1185         menu->redraw |= REDRAW_INDEX;
1186         op = OP_VIEW_ATTACH;
1187         break;
1188       /* functions which are passed through from the pager */
1189       case OP_CHECK_TRADITIONAL:
1190         if (!(WithCrypto & APPLICATION_PGP) || (e && e->security & PGP_TRADITIONAL_CHECKED))
1191         {
1192           op = OP_NULL;
1193           break;
1194         }
1195       /* fallthrough */
1196       case OP_ATTACH_COLLAPSE:
1197         if (recv)
1198           return op;
1199       /* fallthrough */
1200       default:
1201         op = OP_NULL;
1202     }
1203   } while (op != OP_NULL);
1204
1205   return op;
1206 }
1207
1208 /**
1209  * mutt_generate_recvattach_list - Create a list of attachments
1210  * @param actx        Attachment context
1211  * @param e           Email
1212  * @param parts       Body of email
1213  * @param fp          File to read from
1214  * @param parent_type Type, e.g. #TYPE_MULTIPART
1215  * @param level       Attachment depth
1216  * @param decrypted   True if attachment has been decrypted
1217  */
1218 void mutt_generate_recvattach_list(struct AttachCtx *actx, struct Email *e,
1219                                    struct Body *parts, FILE *fp,
1220                                    int parent_type, int level, bool decrypted)
1221 {
1222   struct Body *m = NULL;
1223   struct Body *new_body = NULL;
1224   FILE *fp_new = NULL;
1225   SecurityFlags type;
1226   int need_secured, secured;
1227
1228   for (m = parts; m; m = m->next)
1229   {
1230     need_secured = 0;
1231     secured = 0;
1232
1233     if (((WithCrypto & APPLICATION_SMIME) != 0) && (type = mutt_is_application_smime(m)))
1234     {
1235       need_secured = 1;
1236
1237       if (type & SEC_ENCRYPT)
1238       {
1239         if (!crypt_valid_passphrase(APPLICATION_SMIME))
1240           goto decrypt_failed;
1241
1242         if (e->env)
1243           crypt_smime_getkeys(e->env);
1244       }
1245
1246       secured = !crypt_smime_decrypt_mime(fp, &fp_new, m, &new_body);
1247       /* If the decrypt/verify-opaque doesn't generate mime output, an empty
1248        * text/plain type will still be returned by mutt_read_mime_header().
1249        * We can't distinguish an actual part from a failure, so only use a
1250        * text/plain that results from a single top-level part. */
1251       if (secured && (new_body->type == TYPE_TEXT) &&
1252           (mutt_str_strcasecmp("plain", new_body->subtype) == 0) &&
1253           ((parts != m) || m->next))
1254       {
1255         mutt_body_free(&new_body);
1256         mutt_file_fclose(&fp_new);
1257         goto decrypt_failed;
1258       }
1259
1260       if (secured && (type & SEC_ENCRYPT))
1261         e->security |= SMIME_ENCRYPT;
1262     }
1263
1264     if (((WithCrypto & APPLICATION_PGP) != 0) &&
1265         (mutt_is_multipart_encrypted(m) || mutt_is_malformed_multipart_pgp_encrypted(m)))
1266     {
1267       need_secured = 1;
1268
1269       if (!crypt_valid_passphrase(APPLICATION_PGP))
1270         goto decrypt_failed;
1271
1272       secured = !crypt_pgp_decrypt_mime(fp, &fp_new, m, &new_body);
1273
1274       if (secured)
1275         e->security |= PGP_ENCRYPT;
1276     }
1277
1278     if (need_secured && secured)
1279     {
1280       mutt_actx_add_fp(actx, fp_new);
1281       mutt_actx_add_body(actx, new_body);
1282       mutt_generate_recvattach_list(actx, e, new_body, fp_new, parent_type, level, 1);
1283       continue;
1284     }
1285
1286   decrypt_failed:
1287     /* Fall through and show the original parts if decryption fails */
1288     if (need_secured && !secured)
1289       mutt_error(_("Can't decrypt encrypted message"));
1290
1291     /* Strip out the top level multipart */
1292     if ((m->type == TYPE_MULTIPART) && m->parts && !need_secured &&
1293         ((parent_type == -1) && mutt_str_strcasecmp("alternative", m->subtype)))
1294     {
1295       mutt_generate_recvattach_list(actx, e, m->parts, fp, m->type, level, decrypted);
1296     }
1297     else
1298     {
1299       struct AttachPtr *ap = mutt_mem_calloc(1, sizeof(struct AttachPtr));
1300       mutt_actx_add_attach(actx, ap);
1301
1302       ap->content = m;
1303       ap->fp = fp;
1304       m->aptr = ap;
1305       ap->parent_type = parent_type;
1306       ap->level = level;
1307       ap->decrypted = decrypted;
1308
1309       if (m->type == TYPE_MULTIPART)
1310         mutt_generate_recvattach_list(actx, e, m->parts, fp, m->type, level + 1, decrypted);
1311       else if (mutt_is_message_type(m->type, m->subtype))
1312       {
1313         mutt_generate_recvattach_list(actx, m->email, m->parts, fp, m->type,
1314                                       level + 1, decrypted);
1315         e->security |= m->email->security;
1316       }
1317     }
1318   }
1319 }
1320
1321 /**
1322  * mutt_attach_init - Create a new Attachment context
1323  * @param actx Attachment context
1324  */
1325 void mutt_attach_init(struct AttachCtx *actx)
1326 {
1327   /* Collapse the attachments if '$digest_collapse' is set AND if...
1328    * the outer container is of type 'multipart/digest' */
1329   bool digest = (mutt_str_strcasecmp(actx->email->content->subtype, "digest") == 0);
1330
1331   for (int i = 0; i < actx->idxlen; i++)
1332   {
1333     actx->idx[i]->content->tagged = false;
1334
1335     /* OR an inner container is of type 'multipart/digest' */
1336     actx->idx[i]->content->collapsed =
1337         (C_DigestCollapse &&
1338          (digest ||
1339           ((actx->idx[i]->content->type == TYPE_MULTIPART) &&
1340            (mutt_str_strcasecmp(actx->idx[i]->content->subtype, "digest") == 0))));
1341   }
1342 }
1343
1344 /**
1345  * mutt_update_recvattach_menu - Update the Attachment Menu
1346  * @param actx Attachment context
1347  * @param menu Menu listing Attachments
1348  * @param init If true, create a new Attachments context
1349  */
1350 static void mutt_update_recvattach_menu(struct AttachCtx *actx, struct Menu *menu, bool init)
1351 {
1352   if (init)
1353   {
1354     mutt_generate_recvattach_list(actx, actx->email, actx->email->content,
1355                                   actx->fp_root, -1, 0, 0);
1356     mutt_attach_init(actx);
1357     menu->data = actx;
1358   }
1359
1360   mutt_update_tree(actx);
1361
1362   menu->max = actx->vcount;
1363
1364   if (menu->current >= menu->max)
1365     menu->current = menu->max - 1;
1366   menu_check_recenter(menu);
1367   menu->redraw |= REDRAW_INDEX;
1368 }
1369
1370 /**
1371  * attach_collapse - Close the tree of the current attachment
1372  * @param actx Attachment context
1373  * @param menu Menu listing Attachments
1374  */
1375 static void attach_collapse(struct AttachCtx *actx, struct Menu *menu)
1376 {
1377   int rindex, curlevel;
1378
1379   CUR_ATTACH->content->collapsed = !CUR_ATTACH->content->collapsed;
1380   /* When expanding, expand all the children too */
1381   if (CUR_ATTACH->content->collapsed)
1382     return;
1383
1384   curlevel = CUR_ATTACH->level;
1385   rindex = actx->v2r[menu->current] + 1;
1386
1387   while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel))
1388   {
1389     if (C_DigestCollapse && (actx->idx[rindex]->content->type == TYPE_MULTIPART) &&
1390         !mutt_str_strcasecmp(actx->idx[rindex]->content->subtype, "digest"))
1391     {
1392       actx->idx[rindex]->content->collapsed = true;
1393     }
1394     else
1395     {
1396       actx->idx[rindex]->content->collapsed = false;
1397     }
1398     rindex++;
1399   }
1400 }
1401
1402 /**
1403  * mutt_view_attachments - Show the attachments in a Menu
1404  * @param e Email
1405  */
1406 void mutt_view_attachments(struct Email *e)
1407 {
1408   char helpstr[1024];
1409   int op = OP_NULL;
1410
1411   struct Mailbox *m = Context ? Context->mailbox : NULL;
1412
1413   /* make sure we have parsed this message */
1414   mutt_parse_mime_message(m, e);
1415
1416   mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
1417
1418   struct Message *msg = mx_msg_open(m, e->msgno);
1419   if (!msg)
1420     return;
1421
1422   struct Menu *menu = mutt_menu_new(MENU_ATTACH);
1423   menu->title = _("Attachments");
1424   menu->menu_make_entry = attach_make_entry;
1425   menu->menu_tag = attach_tag;
1426   menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_ATTACH, AttachHelp);
1427   mutt_menu_push_current(menu);
1428
1429   struct AttachCtx *actx = mutt_actx_new();
1430   actx->email = e;
1431   actx->fp_root = msg->fp;
1432   mutt_update_recvattach_menu(actx, menu, true);
1433
1434   while (true)
1435   {
1436     if (op == OP_NULL)
1437       op = mutt_menu_loop(menu);
1438     if (!Context)
1439       return;
1440     switch (op)
1441     {
1442       case OP_ATTACH_VIEW_MAILCAP:
1443         mutt_view_attachment(CUR_ATTACH->fp, CUR_ATTACH->content, MUTT_VA_MAILCAP, e, actx);
1444         menu->redraw = REDRAW_FULL;
1445         break;
1446
1447       case OP_ATTACH_VIEW_TEXT:
1448         mutt_view_attachment(CUR_ATTACH->fp, CUR_ATTACH->content, MUTT_VA_AS_TEXT, e, actx);
1449         menu->redraw = REDRAW_FULL;
1450         break;
1451
1452       case OP_DISPLAY_HEADERS:
1453       case OP_VIEW_ATTACH:
1454         op = mutt_attach_display_loop(menu, op, e, actx, true);
1455         menu->redraw = REDRAW_FULL;
1456         continue;
1457
1458       case OP_ATTACH_COLLAPSE:
1459         if (!CUR_ATTACH->content->parts)
1460         {
1461           mutt_error(_("There are no subparts to show"));
1462           break;
1463         }
1464         attach_collapse(actx, menu);
1465         mutt_update_recvattach_menu(actx, menu, false);
1466         break;
1467
1468       case OP_FORGET_PASSPHRASE:
1469         crypt_forget_passphrase();
1470         break;
1471
1472       case OP_EXTRACT_KEYS:
1473         if (WithCrypto & APPLICATION_PGP)
1474         {
1475           recvattach_extract_pgp_keys(actx, menu);
1476           menu->redraw = REDRAW_FULL;
1477         }
1478         break;
1479
1480       case OP_CHECK_TRADITIONAL:
1481         if (((WithCrypto & APPLICATION_PGP) != 0) &&
1482             recvattach_pgp_check_traditional(actx, menu))
1483         {
1484           e->security = crypt_query(NULL);
1485           menu->redraw = REDRAW_FULL;
1486         }
1487         break;
1488
1489       case OP_PRINT:
1490         mutt_print_attachment_list(actx, CUR_ATTACH->fp, menu->tagprefix,
1491                                    CUR_ATTACH->content);
1492         break;
1493
1494       case OP_PIPE:
1495         mutt_pipe_attachment_list(actx, CUR_ATTACH->fp, menu->tagprefix,
1496                                   CUR_ATTACH->content, false);
1497         break;
1498
1499       case OP_SAVE:
1500         mutt_save_attachment_list(actx, CUR_ATTACH->fp, menu->tagprefix,
1501                                   CUR_ATTACH->content, e, menu);
1502
1503         if (!menu->tagprefix && C_Resolve && (menu->current < menu->max - 1))
1504           menu->current++;
1505
1506         menu->redraw = REDRAW_MOTION_RESYNC | REDRAW_FULL;
1507         break;
1508
1509       case OP_DELETE:
1510         CHECK_READONLY;
1511
1512 #ifdef USE_POP
1513         if (m->magic == MUTT_POP)
1514         {
1515           mutt_flushinp();
1516           mutt_error(_("Can't delete attachment from POP server"));
1517           break;
1518         }
1519 #endif
1520
1521 #ifdef USE_NNTP
1522         if (m->magic == MUTT_NNTP)
1523         {
1524           mutt_flushinp();
1525           mutt_error(_("Can't delete attachment from news server"));
1526           break;
1527         }
1528 #endif
1529
1530         if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT))
1531         {
1532           mutt_message(_("Deletion of attachments from encrypted messages is "
1533                          "unsupported"));
1534           break;
1535         }
1536         if ((WithCrypto != 0) && (e->security & (SEC_SIGN | SEC_PARTSIGN)))
1537         {
1538           mutt_message(_("Deletion of attachments from signed messages may "
1539                          "invalidate the signature"));
1540         }
1541         if (!menu->tagprefix)
1542         {
1543           if (CUR_ATTACH->parent_type == TYPE_MULTIPART)
1544           {
1545             CUR_ATTACH->content->deleted = true;
1546             if (C_Resolve && (menu->current < menu->max - 1))
1547             {
1548               menu->current++;
1549               menu->redraw = REDRAW_MOTION_RESYNC;
1550             }
1551             else
1552               menu->redraw = REDRAW_CURRENT;
1553           }
1554           else
1555           {
1556             mutt_message(
1557                 _("Only deletion of multipart attachments is supported"));
1558           }
1559         }
1560         else
1561         {
1562           for (int i = 0; i < menu->max; i++)
1563           {
1564             if (actx->idx[i]->content->tagged)
1565             {
1566               if (actx->idx[i]->parent_type == TYPE_MULTIPART)
1567               {
1568                 actx->idx[i]->content->deleted = true;
1569                 menu->redraw = REDRAW_INDEX;
1570               }
1571               else
1572               {
1573                 mutt_message(
1574                     _("Only deletion of multipart attachments is supported"));
1575               }
1576             }
1577           }
1578         }
1579         break;
1580
1581       case OP_UNDELETE:
1582         CHECK_READONLY;
1583         if (!menu->tagprefix)
1584         {
1585           CUR_ATTACH->content->deleted = false;
1586           if (C_Resolve && (menu->current < menu->max - 1))
1587           {
1588             menu->current++;
1589             menu->redraw = REDRAW_MOTION_RESYNC;
1590           }
1591           else
1592             menu->redraw = REDRAW_CURRENT;
1593         }
1594         else
1595         {
1596           for (int i = 0; i < menu->max; i++)
1597           {
1598             if (actx->idx[i]->content->tagged)
1599             {
1600               actx->idx[i]->content->deleted = false;
1601               menu->redraw = REDRAW_INDEX;
1602             }
1603           }
1604         }
1605         break;
1606
1607       case OP_RESEND:
1608         CHECK_ATTACH;
1609         mutt_attach_resend(CUR_ATTACH->fp, actx,
1610                            menu->tagprefix ? NULL : CUR_ATTACH->content);
1611         menu->redraw = REDRAW_FULL;
1612         break;
1613
1614       case OP_BOUNCE_MESSAGE:
1615         CHECK_ATTACH;
1616         mutt_attach_bounce(m, CUR_ATTACH->fp, actx,
1617                            menu->tagprefix ? NULL : CUR_ATTACH->content);
1618         menu->redraw = REDRAW_FULL;
1619         break;
1620
1621       case OP_FORWARD_MESSAGE:
1622         CHECK_ATTACH;
1623         mutt_attach_forward(CUR_ATTACH->fp, e, actx,
1624                             menu->tagprefix ? NULL : CUR_ATTACH->content, SEND_NO_FLAGS);
1625         menu->redraw = REDRAW_FULL;
1626         break;
1627
1628 #ifdef USE_NNTP
1629       case OP_FORWARD_TO_GROUP:
1630         CHECK_ATTACH;
1631         mutt_attach_forward(CUR_ATTACH->fp, e, actx,
1632                             menu->tagprefix ? NULL : CUR_ATTACH->content, SEND_NEWS);
1633         menu->redraw = REDRAW_FULL;
1634         break;
1635
1636       case OP_FOLLOWUP:
1637         CHECK_ATTACH;
1638
1639         if (!CUR_ATTACH->content->email->env->followup_to ||
1640             (mutt_str_strcasecmp(CUR_ATTACH->content->email->env->followup_to, "poster") != 0) ||
1641             (query_quadoption(C_FollowupToPoster,
1642                               _("Reply by mail as poster prefers?")) != MUTT_YES))
1643         {
1644           mutt_attach_reply(CUR_ATTACH->fp, e, actx,
1645                             menu->tagprefix ? NULL : CUR_ATTACH->content,
1646                             SEND_NEWS | SEND_REPLY);
1647           menu->redraw = REDRAW_FULL;
1648           break;
1649         }
1650 #endif
1651       /* fallthrough */
1652       case OP_REPLY:
1653       case OP_GROUP_REPLY:
1654       case OP_GROUP_CHAT_REPLY:
1655       case OP_LIST_REPLY:
1656       {
1657         CHECK_ATTACH;
1658
1659         SendFlags flags = SEND_REPLY;
1660         if (op == OP_GROUP_REPLY)
1661           flags |= SEND_GROUP_REPLY;
1662         else if (op == OP_GROUP_CHAT_REPLY)
1663           flags |= SEND_GROUP_CHAT_REPLY;
1664         else if (op == OP_LIST_REPLY)
1665           flags |= SEND_LIST_REPLY;
1666
1667         mutt_attach_reply(CUR_ATTACH->fp, e, actx,
1668                           menu->tagprefix ? NULL : CUR_ATTACH->content, flags);
1669         menu->redraw = REDRAW_FULL;
1670         break;
1671       }
1672
1673       case OP_COMPOSE_TO_SENDER:
1674         CHECK_ATTACH;
1675         mutt_attach_mail_sender(CUR_ATTACH->fp, e, actx,
1676                                 menu->tagprefix ? NULL : CUR_ATTACH->content);
1677         menu->redraw = REDRAW_FULL;
1678         break;
1679
1680       case OP_EDIT_TYPE:
1681         recvattach_edit_content_type(actx, menu, e);
1682         menu->redraw |= REDRAW_INDEX;
1683         break;
1684
1685       case OP_EXIT:
1686         mx_msg_close(m, &msg);
1687
1688         e->attach_del = false;
1689         for (int i = 0; i < actx->idxlen; i++)
1690         {
1691           if (actx->idx[i]->content && actx->idx[i]->content->deleted)
1692           {
1693             e->attach_del = true;
1694             break;
1695           }
1696         }
1697         if (e->attach_del)
1698           e->changed = true;
1699
1700         mutt_actx_free(&actx);
1701
1702         mutt_menu_pop_current(menu);
1703         mutt_menu_free(&menu);
1704         return;
1705     }
1706
1707     op = OP_NULL;
1708   }
1709
1710   /* not reached */
1711 }