]> granicus.if.org Git - neomutt/blob - recvcmd.c
Fix mutt_write_mime_body() application/pgp-encrypted handling
[neomutt] / recvcmd.c
1 /**
2  * @file
3  * Send/reply with an attachment
4  *
5  * @authors
6  * Copyright (C) 1999-2004 Thomas Roessler <roessler@does-not-exist.org>
7  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
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 recvcmd Send/reply with an attachment
26  *
27  * Send/reply with an attachment
28  */
29
30 #include "config.h"
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include "mutt/mutt.h"
36 #include "address/lib.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
39 #include "mutt.h"
40 #include "alias.h"
41 #include "context.h"
42 #include "copy.h"
43 #include "curs_lib.h"
44 #include "globals.h"
45 #include "handler.h"
46 #include "hdrline.h"
47 #include "mutt_body.h"
48 #include "mutt_logging.h"
49 #include "mutt_window.h"
50 #include "muttlib.h"
51 #include "options.h"
52 #include "protos.h"
53 #include "send.h"
54 #include "sendlib.h"
55 #include "state.h"
56 #ifdef ENABLE_NLS
57 #include <libintl.h>
58 #endif
59
60 struct Mailbox;
61
62 /* These Config Variables are only used in recvcmd.c */
63 unsigned char C_MimeForwardRest; ///< Config: Forward all attachments, even if they can't be decoded
64
65 /**
66  * check_msg - Are we working with an RFC822 message
67  * @param b   Body of email
68  * @param err If true, display a message if this isn't an RFC822 message
69  * @retval true This is an RFC822 message
70  *
71  * some helper functions to verify that we are exclusively operating on
72  * message/rfc822 attachments
73  */
74 static bool check_msg(struct Body *b, bool err)
75 {
76   if (!mutt_is_message_type(b->type, b->subtype))
77   {
78     if (err)
79       mutt_error(_("You may only bounce message/rfc822 parts"));
80     return false;
81   }
82   return true;
83 }
84
85 /**
86  * check_all_msg - Are all the Attachments RFC822 messages?
87  * @param actx Attachment context
88  * @param cur  Current message
89  * @param err  If true, report errors
90  * @retval true If all parts are RFC822 messages
91  */
92 static bool check_all_msg(struct AttachCtx *actx, struct Body *cur, bool err)
93 {
94   if (cur && !check_msg(cur, err))
95     return false;
96   else if (!cur)
97   {
98     for (short i = 0; i < actx->idxlen; i++)
99     {
100       if (actx->idx[i]->content->tagged)
101       {
102         if (!check_msg(actx->idx[i]->content, err))
103           return false;
104       }
105     }
106   }
107   return true;
108 }
109
110 /**
111  * check_can_decode - Can we decode all tagged attachments?
112  * @param actx Attachment context
113  * @param cur  Body of email
114  * @retval true All tagged attachments are decodable
115  */
116 static bool check_can_decode(struct AttachCtx *actx, struct Body *cur)
117 {
118   if (cur)
119     return mutt_can_decode(cur);
120
121   for (short i = 0; i < actx->idxlen; i++)
122     if (actx->idx[i]->content->tagged && !mutt_can_decode(actx->idx[i]->content))
123       return false;
124
125   return true;
126 }
127
128 /**
129  * count_tagged - Count the number of tagged attachments
130  * @param actx Attachment context
131  * @retval num Number of tagged attachments
132  */
133 static short count_tagged(struct AttachCtx *actx)
134 {
135   short count = 0;
136   for (short i = 0; i < actx->idxlen; i++)
137     if (actx->idx[i]->content->tagged)
138       count++;
139
140   return count;
141 }
142
143 /**
144  * count_tagged_children - tagged children below a multipart/message attachment
145  * @param actx Attachment context
146  * @param i    Index of first attachment
147  * @retval num Number of tagged attachments
148  */
149 static short count_tagged_children(struct AttachCtx *actx, short i)
150 {
151   short level = actx->idx[i]->level;
152   short count = 0;
153
154   while ((++i < actx->idxlen) && (level < actx->idx[i]->level))
155     if (actx->idx[i]->content->tagged)
156       count++;
157
158   return count;
159 }
160
161 /**
162  * mutt_attach_bounce - Bounce function, from the attachment menu
163  * @param m    Mailbox
164  * @param fp   Handle of message
165  * @param actx Attachment context
166  * @param cur  Body of email
167  */
168 void mutt_attach_bounce(struct Mailbox *m, FILE *fp, struct AttachCtx *actx, struct Body *cur)
169 {
170   if (!m || !fp || !actx)
171     return;
172
173   char prompt[256];
174   char buf[8192];
175   char *err = NULL;
176   int ret = 0;
177   int p = 0;
178
179   if (!check_all_msg(actx, cur, true))
180     return;
181
182   /* one or more messages? */
183   p = cur ? 1 : count_tagged(actx);
184
185   /* RFC5322 mandates a From: header, so warn before bouncing
186    * messages without one */
187   if (cur)
188   {
189     if (TAILQ_EMPTY(&cur->email->env->from))
190     {
191       mutt_error(_("Warning: message contains no From: header"));
192       mutt_clear_error();
193     }
194   }
195   else
196   {
197     for (short i = 0; i < actx->idxlen; i++)
198     {
199       if (actx->idx[i]->content->tagged)
200       {
201         if (TAILQ_EMPTY(&actx->idx[i]->content->email->env->from))
202         {
203           mutt_error(_("Warning: message contains no From: header"));
204           mutt_clear_error();
205           break;
206         }
207       }
208     }
209   }
210
211   if (p)
212     mutt_str_strfcpy(prompt, _("Bounce message to: "), sizeof(prompt));
213   else
214     mutt_str_strfcpy(prompt, _("Bounce tagged messages to: "), sizeof(prompt));
215
216   buf[0] = '\0';
217   if (mutt_get_field(prompt, buf, sizeof(buf), MUTT_ALIAS) || (buf[0] == '\0'))
218     return;
219
220   struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
221   mutt_addrlist_parse(&al, buf);
222   if (TAILQ_EMPTY(&al))
223   {
224     mutt_error(_("Error parsing address"));
225     return;
226   }
227
228   mutt_expand_aliases(&al);
229
230   if (mutt_addrlist_to_intl(&al, &err) < 0)
231   {
232     mutt_error(_("Bad IDN: '%s'"), err);
233     FREE(&err);
234     goto end;
235   }
236
237   buf[0] = '\0';
238   mutt_addrlist_write(buf, sizeof(buf), &al, true);
239
240 #define EXTRA_SPACE (15 + 7 + 2)
241   /* See commands.c.  */
242   snprintf(prompt, sizeof(prompt) - 4,
243            ngettext("Bounce message to %s?", "Bounce messages to %s?", p), buf);
244
245   if (mutt_strwidth(prompt) > MuttMessageWindow->cols - EXTRA_SPACE)
246   {
247     mutt_simple_format(prompt, sizeof(prompt) - 4, 0, MuttMessageWindow->cols - EXTRA_SPACE,
248                        JUSTIFY_LEFT, 0, prompt, sizeof(prompt), false);
249     mutt_str_strcat(prompt, sizeof(prompt), "...?");
250   }
251   else
252     mutt_str_strcat(prompt, sizeof(prompt), "?");
253
254   if (query_quadoption(C_Bounce, prompt) != MUTT_YES)
255   {
256     mutt_window_clearline(MuttMessageWindow, 0);
257     mutt_message(ngettext("Message not bounced", "Messages not bounced", p));
258     goto end;
259   }
260
261   mutt_window_clearline(MuttMessageWindow, 0);
262
263   if (cur)
264     ret = mutt_bounce_message(fp, cur->email, &al);
265   else
266   {
267     for (short i = 0; i < actx->idxlen; i++)
268     {
269       if (actx->idx[i]->content->tagged)
270         if (mutt_bounce_message(actx->idx[i]->fp, actx->idx[i]->content->email, &al))
271           ret = 1;
272     }
273   }
274
275   if (ret == 0)
276     mutt_message(ngettext("Message bounced", "Messages bounced", p));
277   else
278     mutt_error(ngettext("Error bouncing message", "Error bouncing messages", p));
279
280 end:
281   mutt_addrlist_clear(&al);
282 }
283
284 /**
285  * mutt_attach_resend - resend-message, from the attachment menu
286  * @param fp   File containing email
287  * @param actx Attachment context
288  * @param cur  Attachment
289  */
290 void mutt_attach_resend(FILE *fp, struct AttachCtx *actx, struct Body *cur)
291 {
292   if (!check_all_msg(actx, cur, true))
293     return;
294
295   if (cur)
296     mutt_resend_message(fp, Context, cur->email);
297   else
298   {
299     for (short i = 0; i < actx->idxlen; i++)
300       if (actx->idx[i]->content->tagged)
301         mutt_resend_message(actx->idx[i]->fp, Context, actx->idx[i]->content->email);
302   }
303 }
304
305 /**
306  * find_common_parent - find a common parent message for the tagged attachments
307  * @param actx    Attachment context
308  * @param nattach Number of tagged attachments
309  * @retval ptr Parent attachment
310  * @retval NULL Failure, no common parent
311  */
312 static struct AttachPtr *find_common_parent(struct AttachCtx *actx, short nattach)
313 {
314   short i;
315   short nchildren;
316
317   for (i = 0; i < actx->idxlen; i++)
318     if (actx->idx[i]->content->tagged)
319       break;
320
321   while (--i >= 0)
322   {
323     if (mutt_is_message_type(actx->idx[i]->content->type, actx->idx[i]->content->subtype))
324     {
325       nchildren = count_tagged_children(actx, i);
326       if (nchildren == nattach)
327         return actx->idx[i];
328     }
329   }
330
331   return NULL;
332 }
333
334 /**
335  * is_parent - Check whether one attachment is the parent of another
336  * @param i    Index of parent Attachment
337  * @param actx Attachment context
338  * @param cur  Potential child Attachemnt
339  * @retval true Attachment
340  *
341  * check whether attachment i is a parent of the attachment pointed to by cur
342  *
343  * Note: This and the calling procedure could be optimized quite a bit.
344  * For now, it's not worth the effort.
345  */
346 static int is_parent(short i, struct AttachCtx *actx, struct Body *cur)
347 {
348   short level = actx->idx[i]->level;
349
350   while ((++i < actx->idxlen) && (actx->idx[i]->level > level))
351   {
352     if (actx->idx[i]->content == cur)
353       return true;
354   }
355
356   return false;
357 }
358
359 /**
360  * find_parent - Find the parent of an Attachment
361  * @param actx    Attachment context
362  * @param cur     Attachment (OPTIONAL)
363  * @param nattach Use the nth attachment
364  * @retval ptr  Parent attachment
365  * @retval NULL No parent exists
366  */
367 static struct AttachPtr *find_parent(struct AttachCtx *actx, struct Body *cur, short nattach)
368 {
369   struct AttachPtr *parent = NULL;
370
371   if (cur)
372   {
373     for (short i = 0; i < actx->idxlen; i++)
374     {
375       if (mutt_is_message_type(actx->idx[i]->content->type,
376                                actx->idx[i]->content->subtype) &&
377           is_parent(i, actx, cur))
378       {
379         parent = actx->idx[i];
380       }
381       if (actx->idx[i]->content == cur)
382         break;
383     }
384   }
385   else if (nattach)
386     parent = find_common_parent(actx, nattach);
387
388   return parent;
389 }
390
391 /**
392  * include_header - Write an email header to a file, optionally quoting it
393  * @param quote  If true, prefix the lines
394  * @param fp_in  File to read from
395  * @param e      Email
396  * @param fp_out File to write to
397  * @param prefix Prefix for each line (OPTIONAL)
398  */
399 static void include_header(bool quote, FILE *fp_in, struct Email *e, FILE *fp_out, char *prefix)
400 {
401   CopyHeaderFlags chflags = CH_DECODE;
402   char prefix2[128];
403
404   if (C_Weed)
405     chflags |= CH_WEED | CH_REORDER;
406
407   if (quote)
408   {
409     if (prefix)
410       mutt_str_strfcpy(prefix2, prefix, sizeof(prefix2));
411     else if (!C_TextFlowed)
412     {
413       mutt_make_string(prefix2, sizeof(prefix2), NONULL(C_IndentString),
414                        Context, Context->mailbox, e);
415     }
416     else
417       mutt_str_strfcpy(prefix2, ">", sizeof(prefix2));
418
419     chflags |= CH_PREFIX;
420   }
421
422   mutt_copy_header(fp_in, e, fp_out, chflags, quote ? prefix2 : NULL);
423 }
424
425 /**
426  * copy_problematic_attachments - Attach the body parts which can't be decoded
427  * @param[out] last  Body pointer to update
428  * @param[in]  actx  Attachment context
429  * @param[in]  force If true, attach parts that can't be decoded
430  * @retval ptr Pointer to last Body part
431  *
432  * This code is shared by forwarding and replying.
433  */
434 static struct Body **copy_problematic_attachments(struct Body **last,
435                                                   struct AttachCtx *actx, bool force)
436 {
437   for (short i = 0; i < actx->idxlen; i++)
438   {
439     if (actx->idx[i]->content->tagged && (force || !mutt_can_decode(actx->idx[i]->content)))
440     {
441       if (mutt_body_copy(actx->idx[i]->fp, last, actx->idx[i]->content) == -1)
442         return NULL; /* XXXXX - may lead to crashes */
443       last = &((*last)->next);
444     }
445   }
446   return last;
447 }
448
449 /**
450  * attach_forward_bodies - forward one or several MIME bodies
451  * @param fp      File to read from
452  * @param e     Email
453  * @param actx    Attachment Context
454  * @param cur     Body of email
455  * @param nattach Number of tagged attachments
456  *
457  * (non-message types)
458  */
459 static void attach_forward_bodies(FILE *fp, struct Email *e, struct AttachCtx *actx,
460                                   struct Body *cur, short nattach)
461 {
462   bool mime_fwd_all = false;
463   bool mime_fwd_any = true;
464   struct Email *e_parent = NULL;
465   FILE *fp_parent = NULL;
466   char tmpbody[PATH_MAX];
467   char prefix[256];
468   enum QuadOption ans = MUTT_ABORT;
469
470   /* First, find the parent message.
471    * Note: This could be made an option by just
472    * putting the following lines into an if block.  */
473   struct AttachPtr *parent = find_parent(actx, cur, nattach);
474   if (parent)
475   {
476     e_parent = parent->content->email;
477     fp_parent = parent->fp;
478   }
479   else
480   {
481     e_parent = e;
482     fp_parent = actx->fp_root;
483   }
484
485   struct Email *e_tmp = email_new();
486   e_tmp->env = mutt_env_new();
487   mutt_make_forward_subject(e_tmp->env, Context->mailbox, e_parent);
488
489   mutt_mktemp(tmpbody, sizeof(tmpbody));
490   FILE *fp_tmp = mutt_file_fopen(tmpbody, "w");
491   if (!fp_tmp)
492   {
493     mutt_error(_("Can't open temporary file %s"), tmpbody);
494     email_free(&e_tmp);
495     return;
496   }
497
498   mutt_forward_intro(Context->mailbox, e_parent, fp_tmp);
499
500   /* prepare the prefix here since we'll need it later. */
501
502   if (C_ForwardQuote)
503   {
504     if (C_TextFlowed)
505       mutt_str_strfcpy(prefix, ">", sizeof(prefix));
506     else
507     {
508       mutt_make_string(prefix, sizeof(prefix), NONULL(C_IndentString), Context,
509                        Context->mailbox, e_parent);
510     }
511   }
512
513   include_header(C_ForwardQuote, fp_parent, e_parent, fp_tmp, prefix);
514
515   /* Now, we have prepared the first part of the message body: The
516    * original message's header.
517    *
518    * The next part is more interesting: either include the message bodies,
519    * or attach them.  */
520   if ((!cur || mutt_can_decode(cur)) &&
521       ((ans = query_quadoption(C_MimeForward, _("Forward as attachments?"))) == MUTT_YES))
522   {
523     mime_fwd_all = true;
524   }
525   else if (ans == MUTT_ABORT)
526   {
527     goto bail;
528   }
529
530   /* shortcut MIMEFWDREST when there is only one attachment.
531    * Is this intuitive?  */
532   if (!mime_fwd_all && !cur && (nattach > 1) && !check_can_decode(actx, cur))
533   {
534     ans = query_quadoption(
535         C_MimeForwardRest,
536         _("Can't decode all tagged attachments.  MIME-forward the others?"));
537     if (ans == MUTT_ABORT)
538       goto bail;
539     else if (ans == MUTT_NO)
540       mime_fwd_any = false;
541   }
542
543   /* initialize a state structure */
544
545   struct State st = { 0 };
546   if (C_ForwardQuote)
547     st.prefix = prefix;
548   st.flags = MUTT_CHARCONV;
549   if (C_Weed)
550     st.flags |= MUTT_WEED;
551   st.fp_out = fp_tmp;
552
553   /* where do we append new MIME parts? */
554   struct Body **last = &e_tmp->content;
555
556   if (cur)
557   {
558     /* single body case */
559
560     if (!mime_fwd_all && mutt_can_decode(cur))
561     {
562       st.fp_in = fp;
563       mutt_body_handler(cur, &st);
564       state_putc('\n', &st);
565     }
566     else
567     {
568       if (mutt_body_copy(fp, last, cur) == -1)
569         goto bail;
570     }
571   }
572   else
573   {
574     /* multiple body case */
575
576     if (!mime_fwd_all)
577     {
578       for (int i = 0; i < actx->idxlen; i++)
579       {
580         if (actx->idx[i]->content->tagged && mutt_can_decode(actx->idx[i]->content))
581         {
582           st.fp_in = actx->idx[i]->fp;
583           mutt_body_handler(actx->idx[i]->content, &st);
584           state_putc('\n', &st);
585         }
586       }
587     }
588
589     if (mime_fwd_any && !copy_problematic_attachments(last, actx, mime_fwd_all))
590       goto bail;
591   }
592
593   mutt_forward_trailer(Context->mailbox, e_parent, fp_tmp);
594
595   mutt_file_fclose(&fp_tmp);
596   fp_tmp = NULL;
597
598   /* now that we have the template, send it. */
599   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
600   el_add_email(&el, e_parent);
601   ci_send_message(SEND_NO_FLAGS, e_tmp, tmpbody, NULL, &el);
602   emaillist_clear(&el);
603   return;
604
605 bail:
606
607   if (fp_tmp)
608   {
609     mutt_file_fclose(&fp_tmp);
610     mutt_file_unlink(tmpbody);
611   }
612
613   email_free(&e_tmp);
614 }
615
616 /**
617  * attach_forward_msgs - Forward one or several message-type attachments
618  * @param fp    File handle to attachment
619  * @param actx  Attachment Context
620  * @param cur   Attachment to forward (OPTIONAL)
621  * @param flags Send mode, see #SendFlags
622  *
623  * This is different from the previous function since we want to mimic the
624  * index menu's behavior.
625  *
626  * Code reuse from ci_send_message() is not possible here - it relies on a
627  * context structure to find messages, while, on the attachment menu, messages
628  * are referenced through the attachment index.
629  */
630 static void attach_forward_msgs(FILE *fp, struct AttachCtx *actx,
631                                 struct Body *cur, SendFlags flags)
632 {
633   struct Email *e_cur = NULL;
634   struct Email *e_tmp = NULL;
635   enum QuadOption ans;
636
637   struct Body **last = NULL;
638   char tmpbody[PATH_MAX];
639   FILE *fp_tmp = NULL;
640
641   CopyHeaderFlags chflags = CH_XMIT;
642
643   if (cur)
644     e_cur = cur->email;
645   else
646   {
647     for (short i = 0; i < actx->idxlen; i++)
648     {
649       if (actx->idx[i]->content->tagged)
650       {
651         e_cur = actx->idx[i]->content->email;
652         break;
653       }
654     }
655   }
656
657   e_tmp = email_new();
658   e_tmp->env = mutt_env_new();
659   mutt_make_forward_subject(e_tmp->env, Context->mailbox, e_cur);
660
661   tmpbody[0] = '\0';
662
663   ans = query_quadoption(C_MimeForward, _("Forward MIME encapsulated?"));
664   if (ans == MUTT_NO)
665   {
666     /* no MIME encapsulation */
667
668     mutt_mktemp(tmpbody, sizeof(tmpbody));
669     fp_tmp = mutt_file_fopen(tmpbody, "w");
670     if (!fp_tmp)
671     {
672       mutt_error(_("Can't create %s"), tmpbody);
673       email_free(&e_tmp);
674       return;
675     }
676
677     CopyMessageFlags cmflags = MUTT_CM_NO_FLAGS;
678     if (C_ForwardQuote)
679     {
680       chflags |= CH_PREFIX;
681       cmflags |= MUTT_CM_PREFIX;
682     }
683
684     if (C_ForwardDecode)
685     {
686       cmflags |= MUTT_CM_DECODE | MUTT_CM_CHARCONV;
687       if (C_Weed)
688       {
689         chflags |= CH_WEED | CH_REORDER;
690         cmflags |= MUTT_CM_WEED;
691       }
692     }
693
694     if (cur)
695     {
696       mutt_forward_intro(Context->mailbox, cur->email, fp_tmp);
697       mutt_copy_message_fp(fp_tmp, fp, cur->email, cmflags, chflags);
698       mutt_forward_trailer(Context->mailbox, cur->email, fp_tmp);
699     }
700     else
701     {
702       for (short i = 0; i < actx->idxlen; i++)
703       {
704         if (actx->idx[i]->content->tagged)
705         {
706           mutt_forward_intro(Context->mailbox, actx->idx[i]->content->email, fp_tmp);
707           mutt_copy_message_fp(fp_tmp, actx->idx[i]->fp,
708                                actx->idx[i]->content->email, cmflags, chflags);
709           mutt_forward_trailer(Context->mailbox, actx->idx[i]->content->email, fp_tmp);
710         }
711       }
712     }
713     mutt_file_fclose(&fp_tmp);
714   }
715   else if (ans == MUTT_YES) /* do MIME encapsulation - we don't need to do much here */
716   {
717     last = &e_tmp->content;
718     if (cur)
719       mutt_body_copy(fp, last, cur);
720     else
721     {
722       for (short i = 0; i < actx->idxlen; i++)
723       {
724         if (actx->idx[i]->content->tagged)
725         {
726           mutt_body_copy(actx->idx[i]->fp, last, actx->idx[i]->content);
727           last = &((*last)->next);
728         }
729       }
730     }
731   }
732   else
733     email_free(&e_tmp);
734
735   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
736   el_add_email(&el, e_cur);
737   ci_send_message(flags, e_tmp, (tmpbody[0] != '\0') ? tmpbody : NULL, NULL, &el);
738   emaillist_clear(&el);
739 }
740
741 /**
742  * mutt_attach_forward - Forward an Attachment
743  * @param fp    Handle to the attachment
744  * @param e     Email
745  * @param actx  Attachment Context
746  * @param cur   Current message
747  * @param flags Send mode, see #SendFlags
748  */
749 void mutt_attach_forward(FILE *fp, struct Email *e, struct AttachCtx *actx,
750                          struct Body *cur, SendFlags flags)
751 {
752   if (check_all_msg(actx, cur, false))
753     attach_forward_msgs(fp, actx, cur, flags);
754   else
755   {
756     const short nattach = count_tagged(actx);
757     attach_forward_bodies(fp, e, actx, cur, nattach);
758   }
759 }
760
761 /**
762  * attach_reply_envelope_defaults - Create the envelope defaults for a reply
763  * @param env    Envelope to fill in
764  * @param actx   Attachment Context
765  * @param parent Parent Email
766  * @param flags  Flags, see #SendFlags
767  * @retval  0 Success
768  * @retval -1 Error
769  *
770  * This function can be invoked in two ways.
771  *
772  * Either, parent is NULL.  In this case, all tagged bodies are of a message type,
773  * and the header information is fetched from them.
774  *
775  * Or, parent is non-NULL.  In this case, cur is the common parent of all the
776  * tagged attachments.
777  *
778  * Note that this code is horribly similar to envelope_defaults() from send.c.
779  */
780 static int attach_reply_envelope_defaults(struct Envelope *env, struct AttachCtx *actx,
781                                           struct Email *parent, SendFlags flags)
782 {
783   struct Envelope *curenv = NULL;
784   struct Email *e = NULL;
785
786   if (!parent)
787   {
788     for (short i = 0; i < actx->idxlen; i++)
789     {
790       if (actx->idx[i]->content->tagged)
791       {
792         e = actx->idx[i]->content->email;
793         curenv = e->env;
794         break;
795       }
796     }
797   }
798   else
799   {
800     curenv = parent->env;
801     e = parent;
802   }
803
804   if (!curenv || !e)
805   {
806     mutt_error(_("Can't find any tagged messages"));
807     return -1;
808   }
809
810 #ifdef USE_NNTP
811   if ((flags & SEND_NEWS))
812   {
813     /* in case followup set Newsgroups: with Followup-To: if it present */
814     if (!env->newsgroups && curenv &&
815         (mutt_str_strcasecmp(curenv->followup_to, "poster") != 0))
816     {
817       env->newsgroups = mutt_str_strdup(curenv->followup_to);
818     }
819   }
820   else
821 #endif
822   {
823     if (parent)
824     {
825       if (mutt_fetch_recips(env, curenv, flags) == -1)
826         return -1;
827     }
828     else
829     {
830       for (short i = 0; i < actx->idxlen; i++)
831       {
832         if (actx->idx[i]->content->tagged &&
833             (mutt_fetch_recips(env, actx->idx[i]->content->email->env, flags) == -1))
834         {
835           return -1;
836         }
837       }
838     }
839
840     if ((flags & SEND_LIST_REPLY) && TAILQ_EMPTY(&env->to))
841     {
842       mutt_error(_("No mailing lists found"));
843       return -1;
844     }
845
846     mutt_fix_reply_recipients(env);
847   }
848   mutt_make_misc_reply_headers(env, curenv);
849
850   if (parent)
851     mutt_add_to_reference_headers(env, curenv);
852   else
853   {
854     for (short i = 0; i < actx->idxlen; i++)
855     {
856       if (actx->idx[i]->content->tagged)
857         mutt_add_to_reference_headers(env, actx->idx[i]->content->email->env);
858     }
859   }
860
861   return 0;
862 }
863
864 /**
865  * attach_include_reply - This is _very_ similar to send.c's include_reply()
866  * @param fp     File handle to attachment
867  * @param fp_tmp File handle to temporary file
868  * @param e   Email
869  */
870 static void attach_include_reply(FILE *fp, FILE *fp_tmp, struct Email *e)
871 {
872   CopyMessageFlags cmflags = MUTT_CM_PREFIX | MUTT_CM_DECODE | MUTT_CM_CHARCONV;
873   CopyHeaderFlags chflags = CH_DECODE;
874
875   mutt_make_attribution(Context->mailbox, e, fp_tmp);
876
877   if (!C_Header)
878     cmflags |= MUTT_CM_NOHEADER;
879   if (C_Weed)
880   {
881     chflags |= CH_WEED;
882     cmflags |= MUTT_CM_WEED;
883   }
884
885   mutt_copy_message_fp(fp_tmp, fp, e, cmflags, chflags);
886   mutt_make_post_indent(Context->mailbox, e, fp_tmp);
887 }
888
889 /**
890  * mutt_attach_reply - Attach a reply
891  * @param fp    File handle to reply
892  * @param e     Email
893  * @param actx  Attachment Context
894  * @param e_cur   Current message
895  * @param flags Send mode, see #SendFlags
896  */
897 void mutt_attach_reply(FILE *fp, struct Email *e, struct AttachCtx *actx,
898                        struct Body *e_cur, SendFlags flags)
899 {
900   bool mime_reply_any = false;
901
902   short nattach = 0;
903   struct AttachPtr *parent = NULL;
904   struct Email *e_parent = NULL;
905   FILE *fp_parent = NULL;
906   struct Email *e_tmp = NULL;
907
908   struct State st;
909   char tmpbody[PATH_MAX];
910   FILE *fp_tmp = NULL;
911
912   char prefix[128];
913
914 #ifdef USE_NNTP
915   if (flags & SEND_NEWS)
916     OptNewsSend = true;
917   else
918     OptNewsSend = false;
919 #endif
920
921   if (!check_all_msg(actx, e_cur, false))
922   {
923     nattach = count_tagged(actx);
924     parent = find_parent(actx, e_cur, nattach);
925     if (parent)
926     {
927       e_parent = parent->content->email;
928       fp_parent = parent->fp;
929     }
930     else
931     {
932       e_parent = e;
933       fp_parent = actx->fp_root;
934     }
935   }
936
937   if ((nattach > 1) && !check_can_decode(actx, e_cur))
938   {
939     const enum QuadOption ans = query_quadoption(
940         C_MimeForwardRest, _("Can't decode all tagged attachments.  "
941                              "MIME-encapsulate the others?"));
942     if (ans == MUTT_ABORT)
943       return;
944     else if (ans == MUTT_YES)
945       mime_reply_any = true;
946   }
947   else if (nattach == 1)
948     mime_reply_any = true;
949
950   e_tmp = email_new();
951   e_tmp->env = mutt_env_new();
952
953   if (attach_reply_envelope_defaults(
954           e_tmp->env, actx, e_parent ? e_parent : (e_cur ? e_cur->email : NULL), flags) == -1)
955   {
956     email_free(&e_tmp);
957     return;
958   }
959
960   mutt_mktemp(tmpbody, sizeof(tmpbody));
961   fp_tmp = mutt_file_fopen(tmpbody, "w");
962   if (!fp_tmp)
963   {
964     mutt_error(_("Can't create %s"), tmpbody);
965     email_free(&e_tmp);
966     return;
967   }
968
969   if (!e_parent)
970   {
971     if (e_cur)
972       attach_include_reply(fp, fp_tmp, e_cur->email);
973     else
974     {
975       for (short i = 0; i < actx->idxlen; i++)
976       {
977         if (actx->idx[i]->content->tagged)
978           attach_include_reply(actx->idx[i]->fp, fp_tmp, actx->idx[i]->content->email);
979       }
980     }
981   }
982   else
983   {
984     mutt_make_attribution(Context->mailbox, e_parent, fp_tmp);
985
986     memset(&st, 0, sizeof(struct State));
987     st.fp_out = fp_tmp;
988
989     if (!C_TextFlowed)
990     {
991       mutt_make_string(prefix, sizeof(prefix), NONULL(C_IndentString), Context,
992                        Context->mailbox, e_parent);
993     }
994     else
995       mutt_str_strfcpy(prefix, ">", sizeof(prefix));
996
997     st.prefix = prefix;
998     st.flags = MUTT_CHARCONV;
999
1000     if (C_Weed)
1001       st.flags |= MUTT_WEED;
1002
1003     if (C_Header)
1004       include_header(true, fp_parent, e_parent, fp_tmp, prefix);
1005
1006     if (e_cur)
1007     {
1008       if (mutt_can_decode(e_cur))
1009       {
1010         st.fp_in = fp;
1011         mutt_body_handler(e_cur, &st);
1012         state_putc('\n', &st);
1013       }
1014       else
1015         mutt_body_copy(fp, &e_tmp->content, e_cur);
1016     }
1017     else
1018     {
1019       for (short i = 0; i < actx->idxlen; i++)
1020       {
1021         if (actx->idx[i]->content->tagged && mutt_can_decode(actx->idx[i]->content))
1022         {
1023           st.fp_in = actx->idx[i]->fp;
1024           mutt_body_handler(actx->idx[i]->content, &st);
1025           state_putc('\n', &st);
1026         }
1027       }
1028     }
1029
1030     mutt_make_post_indent(Context->mailbox, e_parent, fp_tmp);
1031
1032     if (mime_reply_any && !e_cur &&
1033         !copy_problematic_attachments(&e_tmp->content, actx, false))
1034     {
1035       email_free(&e_tmp);
1036       mutt_file_fclose(&fp_tmp);
1037       return;
1038     }
1039   }
1040
1041   mutt_file_fclose(&fp_tmp);
1042
1043   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1044   el_add_email(&el, e_parent ? e_parent : (e_cur ? e_cur->email : NULL));
1045   if (ci_send_message(flags, e_tmp, tmpbody, NULL, &el) == 0)
1046   {
1047     mutt_set_flag(Context->mailbox, e, MUTT_REPLIED, true);
1048   }
1049   emaillist_clear(&el);
1050 }
1051
1052 /**
1053  * mutt_attach_mail_sender - Compose an email to the sender in the email attachment
1054  * @param fp   File containing attachment (UNUSED)
1055  * @param e    Email (UNUSED)
1056  * @param actx Attachment Context
1057  * @param cur  Current attachment
1058  */
1059 void mutt_attach_mail_sender(FILE *fp, struct Email *e, struct AttachCtx *actx,
1060                              struct Body *cur)
1061 {
1062   if (!check_all_msg(actx, cur, 0))
1063   {
1064     /* L10N: You will see this error message if you invoke <compose-to-sender>
1065        when you are on a normal attachment.  */
1066     mutt_error(_("You may only compose to sender with message/rfc822 parts"));
1067     return;
1068   }
1069
1070   struct Email *e_tmp = email_new();
1071   e_tmp->env = mutt_env_new();
1072
1073   if (cur)
1074   {
1075     if (mutt_fetch_recips(e_tmp->env, cur->email->env, SEND_TO_SENDER) == -1)
1076     {
1077       email_free(&e_tmp);
1078       return;
1079     }
1080   }
1081   else
1082   {
1083     for (int i = 0; i < actx->idxlen; i++)
1084     {
1085       if (actx->idx[i]->content->tagged &&
1086           (mutt_fetch_recips(e_tmp->env, actx->idx[i]->content->email->env,
1087                              SEND_TO_SENDER) == -1))
1088       {
1089         email_free(&e_tmp);
1090         return;
1091       }
1092     }
1093   }
1094
1095   // This call will free e_tmp for us
1096   ci_send_message(SEND_NO_FLAGS, e_tmp, NULL, NULL, NULL);
1097 }