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