1 /**
2 * @file
3 * Duplicate the structure of an entire email
4 *
5 * @authors
6 * Copyright (C) 1996-2000,2002,2014 Michael R. Elkins <me@mutt.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_copy Duplicate the structure of an entire email
26 *
27 * Duplicate the structure of an entire email
28 */
29
30 #include "config.h"
31 #include <ctype.h>
32 #include <inttypes.h> // IWYU pragma: keep
33 #include <stdbool.h>
34 #include <string.h>
35 #include "mutt/lib.h"
36 #include "address/lib.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
39 #include "core/lib.h"
40 #include "gui/lib.h"
41 #include "mutt.h"
42 #include "copy.h"
43 #include "ncrypt/lib.h"
44 #include "send/lib.h"
45 #include "context.h"
46 #include "format_flags.h"
47 #include "handler.h"
48 #include "hdrline.h"
49 #include "mutt_globals.h"
50 #include "mx.h"
51 #ifdef USE_NOTMUCH
52 #include "notmuch/lib.h"
53 #include "muttlib.h"
54 #endif
55 #ifdef ENABLE_NLS
56 #include <libintl.h>
57 #endif
58
59 static int address_header_decode(char **h);
60 static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out,
61 const char *quoted_date);
62
63 ARRAY_HEAD(Headers, char *);
64
65 /**
66 * add_one_header - Add a header to a Headers array
67 * @param headers Headers array
68 * @param pos Position to insert new header
69 * @param value Text to insert
70 *
71 * If a header already exists in that position, the new text will be
72 * concatenated on the old.
73 */
add_one_header(struct Headers * headers,size_t pos,char * value)74 static void add_one_header(struct Headers *headers, size_t pos, char *value)
75 {
76 char **old = ARRAY_GET(headers, pos);
77 if (old && *old)
78 {
79 char *new_value = NULL;
80 mutt_str_asprintf(&new_value, "%s%s", *old, value);
81 FREE(old);
82 FREE(&value);
83 value = new_value;
84 }
85 ARRAY_SET(headers, pos, value);
86 }
87
88 /**
89 * mutt_copy_hdr - Copy header from one file to another
90 * @param fp_in FILE pointer to read from
91 * @param fp_out FILE pointer to write to
92 * @param off_start Offset to start from
93 * @param off_end Offset to finish at
94 * @param chflags Flags, see #CopyHeaderFlags
95 * @param prefix Prefix for quoting headers
96 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
97 * @retval 0 Success
98 * @retval -1 Failure
99 *
100 * Ok, the only reason for not merging this with mutt_copy_header() below is to
101 * avoid creating a Email structure in message_handler(). Also, this one will
102 * wrap headers much more aggressively than the other one.
103 */
mutt_copy_hdr(FILE * fp_in,FILE * fp_out,LOFF_T off_start,LOFF_T off_end,CopyHeaderFlags chflags,const char * prefix,int wraplen)104 int mutt_copy_hdr(FILE *fp_in, FILE *fp_out, LOFF_T off_start, LOFF_T off_end,
105 CopyHeaderFlags chflags, const char *prefix, int wraplen)
106 {
107 bool from = false;
108 bool this_is_from = false;
109 bool ignore = false;
110 char buf[1024]; /* should be long enough to get most fields in one pass */
111 char *nl = NULL;
112 struct Headers headers = ARRAY_HEAD_INITIALIZER;
113 int hdr_count;
114 int x;
115 char *this_one = NULL;
116 size_t this_one_len = 0;
117
118 if (off_start < 0)
119 return -1;
120
121 if (ftello(fp_in) != off_start)
122 if (fseeko(fp_in, off_start, SEEK_SET) < 0)
123 return -1;
124
125 buf[0] = '\n';
126 buf[1] = '\0';
127
128 if ((chflags & (CH_REORDER | CH_WEED | CH_MIME | CH_DECODE | CH_PREFIX | CH_WEED_DELIVERED)) == 0)
129 {
130 /* Without these flags to complicate things
131 * we can do a more efficient line to line copying */
132 while (ftello(fp_in) < off_end)
133 {
134 nl = strchr(buf, '\n');
135
136 if (!fgets(buf, sizeof(buf), fp_in))
137 break;
138
139 /* Is it the beginning of a header? */
140 if (nl && (buf[0] != ' ') && (buf[0] != '\t'))
141 {
142 ignore = true;
143 if (!from && mutt_str_startswith(buf, "From "))
144 {
145 if ((chflags & CH_FROM) == 0)
146 continue;
147 from = true;
148 }
149 else if ((chflags & CH_NOQFROM) && mutt_istr_startswith(buf, ">From "))
150 continue;
151 else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n')))
152 break; /* end of header */
153
154 if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
155 (mutt_istr_startswith(buf, "Status:") || mutt_istr_startswith(buf, "X-Status:")))
156 {
157 continue;
158 }
159 if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
160 (mutt_istr_startswith(buf, "Content-Length:") ||
161 mutt_istr_startswith(buf, "Lines:")))
162 {
163 continue;
164 }
165 if ((chflags & CH_UPDATE_REFS) &&
166 mutt_istr_startswith(buf, "References:"))
167 {
168 continue;
169 }
170 if ((chflags & CH_UPDATE_IRT) &&
171 mutt_istr_startswith(buf, "In-Reply-To:"))
172 {
173 continue;
174 }
175 if (chflags & CH_UPDATE_LABEL && mutt_istr_startswith(buf, "X-Label:"))
176 continue;
177 if ((chflags & CH_UPDATE_SUBJECT) &&
178 mutt_istr_startswith(buf, "Subject:"))
179 {
180 continue;
181 }
182
183 ignore = false;
184 }
185
186 if (!ignore && (fputs(buf, fp_out) == EOF))
187 return -1;
188 }
189 return 0;
190 }
191
192 hdr_count = 1;
193 x = 0;
194
195 /* We are going to read and collect the headers in an array
196 * so we are able to do re-ordering.
197 * First count the number of entries in the array */
198 if (chflags & CH_REORDER)
199 {
200 struct ListNode *np = NULL;
201 STAILQ_FOREACH(np, &HeaderOrderList, entries)
202 {
203 mutt_debug(LL_DEBUG3, "Reorder list: %s\n", np->data);
204 hdr_count++;
205 }
206 }
207
208 mutt_debug(LL_DEBUG1, "WEED is %sset\n", (chflags & CH_WEED) ? "" : "not ");
209
210 ARRAY_RESERVE(&headers, hdr_count);
211
212 /* Read all the headers into the array */
213 while (ftello(fp_in) < off_end)
214 {
215 nl = strchr(buf, '\n');
216
217 /* Read a line */
218 if (!fgets(buf, sizeof(buf), fp_in))
219 break;
220
221 /* Is it the beginning of a header? */
222 if (nl && (buf[0] != ' ') && (buf[0] != '\t'))
223 {
224 /* Do we have anything pending? */
225 if (this_one)
226 {
227 if (chflags & CH_DECODE)
228 {
229 if (address_header_decode(&this_one) == 0)
230 rfc2047_decode(&this_one);
231 this_one_len = mutt_str_len(this_one);
232
233 /* Convert CRLF line endings to LF */
234 if ((this_one_len > 2) && (this_one[this_one_len - 2] == '\r') &&
235 (this_one[this_one_len - 1] == '\n'))
236 {
237 this_one[this_one_len - 2] = '\n';
238 this_one[this_one_len - 1] = '\0';
239 }
240 }
241
242 add_one_header(&headers, x, this_one);
243 this_one = NULL;
244 }
245
246 ignore = true;
247 this_is_from = false;
248 if (!from && mutt_str_startswith(buf, "From "))
249 {
250 if ((chflags & CH_FROM) == 0)
251 continue;
252 this_is_from = true;
253 from = true;
254 }
255 else if ((buf[0] == '\n') || ((buf[0] == '\r') && (buf[1] == '\n')))
256 break; /* end of header */
257
258 /* note: CH_FROM takes precedence over header weeding. */
259 if (!((chflags & CH_FROM) && (chflags & CH_FORCE_FROM) && this_is_from) &&
260 (chflags & CH_WEED) && mutt_matches_ignore(buf))
261 {
262 continue;
263 }
264 if ((chflags & CH_WEED_DELIVERED) &&
265 mutt_istr_startswith(buf, "Delivered-To:"))
266 {
267 continue;
268 }
269 if ((chflags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
270 (mutt_istr_startswith(buf, "Status:") ||
271 mutt_istr_startswith(buf, "X-Status:")))
272 {
273 continue;
274 }
275 if ((chflags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
276 (mutt_istr_startswith(buf, "Content-Length:") || mutt_istr_startswith(buf, "Lines:")))
277 {
278 continue;
279 }
280 if ((chflags & CH_MIME))
281 {
282 if (mutt_istr_startswith(buf, "mime-version:"))
283 {
284 continue;
285 }
286 size_t plen = mutt_istr_startswith(buf, "content-");
287 if ((plen != 0) &&
288 (mutt_istr_startswith(buf + plen, "transfer-encoding:") ||
289 mutt_istr_startswith(buf + plen, "type:")))
290 {
291 continue;
292 }
293 }
294 if ((chflags & CH_UPDATE_REFS) &&
295 mutt_istr_startswith(buf, "References:"))
296 {
297 continue;
298 }
299 if ((chflags & CH_UPDATE_IRT) &&
300 mutt_istr_startswith(buf, "In-Reply-To:"))
301 {
302 continue;
303 }
304 if ((chflags & CH_UPDATE_LABEL) && mutt_istr_startswith(buf, "X-Label:"))
305 continue;
306 if ((chflags & CH_UPDATE_SUBJECT) &&
307 mutt_istr_startswith(buf, "Subject:"))
308 {
309 continue;
310 }
311
312 /* Find x -- the array entry where this header is to be saved */
313 if (chflags & CH_REORDER)
314 {
315 struct ListNode *np = NULL;
316 x = 0;
317 int match = -1;
318 size_t match_len = 0, hdr_order_len;
319
320 STAILQ_FOREACH(np, &HeaderOrderList, entries)
321 {
322 x++;
323 hdr_order_len = mutt_str_len(np->data);
324 if (mutt_istrn_equal(buf, np->data, hdr_order_len))
325 {
326 if ((match == -1) || (hdr_order_len > match_len))
327 {
328 match = x;
329 match_len = hdr_order_len;
330 }
331 mutt_debug(LL_DEBUG2, "Reorder: %s matches %s", np->data, buf);
332 }
333 }
334 if (match != -1)
335 x = match;
336 }
337
338 ignore = false;
339 } /* If beginning of header */
340
341 if (!ignore)
342 {
343 mutt_debug(LL_DEBUG2, "Reorder: x = %d; hdr_count = %d\n", x, hdr_count);
344 if (this_one)
345 {
346 size_t blen = mutt_str_len(buf);
347
348 mutt_mem_realloc(&this_one, this_one_len + blen + sizeof(char));
349 strcat(this_one + this_one_len, buf);
350 this_one_len += blen;
351 }
352 else
353 {
354 this_one = mutt_str_dup(buf);
355 this_one_len = mutt_str_len(this_one);
356 }
357 }
358 } /* while (ftello (fp_in) < off_end) */
359
360 /* Do we have anything pending? -- XXX, same code as in above in the loop. */
361 if (this_one)
362 {
363 if (chflags & CH_DECODE)
364 {
365 if (address_header_decode(&this_one) == 0)
366 rfc2047_decode(&this_one);
367 this_one_len = mutt_str_len(this_one);
368 }
369
370 add_one_header(&headers, x, this_one);
371 this_one = NULL;
372 }
373
374 /* Now output the headers in order */
375 bool error = false;
376 char **hp = NULL;
377 ARRAY_FOREACH(hp, &headers)
378 {
379 if (!error && hp && *hp)
380 {
381 /* We couldn't do the prefixing when reading because RFC2047
382 * decoding may have concatenated lines. */
383 if (chflags & (CH_DECODE | CH_PREFIX))
384 {
385 const char *pre = (chflags & CH_PREFIX) ? prefix : NULL;
386 const short c_wrap = cs_subset_number(NeoMutt->sub, "wrap");
387 wraplen = mutt_window_wrap_cols(wraplen, c_wrap);
388
389 if (mutt_write_one_header(fp_out, 0, *hp, pre, wraplen, chflags, NeoMutt->sub) == -1)
390 {
391 error = true;
392 }
393 }
394 else
395 {
396 if (fputs(*hp, fp_out) == EOF)
397 {
398 error = true;
399 }
400 }
401 }
402
403 FREE(hp);
404 }
405 ARRAY_FREE(&headers);
406
407 if (error)
408 return -1;
409 return 0;
410 }
411
412 /**
413 * mutt_copy_header - Copy Email header
414 * @param fp_in FILE pointer to read from
415 * @param e Email
416 * @param fp_out FILE pointer to write to
417 * @param chflags See #CopyHeaderFlags
418 * @param prefix Prefix for quoting headers (if #CH_PREFIX is set)
419 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
420 * @retval 0 Success
421 * @retval -1 Failure
422 */
mutt_copy_header(FILE * fp_in,struct Email * e,FILE * fp_out,CopyHeaderFlags chflags,const char * prefix,int wraplen)423 int mutt_copy_header(FILE *fp_in, struct Email *e, FILE *fp_out,
424 CopyHeaderFlags chflags, const char *prefix, int wraplen)
425 {
426 char *temp_hdr = NULL;
427
428 if (e->env)
429 {
430 chflags |= ((e->env->changed & MUTT_ENV_CHANGED_IRT) ? CH_UPDATE_IRT : 0) |
431 ((e->env->changed & MUTT_ENV_CHANGED_REFS) ? CH_UPDATE_REFS : 0) |
432 ((e->env->changed & MUTT_ENV_CHANGED_XLABEL) ? CH_UPDATE_LABEL : 0) |
433 ((e->env->changed & MUTT_ENV_CHANGED_SUBJECT) ? CH_UPDATE_SUBJECT : 0);
434 }
435
436 if (mutt_copy_hdr(fp_in, fp_out, e->offset, e->body->offset, chflags, prefix, wraplen) == -1)
437 return -1;
438
439 if (chflags & CH_TXTPLAIN)
440 {
441 char chsbuf[128];
442 char buf[128];
443 fputs("MIME-Version: 1.0\n", fp_out);
444 fputs("Content-Transfer-Encoding: 8bit\n", fp_out);
445 fputs("Content-Type: text/plain; charset=", fp_out);
446 const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
447 mutt_ch_canonical_charset(chsbuf, sizeof(chsbuf), c_charset ? c_charset : "us-ascii");
448 mutt_addr_cat(buf, sizeof(buf), chsbuf, MimeSpecials);
449 fputs(buf, fp_out);
450 fputc('\n', fp_out);
451 }
452
453 if ((chflags & CH_UPDATE_IRT) && !STAILQ_EMPTY(&e->env->in_reply_to))
454 {
455 fputs("In-Reply-To:", fp_out);
456 struct ListNode *np = NULL;
457 STAILQ_FOREACH(np, &e->env->in_reply_to, entries)
458 {
459 fputc(' ', fp_out);
460 fputs(np->data, fp_out);
461 }
462 fputc('\n', fp_out);
463 }
464
465 if ((chflags & CH_UPDATE_REFS) && !STAILQ_EMPTY(&e->env->references))
466 {
467 fputs("References:", fp_out);
468 mutt_write_references(&e->env->references, fp_out, 0);
469 fputc('\n', fp_out);
470 }
471
472 if ((chflags & CH_UPDATE) && ((chflags & CH_NOSTATUS) == 0))
473 {
474 if (e->old || e->read)
475 {
476 fputs("Status: ", fp_out);
477 if (e->read)
478 fputs("RO", fp_out);
479 else if (e->old)
480 fputc('O', fp_out);
481 fputc('\n', fp_out);
482 }
483
484 if (e->flagged || e->replied)
485 {
486 fputs("X-Status: ", fp_out);
487 if (e->replied)
488 fputc('A', fp_out);
489 if (e->flagged)
490 fputc('F', fp_out);
491 fputc('\n', fp_out);
492 }
493 }
494
495 if (chflags & CH_UPDATE_LEN && ((chflags & CH_NOLEN) == 0))
496 {
497 fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", e->body->length);
498 if ((e->lines != 0) || (e->body->length == 0))
499 fprintf(fp_out, "Lines: %d\n", e->lines);
500 }
501
502 const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
503 #ifdef USE_NOTMUCH
504 if (chflags & CH_VIRTUAL)
505 {
506 /* Add some fake headers based on notmuch data */
507 char *folder = nm_email_get_folder(e);
508 if (folder && !(c_weed && mutt_matches_ignore("folder")))
509 {
510 char buf[1024];
511 mutt_str_copy(buf, folder, sizeof(buf));
512 mutt_pretty_mailbox(buf, sizeof(buf));
513
514 fputs("Folder: ", fp_out);
515 fputs(buf, fp_out);
516 fputc('\n', fp_out);
517 }
518 }
519 #endif
520 char *tags = driver_tags_get(&e->tags);
521 if (tags && !(c_weed && mutt_matches_ignore("tags")))
522 {
523 fputs("Tags: ", fp_out);
524 fputs(tags, fp_out);
525 fputc('\n', fp_out);
526 }
527 FREE(&tags);
528
529 const char *const c_send_charset =
530 cs_subset_string(NeoMutt->sub, "send_charset");
531 const short c_wrap = cs_subset_number(NeoMutt->sub, "wrap");
532 if ((chflags & CH_UPDATE_LABEL) && e->env->x_label)
533 {
534 temp_hdr = e->env->x_label;
535 /* env->x_label isn't currently stored with direct references elsewhere.
536 * Context->label_hash strdups the keys. But to be safe, encode a copy */
537 if (!(chflags & CH_DECODE))
538 {
539 temp_hdr = mutt_str_dup(temp_hdr);
540 rfc2047_encode(&temp_hdr, NULL, sizeof("X-Label:"), c_send_charset);
541 }
542 if (mutt_write_one_header(
543 fp_out, "X-Label", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0,
544 mutt_window_wrap_cols(wraplen, c_wrap), chflags, NeoMutt->sub) == -1)
545 {
546 return -1;
547 }
548 if (!(chflags & CH_DECODE))
549 FREE(&temp_hdr);
550 }
551
552 if ((chflags & CH_UPDATE_SUBJECT) && e->env->subject)
553 {
554 temp_hdr = e->env->subject;
555 /* env->subject is directly referenced in Context->subj_hash, so we
556 * have to be careful not to encode (and thus free) that memory. */
557 if (!(chflags & CH_DECODE))
558 {
559 temp_hdr = mutt_str_dup(temp_hdr);
560 rfc2047_encode(&temp_hdr, NULL, sizeof("Subject:"), c_send_charset);
561 }
562 if (mutt_write_one_header(
563 fp_out, "Subject", temp_hdr, (chflags & CH_PREFIX) ? prefix : 0,
564 mutt_window_wrap_cols(wraplen, c_wrap), chflags, NeoMutt->sub) == -1)
565 {
566 return -1;
567 }
568 if (!(chflags & CH_DECODE))
569 FREE(&temp_hdr);
570 }
571
572 if ((chflags & CH_NONEWLINE) == 0)
573 {
574 if (chflags & CH_PREFIX)
575 fputs(prefix, fp_out);
576 fputc('\n', fp_out); /* add header terminator */
577 }
578
579 if (ferror(fp_out) || feof(fp_out))
580 return -1;
581
582 return 0;
583 }
584
585 /**
586 * count_delete_lines - Count lines to be deleted in this email body
587 * @param fp FILE pointer to read from
588 * @param b Email Body
589 * @param length Number of bytes to be deleted
590 * @param datelen Length of the date
591 * @retval num Number of lines to be deleted
592 * @retval -1 on error
593 *
594 * Count the number of lines and bytes to be deleted in this body
595 */
count_delete_lines(FILE * fp,struct Body * b,LOFF_T * length,size_t datelen)596 static int count_delete_lines(FILE *fp, struct Body *b, LOFF_T *length, size_t datelen)
597 {
598 int dellines = 0;
599
600 if (b->deleted)
601 {
602 if (fseeko(fp, b->offset, SEEK_SET) != 0)
603 {
604 return -1;
605 }
606 for (long l = b->length; l; l--)
607 {
608 const int ch = getc(fp);
609 if (ch == EOF)
610 break;
611 if (ch == '\n')
612 dellines++;
613 }
614 /* 3 and 89 come from the added header of three lines in
615 * copy_delete_attach(). 89 is the size of the header(including
616 * the newlines, tabs, and a single digit length), not including
617 * the date length. */
618 dellines -= 3;
619 *length -= b->length - (89 + datelen);
620 /* Count the number of digits exceeding the first one to write the size */
621 for (long l = 10; b->length >= l; l *= 10)
622 (*length)++;
623 }
624 else
625 {
626 for (b = b->parts; b; b = b->next)
627 {
628 const int del = count_delete_lines(fp, b, length, datelen);
629 if (del == -1)
630 {
631 return -1;
632 }
633 dellines += del;
634 }
635 }
636 return dellines;
637 }
638
639 /**
640 * mutt_copy_message_fp - Make a copy of a message from a FILE pointer
641 * @param fp_out Where to write output
642 * @param fp_in Where to get input
643 * @param e Email being copied
644 * @param cmflags Flags, see #CopyMessageFlags
645 * @param chflags Flags, see #CopyHeaderFlags
646 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
647 * @retval 0 Success
648 * @retval -1 Failure
649 */
mutt_copy_message_fp(FILE * fp_out,FILE * fp_in,struct Email * e,CopyMessageFlags cmflags,CopyHeaderFlags chflags,int wraplen)650 int mutt_copy_message_fp(FILE *fp_out, FILE *fp_in, struct Email *e,
651 CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
652 {
653 struct Body *body = e->body;
654 char prefix[128];
655 LOFF_T new_offset = -1;
656 int rc = 0;
657
658 if (cmflags & MUTT_CM_PREFIX)
659 {
660 const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
661 if (c_text_flowed)
662 mutt_str_copy(prefix, ">", sizeof(prefix));
663 else
664 {
665 const char *const c_indent_string =
666 cs_subset_string(NeoMutt->sub, "indent_string");
667 mutt_make_string(prefix, sizeof(prefix), wraplen, NONULL(c_indent_string),
668 Context->mailbox, -1, e, MUTT_FORMAT_NO_FLAGS, NULL);
669 }
670 }
671
672 if ((cmflags & MUTT_CM_NOHEADER) == 0)
673 {
674 if (cmflags & MUTT_CM_PREFIX)
675 chflags |= CH_PREFIX;
676 else if (e->attach_del && (chflags & CH_UPDATE_LEN))
677 {
678 int new_lines;
679 int rc_attach_del = -1;
680 LOFF_T new_length = body->length;
681 struct Buffer *quoted_date = NULL;
682
683 quoted_date = mutt_buffer_pool_get();
684 mutt_buffer_addch(quoted_date, '"');
685 mutt_date_make_date(quoted_date,
686 cs_subset_bool(NeoMutt->sub, "local_date_header"));
687 mutt_buffer_addch(quoted_date, '"');
688
689 /* Count the number of lines and bytes to be deleted */
690 if (fseeko(fp_in, body->offset, SEEK_SET) != 0)
691 {
692 goto attach_del_cleanup;
693 }
694 const int del =
695 count_delete_lines(fp_in, body, &new_length, mutt_buffer_len(quoted_date));
696 if (del == -1)
697 {
698 goto attach_del_cleanup;
699 }
700 new_lines = e->lines - del;
701
702 /* Copy the headers */
703 if (mutt_copy_header(fp_in, e, fp_out, chflags | CH_NOLEN | CH_NONEWLINE, NULL, wraplen))
704 goto attach_del_cleanup;
705 fprintf(fp_out, "Content-Length: " OFF_T_FMT "\n", new_length);
706 if (new_lines <= 0)
707 new_lines = 0;
708 else
709 fprintf(fp_out, "Lines: %d\n", new_lines);
710
711 putc('\n', fp_out);
712 if (ferror(fp_out) || feof(fp_out))
713 goto attach_del_cleanup;
714 new_offset = ftello(fp_out);
715
716 /* Copy the body */
717 if (fseeko(fp_in, body->offset, SEEK_SET) < 0)
718 goto attach_del_cleanup;
719 if (copy_delete_attach(body, fp_in, fp_out, mutt_buffer_string(quoted_date)))
720 goto attach_del_cleanup;
721
722 mutt_buffer_pool_release("ed_date);
723
724 LOFF_T fail = ((ftello(fp_out) - new_offset) - new_length);
725 if (fail)
726 {
727 mutt_error(ngettext("The length calculation was wrong by %ld byte",
728 "The length calculation was wrong by %ld bytes", fail),
729 fail);
730 new_length += fail;
731 }
732
733 /* Update original message if we are sync'ing a mailfolder */
734 if (cmflags & MUTT_CM_UPDATE)
735 {
736 e->attach_del = false;
737 e->lines = new_lines;
738 body->offset = new_offset;
739
740 /* update the total size of the mailbox to reflect this deletion */
741 Context->mailbox->size -= body->length - new_length;
742 /* if the message is visible, update the visible size of the mailbox as well. */
743 if (Context->mailbox->v2r[e->msgno] != -1)
744 Context->vsize -= body->length - new_length;
745
746 body->length = new_length;
747 mutt_body_free(&body->parts);
748 }
749
750 rc_attach_del = 0;
751
752 attach_del_cleanup:
753 mutt_buffer_pool_release("ed_date);
754 return rc_attach_del;
755 }
756
757 if (mutt_copy_header(fp_in, e, fp_out, chflags,
758 (chflags & CH_PREFIX) ? prefix : NULL, wraplen) == -1)
759 {
760 return -1;
761 }
762
763 new_offset = ftello(fp_out);
764 }
765
766 if (cmflags & MUTT_CM_DECODE)
767 {
768 /* now make a text/plain version of the message */
769 struct State s = { 0 };
770 s.fp_in = fp_in;
771 s.fp_out = fp_out;
772 if (cmflags & MUTT_CM_PREFIX)
773 s.prefix = prefix;
774 if (cmflags & MUTT_CM_DISPLAY)
775 {
776 s.flags |= MUTT_DISPLAY;
777 s.wraplen = wraplen;
778 }
779 if (cmflags & MUTT_CM_PRINTING)
780 s.flags |= MUTT_PRINTING;
781 if (cmflags & MUTT_CM_WEED)
782 s.flags |= MUTT_WEED;
783 if (cmflags & MUTT_CM_CHARCONV)
784 s.flags |= MUTT_CHARCONV;
785 if (cmflags & MUTT_CM_REPLYING)
786 s.flags |= MUTT_REPLYING;
787
788 if ((WithCrypto != 0) && cmflags & MUTT_CM_VERIFY)
789 s.flags |= MUTT_VERIFY;
790
791 rc = mutt_body_handler(body, &s);
792 }
793 else if ((WithCrypto != 0) && (cmflags & MUTT_CM_DECODE_CRYPT) && (e->security & SEC_ENCRYPT))
794 {
795 struct Body *cur = NULL;
796 FILE *fp = NULL;
797
798 if (((WithCrypto & APPLICATION_PGP) != 0) && (cmflags & MUTT_CM_DECODE_PGP) &&
799 (e->security & APPLICATION_PGP) && (e->body->type == TYPE_MULTIPART))
800 {
801 if (crypt_pgp_decrypt_mime(fp_in, &fp, e->body, &cur))
802 return -1;
803 fputs("MIME-Version: 1.0\n", fp_out);
804 }
805
806 if (((WithCrypto & APPLICATION_SMIME) != 0) && (cmflags & MUTT_CM_DECODE_SMIME) &&
807 (e->security & APPLICATION_SMIME) && (e->body->type == TYPE_APPLICATION))
808 {
809 if (crypt_smime_decrypt_mime(fp_in, &fp, e->body, &cur))
810 return -1;
811 }
812
813 if (!cur)
814 {
815 mutt_error(_("No decryption engine available for message"));
816 return -1;
817 }
818
819 mutt_write_mime_header(cur, fp_out, NeoMutt->sub);
820 fputc('\n', fp_out);
821
822 if (fseeko(fp, cur->offset, SEEK_SET) < 0)
823 return -1;
824 if (mutt_file_copy_bytes(fp, fp_out, cur->length) == -1)
825 {
826 mutt_file_fclose(&fp);
827 mutt_body_free(&cur);
828 return -1;
829 }
830 mutt_body_free(&cur);
831 mutt_file_fclose(&fp);
832 }
833 else
834 {
835 if (fseeko(fp_in, body->offset, SEEK_SET) < 0)
836 return -1;
837 if (cmflags & MUTT_CM_PREFIX)
838 {
839 int c;
840 size_t bytes = body->length;
841
842 fputs(prefix, fp_out);
843
844 while (((c = fgetc(fp_in)) != EOF) && bytes--)
845 {
846 fputc(c, fp_out);
847 if (c == '\n')
848 {
849 fputs(prefix, fp_out);
850 }
851 }
852 }
853 else if (mutt_file_copy_bytes(fp_in, fp_out, body->length) == -1)
854 return -1;
855 }
856
857 if ((cmflags & MUTT_CM_UPDATE) && ((cmflags & MUTT_CM_NOHEADER) == 0) &&
858 (new_offset != -1))
859 {
860 body->offset = new_offset;
861 mutt_body_free(&body->parts);
862 }
863
864 return rc;
865 }
866
867 /**
868 * mutt_copy_message - Copy a message from a Mailbox
869 * @param fp_out FILE pointer to write to
870 * @param e Email
871 * @param msg Message
872 * @param cmflags Flags, see #CopyMessageFlags
873 * @param chflags Flags, see #CopyHeaderFlags
874 * @param wraplen Width to wrap at (when chflags & CH_DISPLAY)
875 * @retval 0 Success
876 * @retval -1 Failure
877 *
878 * should be made to return -1 on fatal errors, and 1 on non-fatal errors
879 * like partial decode, where it is worth displaying as much as possible
880 */
mutt_copy_message(FILE * fp_out,struct Email * e,struct Message * msg,CopyMessageFlags cmflags,CopyHeaderFlags chflags,int wraplen)881 int mutt_copy_message(FILE *fp_out, struct Email *e, struct Message *msg,
882 CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
883 {
884 if (!msg || !e->body)
885 {
886 return -1;
887 }
888 if (fp_out == msg->fp)
889 {
890 mutt_debug(LL_DEBUG1, "trying to read/write from/to the same FILE*!\n");
891 return -1;
892 }
893
894 int rc = mutt_copy_message_fp(fp_out, msg->fp, e, cmflags, chflags, wraplen);
895 if ((rc == 0) && (ferror(fp_out) || feof(fp_out)))
896 {
897 mutt_debug(LL_DEBUG1, "failed to detect EOF!\n");
898 rc = -1;
899 }
900 return rc;
901 }
902
903 /**
904 * append_message - Appends a copy of the given message to a mailbox
905 * @param dest destination mailbox
906 * @param fp_in where to get input
907 * @param src source mailbox
908 * @param e Email being copied
909 * @param cmflags Flags, see #CopyMessageFlags
910 * @param chflags Flags, see #CopyHeaderFlags
911 * @retval 0 Success
912 * @retval -1 Error
913 */
append_message(struct Mailbox * dest,FILE * fp_in,struct Mailbox * src,struct Email * e,CopyMessageFlags cmflags,CopyHeaderFlags chflags)914 static int append_message(struct Mailbox *dest, FILE *fp_in, struct Mailbox *src,
915 struct Email *e, CopyMessageFlags cmflags, CopyHeaderFlags chflags)
916 {
917 char buf[256];
918 struct Message *msg = NULL;
919 int rc;
920
921 if (fseeko(fp_in, e->offset, SEEK_SET) < 0)
922 return -1;
923 if (!fgets(buf, sizeof(buf), fp_in))
924 return -1;
925
926 msg = mx_msg_open_new(dest, e, is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
927 if (!msg)
928 return -1;
929 if ((dest->type == MUTT_MBOX) || (dest->type == MUTT_MMDF))
930 chflags |= CH_FROM | CH_FORCE_FROM;
931 chflags |= ((dest->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
932 rc = mutt_copy_message_fp(msg->fp, fp_in, e, cmflags, chflags, 0);
933 if (mx_msg_commit(dest, msg) != 0)
934 rc = -1;
935
936 #ifdef USE_NOTMUCH
937 if (msg->committed_path && (dest->type == MUTT_MAILDIR) && (src->type == MUTT_NOTMUCH))
938 nm_update_filename(src, NULL, msg->committed_path, e);
939 #endif
940
941 mx_msg_close(dest, &msg);
942 return rc;
943 }
944
945 /**
946 * mutt_append_message - Append a message
947 * @param m_dst Destination Mailbox
948 * @param m_src Source Mailbox
949 * @param e Email
950 * @param msg Message
951 * @param cmflags Flags, see #CopyMessageFlags
952 * @param chflags Flags, see #CopyHeaderFlags
953 * @retval 0 Success
954 * @retval -1 Failure
955 */
mutt_append_message(struct Mailbox * m_dst,struct Mailbox * m_src,struct Email * e,struct Message * msg,CopyMessageFlags cmflags,CopyHeaderFlags chflags)956 int mutt_append_message(struct Mailbox *m_dst, struct Mailbox *m_src,
957 struct Email *e, struct Message *msg,
958 CopyMessageFlags cmflags, CopyHeaderFlags chflags)
959 {
960 const bool own_msg = !msg;
961 if (own_msg && !(msg = mx_msg_open(m_src, e->msgno)))
962 {
963 return -1;
964 }
965
966 int rc = append_message(m_dst, msg->fp, m_src, e, cmflags, chflags);
967 if (own_msg)
968 {
969 mx_msg_close(m_src, &msg);
970 }
971 return rc;
972 }
973
974 /**
975 * copy_delete_attach - Copy a message, deleting marked attachments
976 * @param b Email Body
977 * @param fp_in FILE pointer to read from
978 * @param fp_out FILE pointer to write to
979 * @param quoted_date Date stamp
980 * @retval 0 Success
981 * @retval -1 Failure
982 *
983 * This function copies a message body, while deleting _in_the_copy_
984 * any attachments which are marked for deletion.
985 * Nothing is changed in the original message -- this is left to the caller.
986 */
copy_delete_attach(struct Body * b,FILE * fp_in,FILE * fp_out,const char * quoted_date)987 static int copy_delete_attach(struct Body *b, FILE *fp_in, FILE *fp_out, const char *quoted_date)
988 {
989 struct Body *part = NULL;
990
991 for (part = b->parts; part; part = part->next)
992 {
993 if (part->deleted || part->parts)
994 {
995 /* Copy till start of this part */
996 if (mutt_file_copy_bytes(fp_in, fp_out, part->hdr_offset - ftello(fp_in)))
997 {
998 return -1;
999 }
1000
1001 if (part->deleted)
1002 {
1003 /* If this is modified, count_delete_lines() needs to be changed too */
1004 fprintf(
1005 fp_out,
1006 "Content-Type: message/external-body; access-type=x-mutt-deleted;\n"
1007 "\texpiration=%s; length=" OFF_T_FMT "\n"
1008 "\n",
1009 quoted_date, part->length);
1010 if (ferror(fp_out))
1011 {
1012 return -1;
1013 }
1014
1015 /* Copy the original mime headers */
1016 if (mutt_file_copy_bytes(fp_in, fp_out, part->offset - ftello(fp_in)))
1017 {
1018 return -1;
1019 }
1020
1021 /* Skip the deleted body */
1022 if (fseeko(fp_in, part->offset + part->length, SEEK_SET) != 0)
1023 {
1024 return -1;
1025 }
1026 }
1027 else
1028 {
1029 if (copy_delete_attach(part, fp_in, fp_out, quoted_date))
1030 {
1031 return -1;
1032 }
1033 }
1034 }
1035 }
1036
1037 /* Copy the last parts */
1038 if (mutt_file_copy_bytes(fp_in, fp_out, b->offset + b->length - ftello(fp_in)))
1039 return -1;
1040
1041 return 0;
1042 }
1043
1044 /**
1045 * format_address_header - Write address headers to a buffer
1046 * @param[out] h Array of header strings
1047 * @param[in] al AddressList
1048 *
1049 * This function is the equivalent of mutt_addrlist_write_file(), but writes to
1050 * a buffer instead of writing to a stream. mutt_addrlist_write_file could be
1051 * re-used if we wouldn't store all the decoded headers in a huge array, first.
1052 *
1053 * TODO fix that.
1054 */
format_address_header(char ** h,struct AddressList * al)1055 static void format_address_header(char **h, struct AddressList *al)
1056 {
1057 char buf[8192];
1058 char cbuf[256];
1059 char c2buf[256];
1060 char *p = NULL;
1061 size_t linelen, buflen, plen;
1062
1063 linelen = mutt_str_len(*h);
1064 plen = linelen;
1065 buflen = linelen + 3;
1066
1067 mutt_mem_realloc(h, buflen);
1068 struct Address *first = TAILQ_FIRST(al);
1069 struct Address *a = NULL;
1070 TAILQ_FOREACH(a, al, entries)
1071 {
1072 *buf = '\0';
1073 *cbuf = '\0';
1074 *c2buf = '\0';
1075 const size_t l = mutt_addr_write(buf, sizeof(buf), a, false);
1076
1077 if (a != first && (linelen + l > 74))
1078 {
1079 strcpy(cbuf, "\n\t");
1080 linelen = l + 8;
1081 }
1082 else
1083 {
1084 if (a->mailbox)
1085 {
1086 strcpy(cbuf, " ");
1087 linelen++;
1088 }
1089 linelen += l;
1090 }
1091 if (!a->group && TAILQ_NEXT(a, entries) && TAILQ_NEXT(a, entries)->mailbox)
1092 {
1093 linelen++;
1094 buflen++;
1095 strcpy(c2buf, ",");
1096 }
1097
1098 const size_t cbuflen = mutt_str_len(cbuf);
1099 const size_t c2buflen = mutt_str_len(c2buf);
1100 buflen += l + cbuflen + c2buflen;
1101 mutt_mem_realloc(h, buflen);
1102 p = *h;
1103 strcat(p + plen, cbuf);
1104 plen += cbuflen;
1105 strcat(p + plen, buf);
1106 plen += l;
1107 strcat(p + plen, c2buf);
1108 plen += c2buflen;
1109 }
1110
1111 /* Space for this was allocated in the beginning of this function. */
1112 strcat(p + plen, "\n");
1113 }
1114
1115 /**
1116 * address_header_decode - Parse an email's headers
1117 * @param[out] h Array of header strings
1118 * @retval 0 Success
1119 * @retval 1 Failure
1120 */
address_header_decode(char ** h)1121 static int address_header_decode(char **h)
1122 {
1123 char *s = *h;
1124 size_t l;
1125 bool rp = false;
1126
1127 switch (tolower((unsigned char) *s))
1128 {
1129 case 'b':
1130 {
1131 if (!(l = mutt_istr_startswith(s, "bcc:")))
1132 return 0;
1133 break;
1134 }
1135 case 'c':
1136 {
1137 if (!(l = mutt_istr_startswith(s, "cc:")))
1138 return 0;
1139 break;
1140 }
1141 case 'f':
1142 {
1143 if (!(l = mutt_istr_startswith(s, "from:")))
1144 return 0;
1145 break;
1146 }
1147 case 'm':
1148 {
1149 if (!(l = mutt_istr_startswith(s, "mail-followup-to:")))
1150 return 0;
1151 break;
1152 }
1153 case 'r':
1154 {
1155 if ((l = mutt_istr_startswith(s, "return-path:")))
1156 {
1157 rp = true;
1158 break;
1159 }
1160 else if ((l = mutt_istr_startswith(s, "reply-to:")))
1161 {
1162 break;
1163 }
1164 return 0;
1165 }
1166 case 's':
1167 {
1168 if (!(l = mutt_istr_startswith(s, "sender:")))
1169 return 0;
1170 break;
1171 }
1172 case 't':
1173 {
1174 if (!(l = mutt_istr_startswith(s, "to:")))
1175 return 0;
1176 break;
1177 }
1178 default:
1179 return 0;
1180 }
1181
1182 struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
1183 mutt_addrlist_parse(&al, s + l);
1184 if (TAILQ_EMPTY(&al))
1185 return 0;
1186
1187 mutt_addrlist_to_local(&al);
1188 rfc2047_decode_addrlist(&al);
1189 struct Address *a = NULL;
1190 TAILQ_FOREACH(a, &al, entries)
1191 {
1192 if (a->personal)
1193 {
1194 mutt_str_dequote_comment(a->personal);
1195 }
1196 }
1197
1198 /* angle brackets for return path are mandated by RFC5322,
1199 * so leave Return-Path as-is */
1200 if (rp)
1201 *h = mutt_str_dup(s);
1202 else
1203 {
1204 *h = mutt_mem_calloc(1, l + 2);
1205 mutt_str_copy(*h, s, l + 1);
1206 format_address_header(h, &al);
1207 }
1208
1209 mutt_addrlist_clear(&al);
1210
1211 FREE(&s);
1212 return 1;
1213 }
1214