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