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