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(&quoted_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(&quoted_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