]> granicus.if.org Git - neomutt/blob - recvcmd.c
merge: light refactoring
[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   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), 0, 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, 0);
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 prefix[256];
467   enum QuadOption ans = MUTT_ABORT;
468   struct Buffer *tmpbody = NULL;
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   tmpbody = mutt_buffer_pool_get();
490   mutt_buffer_mktemp(tmpbody);
491   FILE *fp_tmp = mutt_file_fopen(mutt_b2s(tmpbody), "w");
492   if (!fp_tmp)
493   {
494     mutt_error(_("Can't open temporary file %s"), mutt_b2s(tmpbody));
495     email_free(&e_tmp);
496     goto bail;
497   }
498
499   mutt_forward_intro(Context->mailbox, e_parent, fp_tmp);
500
501   /* prepare the prefix here since we'll need it later. */
502
503   if (C_ForwardQuote)
504   {
505     if (C_TextFlowed)
506       mutt_str_strfcpy(prefix, ">", sizeof(prefix));
507     else
508     {
509       mutt_make_string(prefix, sizeof(prefix), 0, NONULL(C_IndentString),
510                        Context, Context->mailbox, e_parent);
511     }
512   }
513
514   include_header(C_ForwardQuote, fp_parent, e_parent, fp_tmp, prefix);
515
516   /* Now, we have prepared the first part of the message body: The
517    * original message's header.
518    *
519    * The next part is more interesting: either include the message bodies,
520    * or attach them.  */
521   if ((!cur || mutt_can_decode(cur)) &&
522       ((ans = query_quadoption(C_MimeForward, _("Forward as attachments?"))) == MUTT_YES))
523   {
524     mime_fwd_all = true;
525   }
526   else if (ans == MUTT_ABORT)
527   {
528     goto bail;
529   }
530
531   /* shortcut MIMEFWDREST when there is only one attachment.
532    * Is this intuitive?  */
533   if (!mime_fwd_all && !cur && (nattach > 1) && !check_can_decode(actx, cur))
534   {
535     ans = query_quadoption(
536         C_MimeForwardRest,
537         _("Can't decode all tagged attachments.  MIME-forward the others?"));
538     if (ans == MUTT_ABORT)
539       goto bail;
540     else if (ans == MUTT_NO)
541       mime_fwd_any = false;
542   }
543
544   /* initialize a state structure */
545
546   struct State st = { 0 };
547   if (C_ForwardQuote)
548     st.prefix = prefix;
549   st.flags = MUTT_CHARCONV;
550   if (C_Weed)
551     st.flags |= MUTT_WEED;
552   st.fp_out = fp_tmp;
553
554   /* where do we append new MIME parts? */
555   struct Body **last = &e_tmp->content;
556
557   if (cur)
558   {
559     /* single body case */
560
561     if (!mime_fwd_all && mutt_can_decode(cur))
562     {
563       st.fp_in = fp;
564       mutt_body_handler(cur, &st);
565       state_putc(&st, '\n');
566     }
567     else
568     {
569       if (mutt_body_copy(fp, last, cur) == -1)
570         goto bail;
571     }
572   }
573   else
574   {
575     /* multiple body case */
576
577     if (!mime_fwd_all)
578     {
579       for (int i = 0; i < actx->idxlen; i++)
580       {
581         if (actx->idx[i]->content->tagged && mutt_can_decode(actx->idx[i]->content))
582         {
583           st.fp_in = actx->idx[i]->fp;
584           mutt_body_handler(actx->idx[i]->content, &st);
585           state_putc(&st, '\n');
586         }
587       }
588     }
589
590     if (mime_fwd_any && !copy_problematic_attachments(last, actx, mime_fwd_all))
591       goto bail;
592   }
593
594   mutt_forward_trailer(Context->mailbox, e_parent, fp_tmp);
595
596   mutt_file_fclose(&fp_tmp);
597   fp_tmp = NULL;
598
599   /* now that we have the template, send it. */
600   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
601   el_add_email(&el, e_parent);
602   ci_send_message(SEND_NO_FLAGS, e_tmp, mutt_b2s(tmpbody), NULL, &el);
603   emaillist_clear(&el);
604   mutt_buffer_pool_release(&tmpbody);
605   return;
606
607 bail:
608   if (fp_tmp)
609   {
610     mutt_file_fclose(&fp_tmp);
611     mutt_file_unlink(mutt_b2s(tmpbody));
612   }
613   mutt_buffer_pool_release(&tmpbody);
614
615   email_free(&e_tmp);
616 }
617
618 /**
619  * attach_forward_msgs - Forward one or several message-type attachments
620  * @param fp    File handle to attachment
621  * @param actx  Attachment Context
622  * @param cur   Attachment to forward (OPTIONAL)
623  * @param flags Send mode, see #SendFlags
624  *
625  * This is different from the previous function since we want to mimic the
626  * index menu's behavior.
627  *
628  * Code reuse from ci_send_message() is not possible here - it relies on a
629  * context structure to find messages, while, on the attachment menu, messages
630  * are referenced through the attachment index.
631  */
632 static void attach_forward_msgs(FILE *fp, struct AttachCtx *actx,
633                                 struct Body *cur, SendFlags flags)
634 {
635   struct Email *e_cur = NULL;
636   struct Email *e_tmp = NULL;
637   enum QuadOption ans;
638   struct Body **last = NULL;
639   struct Buffer *tmpbody = NULL;
640   FILE *fp_tmp = NULL;
641
642   CopyHeaderFlags chflags = CH_XMIT;
643
644   if (cur)
645     e_cur = cur->email;
646   else
647   {
648     for (short i = 0; i < actx->idxlen; i++)
649     {
650       if (actx->idx[i]->content->tagged)
651       {
652         e_cur = actx->idx[i]->content->email;
653         break;
654       }
655     }
656   }
657
658   e_tmp = email_new();
659   e_tmp->env = mutt_env_new();
660   mutt_make_forward_subject(e_tmp->env, Context->mailbox, e_cur);
661
662   tmpbody = mutt_buffer_pool_get();
663
664   ans = query_quadoption(C_MimeForward, _("Forward MIME encapsulated?"));
665   if (ans == MUTT_NO)
666   {
667     /* no MIME encapsulation */
668
669     mutt_buffer_mktemp(tmpbody);
670     fp_tmp = mutt_file_fopen(mutt_b2s(tmpbody), "w");
671     if (!fp_tmp)
672     {
673       mutt_error(_("Can't create %s"), mutt_b2s(tmpbody));
674       goto cleanup;
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, 0);
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, 0);
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,
738                   mutt_buffer_is_empty(tmpbody) ? NULL : mutt_b2s(tmpbody), NULL, &el);
739   emaillist_clear(&el);
740   e_tmp = NULL; /* ci_send_message frees this */
741
742 cleanup:
743   email_free(&e_tmp);
744   mutt_buffer_pool_release(&tmpbody);
745 }
746
747 /**
748  * mutt_attach_forward - Forward an Attachment
749  * @param fp    Handle to the attachment
750  * @param e     Email
751  * @param actx  Attachment Context
752  * @param cur   Current message
753  * @param flags Send mode, see #SendFlags
754  */
755 void mutt_attach_forward(FILE *fp, struct Email *e, struct AttachCtx *actx,
756                          struct Body *cur, SendFlags flags)
757 {
758   if (check_all_msg(actx, cur, false))
759     attach_forward_msgs(fp, actx, cur, flags);
760   else
761   {
762     const short nattach = count_tagged(actx);
763     attach_forward_bodies(fp, e, actx, cur, nattach);
764   }
765 }
766
767 /**
768  * attach_reply_envelope_defaults - Create the envelope defaults for a reply
769  * @param env    Envelope to fill in
770  * @param actx   Attachment Context
771  * @param parent Parent Email
772  * @param flags  Flags, see #SendFlags
773  * @retval  0 Success
774  * @retval -1 Error
775  *
776  * This function can be invoked in two ways.
777  *
778  * Either, parent is NULL.  In this case, all tagged bodies are of a message type,
779  * and the header information is fetched from them.
780  *
781  * Or, parent is non-NULL.  In this case, cur is the common parent of all the
782  * tagged attachments.
783  *
784  * Note that this code is horribly similar to envelope_defaults() from send.c.
785  */
786 static int attach_reply_envelope_defaults(struct Envelope *env, struct AttachCtx *actx,
787                                           struct Email *parent, SendFlags flags)
788 {
789   struct Envelope *curenv = NULL;
790   struct Email *e = NULL;
791
792   if (!parent)
793   {
794     for (short i = 0; i < actx->idxlen; i++)
795     {
796       if (actx->idx[i]->content->tagged)
797       {
798         e = actx->idx[i]->content->email;
799         curenv = e->env;
800         break;
801       }
802     }
803   }
804   else
805   {
806     curenv = parent->env;
807     e = parent;
808   }
809
810   if (!curenv || !e)
811   {
812     mutt_error(_("Can't find any tagged messages"));
813     return -1;
814   }
815
816 #ifdef USE_NNTP
817   if ((flags & SEND_NEWS))
818   {
819     /* in case followup set Newsgroups: with Followup-To: if it present */
820     if (!env->newsgroups && curenv &&
821         (mutt_str_strcasecmp(curenv->followup_to, "poster") != 0))
822     {
823       env->newsgroups = mutt_str_strdup(curenv->followup_to);
824     }
825   }
826   else
827 #endif
828   {
829     if (parent)
830     {
831       if (mutt_fetch_recips(env, curenv, flags) == -1)
832         return -1;
833     }
834     else
835     {
836       for (short i = 0; i < actx->idxlen; i++)
837       {
838         if (actx->idx[i]->content->tagged &&
839             (mutt_fetch_recips(env, actx->idx[i]->content->email->env, flags) == -1))
840         {
841           return -1;
842         }
843       }
844     }
845
846     if ((flags & SEND_LIST_REPLY) && TAILQ_EMPTY(&env->to))
847     {
848       mutt_error(_("No mailing lists found"));
849       return -1;
850     }
851
852     mutt_fix_reply_recipients(env);
853   }
854   mutt_make_misc_reply_headers(env, curenv);
855
856   if (parent)
857     mutt_add_to_reference_headers(env, curenv);
858   else
859   {
860     for (short i = 0; i < actx->idxlen; i++)
861     {
862       if (actx->idx[i]->content->tagged)
863         mutt_add_to_reference_headers(env, actx->idx[i]->content->email->env);
864     }
865   }
866
867   return 0;
868 }
869
870 /**
871  * attach_include_reply - This is _very_ similar to send.c's include_reply()
872  * @param fp     File handle to attachment
873  * @param fp_tmp File handle to temporary file
874  * @param e   Email
875  */
876 static void attach_include_reply(FILE *fp, FILE *fp_tmp, struct Email *e)
877 {
878   CopyMessageFlags cmflags = MUTT_CM_PREFIX | MUTT_CM_DECODE | MUTT_CM_CHARCONV;
879   CopyHeaderFlags chflags = CH_DECODE;
880
881   mutt_make_attribution(Context->mailbox, e, fp_tmp);
882
883   if (!C_Header)
884     cmflags |= MUTT_CM_NOHEADER;
885   if (C_Weed)
886   {
887     chflags |= CH_WEED;
888     cmflags |= MUTT_CM_WEED;
889   }
890
891   mutt_copy_message_fp(fp_tmp, fp, e, cmflags, chflags, 0);
892   mutt_make_post_indent(Context->mailbox, e, fp_tmp);
893 }
894
895 /**
896  * mutt_attach_reply - Attach a reply
897  * @param fp    File handle to reply
898  * @param e     Email
899  * @param actx  Attachment Context
900  * @param e_cur   Current message
901  * @param flags Send mode, see #SendFlags
902  */
903 void mutt_attach_reply(FILE *fp, struct Email *e, struct AttachCtx *actx,
904                        struct Body *e_cur, SendFlags flags)
905 {
906   bool mime_reply_any = false;
907
908   short nattach = 0;
909   struct AttachPtr *parent = NULL;
910   struct Email *e_parent = NULL;
911   FILE *fp_parent = NULL;
912   struct Email *e_tmp = NULL;
913   FILE *fp_tmp = NULL;
914   struct Buffer *tmpbody = NULL;
915
916   char prefix[128];
917
918 #ifdef USE_NNTP
919   if (flags & SEND_NEWS)
920     OptNewsSend = true;
921   else
922     OptNewsSend = false;
923 #endif
924
925   if (!check_all_msg(actx, e_cur, false))
926   {
927     nattach = count_tagged(actx);
928     parent = find_parent(actx, e_cur, nattach);
929     if (parent)
930     {
931       e_parent = parent->content->email;
932       fp_parent = parent->fp;
933     }
934     else
935     {
936       e_parent = e;
937       fp_parent = actx->fp_root;
938     }
939   }
940
941   if ((nattach > 1) && !check_can_decode(actx, e_cur))
942   {
943     const enum QuadOption ans = query_quadoption(
944         C_MimeForwardRest, _("Can't decode all tagged attachments.  "
945                              "MIME-encapsulate the others?"));
946     if (ans == MUTT_ABORT)
947       return;
948     if (ans == MUTT_YES)
949       mime_reply_any = true;
950   }
951   else if (nattach == 1)
952     mime_reply_any = true;
953
954   e_tmp = email_new();
955   e_tmp->env = mutt_env_new();
956
957   if (attach_reply_envelope_defaults(
958           e_tmp->env, actx, e_parent ? e_parent : (e_cur ? e_cur->email : NULL), flags) == -1)
959   {
960     goto cleanup;
961   }
962
963   tmpbody = mutt_buffer_pool_get();
964   mutt_buffer_mktemp(tmpbody);
965   fp_tmp = mutt_file_fopen(mutt_b2s(tmpbody), "w");
966   if (!fp_tmp)
967   {
968     mutt_error(_("Can't create %s"), mutt_b2s(tmpbody));
969     goto cleanup;
970   }
971
972   if (!e_parent)
973   {
974     if (e_cur)
975       attach_include_reply(fp, fp_tmp, e_cur->email);
976     else
977     {
978       for (short i = 0; i < actx->idxlen; i++)
979       {
980         if (actx->idx[i]->content->tagged)
981           attach_include_reply(actx->idx[i]->fp, fp_tmp, actx->idx[i]->content->email);
982       }
983     }
984   }
985   else
986   {
987     mutt_make_attribution(Context->mailbox, e_parent, fp_tmp);
988
989     struct State st;
990     memset(&st, 0, sizeof(struct State));
991     st.fp_out = fp_tmp;
992
993     if (C_TextFlowed)
994     {
995       mutt_str_strfcpy(prefix, ">", sizeof(prefix));
996     }
997     else
998     {
999       mutt_make_string(prefix, sizeof(prefix), 0, NONULL(C_IndentString),
1000                        Context, Context->mailbox, e_parent);
1001     }
1002
1003     st.prefix = prefix;
1004     st.flags = MUTT_CHARCONV;
1005
1006     if (C_Weed)
1007       st.flags |= MUTT_WEED;
1008
1009     if (C_Header)
1010       include_header(true, fp_parent, e_parent, fp_tmp, prefix);
1011
1012     if (e_cur)
1013     {
1014       if (mutt_can_decode(e_cur))
1015       {
1016         st.fp_in = fp;
1017         mutt_body_handler(e_cur, &st);
1018         state_putc(&st, '\n');
1019       }
1020       else
1021         mutt_body_copy(fp, &e_tmp->content, e_cur);
1022     }
1023     else
1024     {
1025       for (short i = 0; i < actx->idxlen; i++)
1026       {
1027         if (actx->idx[i]->content->tagged && mutt_can_decode(actx->idx[i]->content))
1028         {
1029           st.fp_in = actx->idx[i]->fp;
1030           mutt_body_handler(actx->idx[i]->content, &st);
1031           state_putc(&st, '\n');
1032         }
1033       }
1034     }
1035
1036     mutt_make_post_indent(Context->mailbox, e_parent, fp_tmp);
1037
1038     if (mime_reply_any && !e_cur &&
1039         !copy_problematic_attachments(&e_tmp->content, actx, false))
1040     {
1041       goto cleanup;
1042     }
1043   }
1044
1045   mutt_file_fclose(&fp_tmp);
1046
1047   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1048   el_add_email(&el, e_parent ? e_parent : (e_cur ? e_cur->email : NULL));
1049   if (ci_send_message(flags, e_tmp, mutt_b2s(tmpbody), NULL, &el) == 0)
1050   {
1051     mutt_set_flag(Context->mailbox, e, MUTT_REPLIED, true);
1052   }
1053   e_tmp = NULL; /* ci_send_message frees this */
1054
1055 cleanup:
1056   if (fp_tmp)
1057   {
1058     mutt_file_fclose(&fp_tmp);
1059     mutt_file_unlink(mutt_b2s(tmpbody));
1060   }
1061   mutt_buffer_pool_release(&tmpbody);
1062   email_free(&e_tmp);
1063   emaillist_clear(&el);
1064 }
1065
1066 /**
1067  * mutt_attach_mail_sender - Compose an email to the sender in the email attachment
1068  * @param fp   File containing attachment (UNUSED)
1069  * @param e    Email (UNUSED)
1070  * @param actx Attachment Context
1071  * @param cur  Current attachment
1072  */
1073 void mutt_attach_mail_sender(FILE *fp, struct Email *e, struct AttachCtx *actx,
1074                              struct Body *cur)
1075 {
1076   if (!check_all_msg(actx, cur, 0))
1077   {
1078     /* L10N: You will see this error message if you invoke <compose-to-sender>
1079        when you are on a normal attachment.  */
1080     mutt_error(_("You may only compose to sender with message/rfc822 parts"));
1081     return;
1082   }
1083
1084   struct Email *e_tmp = email_new();
1085   e_tmp->env = mutt_env_new();
1086
1087   if (cur)
1088   {
1089     if (mutt_fetch_recips(e_tmp->env, cur->email->env, SEND_TO_SENDER) == -1)
1090     {
1091       email_free(&e_tmp);
1092       return;
1093     }
1094   }
1095   else
1096   {
1097     for (int i = 0; i < actx->idxlen; i++)
1098     {
1099       if (actx->idx[i]->content->tagged &&
1100           (mutt_fetch_recips(e_tmp->env, actx->idx[i]->content->email->env,
1101                              SEND_TO_SENDER) == -1))
1102       {
1103         email_free(&e_tmp);
1104         return;
1105       }
1106     }
1107   }
1108
1109   // This call will free e_tmp for us
1110   ci_send_message(SEND_NO_FLAGS, e_tmp, NULL, NULL, NULL);
1111 }