3a010d4f1443e3b25cd94dfc879b82a3b2ea797c
[apps/madmutt.git] / copy.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000,2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <lib-mime/mime.h>
13
14 #include "mutt.h"
15 #include "handler.h"
16 #include "mx.h"
17 #include "copy.h"
18 #include <lib-crypt/crypt.h>
19 #include "mutt_idna.h"
20
21 static int address_header_decode (char **str);
22 static int copy_delete_attach (BODY * b, FILE * fpin, FILE * fpout,
23                                char *date);
24
25 /* Ok, the only reason for not merging this with mutt_copy_header()
26  * below is to avoid creating a HEADER structure in message_handler().
27  */
28 int
29 mutt_copy_hdr (FILE* in, FILE* out, off_t off_start, off_t off_end,
30                int flags, const char *prefix) {
31   int from = 0;
32   int this_is_from;
33   int ignore = 0;
34   char buf[STRING];             /* should be long enough to get most fields in one pass */
35   char *nl;
36   string_list_t *t;
37   char **headers;
38   int hdr_count;
39   int x;
40   char *this_one = NULL;
41   int error;
42   int curline = 0;
43
44   if (ftello (in) != off_start)
45     fseeko (in, off_start, 0);
46
47   buf[0] = '\n';
48   buf[1] = 0;
49
50   if ((flags &
51        (CH_REORDER | CH_WEED | CH_MIME | CH_DECODE | CH_PREFIX |
52         CH_WEED_DELIVERED)) == 0) {
53     /* Without these flags to complicate things
54      * we can do a more efficient line to line copying
55      */
56     while (ftello (in) < off_end) {
57       nl = strchr (buf, '\n');
58
59       if ((fgets (buf, sizeof (buf), in)) == NULL)
60         break;
61
62       /* Is it the begining of a header? */
63       if (nl && buf[0] != ' ' && buf[0] != '\t') {
64         ignore = 1;
65         if (!from && m_strncmp("From ", buf, 5) == 0) {
66           if ((flags & CH_FROM) == 0)
67             continue;
68           from = 1;
69         }
70         else if (flags & (CH_NOQFROM) &&
71                  ascii_strncasecmp (">From ", buf, 6) == 0)
72           continue;
73
74         else if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n'))
75           break;                /* end of header */
76
77         if ((flags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
78             (ascii_strncasecmp ("Status:", buf, 7) == 0 ||
79              ascii_strncasecmp ("X-Status:", buf, 9) == 0))
80           continue;
81         if ((flags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
82             (ascii_strncasecmp ("Content-Length:", buf, 15) == 0 ||
83              ascii_strncasecmp ("Lines:", buf, 6) == 0))
84           continue;
85         if ((flags & CH_UPDATE_REFS) &&
86             ascii_strncasecmp ("References:", buf, 11) == 0)
87           continue;
88         if ((flags & CH_UPDATE_IRT) &&
89             ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0)
90           continue;
91         ignore = 0;
92       }
93
94       if (!ignore && fputs (buf, out) == EOF)
95         return (-1);
96     }
97     return 0;
98   }
99
100   hdr_count = 1;
101   x = 0;
102   error = FALSE;
103
104   /* We are going to read and collect the headers in an array
105    * so we are able to do re-ordering.
106    * First count the number of entries in the array
107    */
108   if (flags & CH_REORDER) {
109     for (t = HeaderOrderList; t; t = t->next) {
110       hdr_count++;
111     }
112   }
113
114   headers = p_new(char *, hdr_count);
115
116   /* Read all the headers into the array */
117   while (ftello (in) < off_end) {
118     nl = strchr (buf, '\n');
119
120     /* Read a line */
121     if ((fgets (buf, sizeof (buf), in)) == NULL)
122       break;
123
124     /* Is it the begining of a header? */
125     if (nl && buf[0] != ' ' && buf[0] != '\t') {
126
127       /* set curline to 1 for To:/Cc:/Bcc: and 0 otherwise */
128       curline = (flags & CH_WEED) && (ascii_strncmp ("To:", buf, 3) == 0 ||
129                                       ascii_strncmp ("Cc:", buf, 3) == 0 ||
130                                       ascii_strncmp ("Bcc:", buf, 4) == 0);
131
132       /* Do we have anything pending? */
133       if (this_one) {
134         if (flags & CH_DECODE) {
135           if (!address_header_decode (&this_one))
136             rfc2047_decode (&this_one);
137         }
138
139         if (!headers[x])
140           headers[x] = this_one;
141         else {
142           p_realloc(&headers[x], m_strlen(headers[x]) + m_strlen(this_one) + 1);
143           strcat(headers[x], this_one);        /* __STRCAT_CHECKED__ */
144           p_delete(&this_one);
145         }
146
147         this_one = NULL;
148       }
149
150       ignore = 1;
151       this_is_from = 0;
152       if (!from && m_strncmp("From ", buf, 5) == 0) {
153         if ((flags & CH_FROM) == 0)
154           continue;
155         this_is_from = from = 1;
156       }
157       else if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n'))
158         break;                  /* end of header */
159
160       /* note: CH_FROM takes precedence over header weeding. */
161       if (!((flags & CH_FROM) && (flags & CH_FORCE_FROM) && this_is_from) &&
162           (flags & CH_WEED) &&
163           mutt_matches_ignore (buf, Ignore) &&
164           !mutt_matches_ignore (buf, UnIgnore))
165         continue;
166       if ((flags & CH_WEED_DELIVERED) &&
167           ascii_strncasecmp ("Delivered-To:", buf, 13) == 0)
168         continue;
169       if ((flags & (CH_UPDATE | CH_XMIT | CH_NOSTATUS)) &&
170           (ascii_strncasecmp ("Status:", buf, 7) == 0 ||
171            ascii_strncasecmp ("X-Status:", buf, 9) == 0))
172         continue;
173       if ((flags & (CH_UPDATE_LEN | CH_XMIT | CH_NOLEN)) &&
174           (ascii_strncasecmp ("Content-Length:", buf, 15) == 0 ||
175            ascii_strncasecmp ("Lines:", buf, 6) == 0))
176         continue;
177       if ((flags & CH_MIME) &&
178           ((ascii_strncasecmp ("content-", buf, 8) == 0 &&
179             (ascii_strncasecmp ("transfer-encoding:", buf + 8, 18) == 0 ||
180              ascii_strncasecmp ("type:", buf + 8, 5) == 0)) ||
181            ascii_strncasecmp ("mime-version:", buf, 13) == 0))
182         continue;
183       if ((flags & CH_UPDATE_REFS) &&
184           ascii_strncasecmp ("References:", buf, 11) == 0)
185         continue;
186       if ((flags & CH_UPDATE_IRT) &&
187           ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0)
188         continue;
189
190       /* Find x -- the array entry where this header is to be saved */
191       if (flags & CH_REORDER) {
192         for (t = HeaderOrderList, x = 0; (t); t = t->next, x++) {
193           if (!ascii_strncasecmp (buf, t->data, m_strlen(t->data))) {
194             break;
195           }
196         }
197       }
198
199       ignore = 0;
200     }                           /* If beginning of header */
201
202     if (!ignore) {
203       if (!this_one)
204         this_one = m_strdup(buf);
205       /* we do want to see all lines if this header doesn't feature
206        * abbreviations (curline is 0), $max_display_recips is 0 and
207        * while the number hasn't reached $max_display_recips yet */
208       else if (curline == 0 || MaxDispRecips == 0 || ++curline <= MaxDispRecips) {
209         p_realloc(&this_one, m_strlen(this_one) + m_strlen(buf) + 1);
210         strcat (this_one, buf); /* __STRCAT_CHECKED__ */
211       /* only for the first line which doesn't exeeds
212        * $max_display_recips: abbreviate it */
213       } else if (curline == MaxDispRecips+1) {
214         p_realloc(&this_one, m_strlen(this_one) + 5);
215         strcat (this_one, " ...");
216       }
217     }
218   }                             /* while (ftello (in) < off_end) */
219
220   /* Do we have anything pending?  -- XXX, same code as in above in the loop. */
221   if (this_one) {
222     if (flags & CH_DECODE) {
223       if (!address_header_decode (&this_one))
224         rfc2047_decode (&this_one);
225     }
226
227     if (!headers[x])
228       headers[x] = this_one;
229     else {
230       p_realloc(&headers[x], m_strlen(headers[x]) + m_strlen(this_one) + 1);
231       strcat (headers[x], this_one);    /* __STRCAT_CHECKED__ */
232       p_delete(&this_one);
233     }
234
235     this_one = NULL;
236   }
237
238   /* Now output the headers in order */
239   for (x = 0; x < hdr_count; x++) {
240     if (headers[x]) {
241 #if 0
242       if (flags & CH_DECODE)
243         rfc2047_decode (&headers[x]);
244 #endif
245
246       /* We couldn't do the prefixing when reading because RFC 2047
247        * decoding may have concatenated lines.
248        */
249       if (flags & CH_PREFIX) {
250         char *ch = headers[x];
251         int print_prefix = 1;
252
253         while (*ch) {
254           if (print_prefix) {
255             if (fputs (prefix, out) == EOF) {
256               error = TRUE;
257               break;
258             }
259             print_prefix = 0;
260           }
261
262           if (*ch == '\n' && ch[1])
263             print_prefix = 1;
264
265           if (putc (*ch++, out) == EOF) {
266             error = TRUE;
267             break;
268           }
269         }
270         if (error)
271           break;
272       }
273       else {
274         if (fputs (headers[x], out) == EOF) {
275           error = TRUE;
276           break;
277         }
278       }
279     }
280   }
281
282   /* Free in a separate loop to be sure that all headers are freed
283    * in case of error. */
284   for (x = 0; x < hdr_count; x++)
285     p_delete(&headers[x]);
286   p_delete(&headers);
287
288   if (error)
289     return (-1);
290   return (0);
291 }
292
293 /* flags
294         CH_DECODE       RFC2047 header decoding
295         CH_FROM         retain the "From " message separator
296         CH_FORCE_FROM   give CH_FROM precedence over CH_WEED
297         CH_MIME         ignore MIME fields
298         CH_NOLEN        don't write Content-Length: and Lines:
299         CH_NONEWLINE    don't output a newline after the header
300         CH_NOSTATUS     ignore the Status: and X-Status:
301         CH_PREFIX       quote header with $indent_str
302         CH_REORDER      output header in order specified by `hdr_order'
303         CH_TXTPLAIN     generate text/plain MIME headers [hack alert.]
304         CH_UPDATE       write new Status: and X-Status:
305         CH_UPDATE_LEN   write new Content-Length: and Lines:
306         CH_XMIT         ignore Lines: and Content-Length:
307         CH_WEED         do header weeding
308         CH_NOQFROM      ignore ">From " line
309         CH_UPDATE_IRT   update the In-Reply-To: header
310         CH_UPDATE_REFS  update the References: header
311
312    prefix
313         string to use if CH_PREFIX is set
314  */
315
316 int
317 mutt_copy_header (FILE * in, HEADER * h, FILE * out, int flags,
318                   const char *prefix)
319 {
320   char buffer[SHORT_STRING];
321
322   if (h->env)
323     flags |= (h->env->irt_changed ? CH_UPDATE_IRT : 0) |
324       (h->env->refs_changed ? CH_UPDATE_REFS : 0);
325
326   if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) ==
327       -1)
328     return (-1);
329
330   if (flags & CH_TXTPLAIN) {
331     char chsbuf[SHORT_STRING];
332
333     fputs ("MIME-Version: 1.0\n", out);
334     fputs ("Content-Transfer-Encoding: 8bit\n", out);
335     fputs ("Content-Type: text/plain; charset=", out);
336     charset_canonicalize(chsbuf, sizeof (chsbuf), Charset);
337     rfc822_strcpy(buffer, sizeof(buffer), chsbuf, MimeSpecials);
338     fputs (buffer, out);
339     fputc ('\n', out);
340
341     if (ferror (out) != 0 || feof (out) != 0)
342       return -1;
343
344   }
345
346   if (flags & CH_UPDATE) {
347     if ((flags & CH_NOSTATUS) == 0) {
348       if (h->env->irt_changed && h->env->in_reply_to) {
349         string_list_t *listp = h->env->in_reply_to;
350
351         if (fputs ("In-Reply-To: ", out) == EOF)
352           return (-1);
353
354         for (; listp; listp = listp->next)
355           if ((fputs (listp->data, out) == EOF) || (fputc (' ', out) == EOF))
356             return (-1);
357
358         if (fputc ('\n', out) == EOF)
359           return (-1);
360       }
361
362       if (h->env->refs_changed && h->env->references) {
363         string_list_t *listp = h->env->references, *refs = NULL, *t;
364
365         if (fputs ("References: ", out) == EOF)
366           return (-1);
367
368         /* Mutt stores references in reverse order, thus we create
369          * a reordered refs list that we can put in the headers */
370         for (; listp; listp = listp->next, refs = t) {
371           t = p_new(string_list_t, 1);
372           t->data = listp->data;
373           t->next = refs;
374         }
375
376         for (; refs; refs = refs->next)
377           if ((fputs (refs->data, out) == EOF) || (fputc (' ', out) == EOF))
378             return (-1);
379
380         /* clearing refs from memory */
381         for (t = refs; refs; refs = t->next, t = refs)
382           p_delete(&refs);
383
384         if (fputc ('\n', out) == EOF)
385           return (-1);
386       }
387
388       if (h->old || h->read) {
389         if (fputs ("Status: ", out) == EOF)
390           return (-1);
391
392         if (h->read) {
393           if (fputs ("RO", out) == EOF)
394             return (-1);
395         }
396         else if (h->old) {
397           if (fputc ('O', out) == EOF)
398             return (-1);
399         }
400
401         if (fputc ('\n', out) == EOF)
402           return (-1);
403       }
404
405       if (h->flagged || h->replied) {
406         if (fputs ("X-Status: ", out) == EOF)
407           return (-1);
408
409         if (h->replied) {
410           if (fputc ('A', out) == EOF)
411             return (-1);
412         }
413
414         if (h->flagged) {
415           if (fputc ('F', out) == EOF)
416             return (-1);
417         }
418
419         if (fputc ('\n', out) == EOF)
420           return (-1);
421       }
422     }
423   }
424
425   if (flags & CH_UPDATE_LEN && (flags & CH_NOLEN) == 0) {
426     fprintf (out, "Content-Length: %lld\n", h->content->length);
427     if (h->lines != 0 || h->content->length == 0)
428       fprintf (out, "Lines: %d\n", h->lines);
429   }
430
431   if ((flags & CH_NONEWLINE) == 0) {
432     if (flags & CH_PREFIX)
433       fputs (prefix, out);
434     if (fputc ('\n', out) == EOF)       /* add header terminator */
435       return (-1);
436   }
437
438   if (ferror (out) || feof (out))
439     return -1;
440
441   return (0);
442 }
443
444 /* Count the number of lines and bytes to be deleted in this body*/
445 static int count_delete_lines (FILE * fp, BODY * b, off_t *length,
446                                size_t datelen)
447 {
448   int dellines = 0;
449   long l;
450   int ch;
451
452   if (b->deleted) {
453     fseeko (fp, b->offset, SEEK_SET);
454     for (l = b->length; l; l--) {
455       ch = getc (fp);
456       if (ch == EOF)
457         break;
458       if (ch == '\n')
459         dellines++;
460     }
461     dellines -= 3;
462     *length -= b->length - (84 + datelen);
463     /* Count the number of digits exceeding the first one to write the size */
464     for (l = 10; b->length >= l; l *= 10)
465       (*length)++;
466   }
467   else {
468     for (b = b->parts; b; b = b->next)
469       dellines += count_delete_lines (fp, b, length, datelen);
470   }
471   return dellines;
472 }
473
474 /* make a copy of a message
475  * 
476  * fpout        where to write output
477  * fpin         where to get input
478  * hdr          header of message being copied
479  * body         structure of message being copied
480  * flags
481  *      M_CM_NOHEADER   don't copy header
482  *      M_CM_PREFIX     quote header and body
483  *      M_CM_DECODE     decode message body to text/plain
484  *      M_CM_DISPLAY    displaying output to the user
485  *      M_CM_PRINTING   printing the message
486  *      M_CM_UPDATE     update structures in memory after syncing
487  *      M_CM_DECODE_PGP used for decoding PGP messages
488  *      M_CM_CHARCONV   perform character set conversion 
489  * chflags      flags to mutt_copy_header()
490  */
491
492 int
493 _mutt_copy_message (FILE * fpout, FILE * fpin, HEADER * hdr, BODY * body,
494                     int flags, int chflags)
495 {
496   char prefix[SHORT_STRING];
497   STATE s;
498   off_t new_offset = -1;
499   int rc = 0;
500
501   if (flags & M_CM_PREFIX) {
502     if (option (OPTTEXTFLOWED))
503       m_strcpy(prefix, sizeof(prefix), ">");
504     else
505       _mutt_make_string (prefix, sizeof (prefix), NONULL (Prefix), Context,
506                          hdr, 0);
507   }
508
509   if ((flags & M_CM_NOHEADER) == 0) {
510     if (flags & M_CM_PREFIX)
511       chflags |= CH_PREFIX;
512
513     else if (hdr->attach_del && (chflags & CH_UPDATE_LEN)) {
514       int new_lines;
515       off_t new_length = body->length;
516       char date[SHORT_STRING];
517
518       mutt_make_date (date, sizeof (date));
519       date[5] = date[m_strlen(date) - 1] = '\"';
520
521       /* Count the number of lines and bytes to be deleted */
522       fseeko (fpin, body->offset, SEEK_SET);
523       new_lines = hdr->lines -
524         count_delete_lines (fpin, body, &new_length, m_strlen(date));
525
526       /* Copy the headers */
527       if (mutt_copy_header (fpin, hdr, fpout,
528                             chflags | CH_NOLEN | CH_NONEWLINE, NULL))
529         return -1;
530       fprintf (fpout, "Content-Length: %lld\n", new_length);
531       if (new_lines <= 0)
532         new_lines = 0;
533       else
534         fprintf (fpout, "Lines: %d\n\n", new_lines);
535       if (ferror (fpout) || feof (fpout))
536         return -1;
537       new_offset = ftello (fpout);
538
539       /* Copy the body */
540       fseeko (fpin, body->offset, SEEK_SET);
541       if (copy_delete_attach (body, fpin, fpout, date))
542         return -1;
543
544       /* Update original message if we are sync'ing a mailfolder */
545       if (flags & M_CM_UPDATE) {
546         hdr->attach_del = 0;
547         hdr->lines = new_lines;
548         body->offset = new_offset;
549
550         /* update the total size of the mailbox to reflect this deletion */
551         Context->size -= body->length - new_length;
552         /*
553          * if the message is visible, update the visible size of the mailbox
554          * as well.
555          */
556         if (Context->v2r[hdr->msgno] != -1)
557           Context->vsize -= body->length - new_length;
558
559         body->length = new_length;
560         body_list_wipe(&body->parts);
561       }
562
563       return 0;
564     }
565
566     if (mutt_copy_header (fpin, hdr, fpout, chflags,
567                           (chflags & CH_PREFIX) ? prefix : NULL) == -1)
568       return -1;
569
570     new_offset = ftello (fpout);
571   }
572
573   if (flags & M_CM_DECODE) {
574     /* now make a text/plain version of the message */
575     p_clear(&s, 1);
576     s.fpin = fpin;
577     s.fpout = fpout;
578     if (flags & M_CM_PREFIX)
579       s.prefix = prefix;
580     if (flags & M_CM_DISPLAY)
581       s.flags |= M_DISPLAY;
582     if (flags & M_CM_PRINTING)
583       s.flags |= M_PRINTING;
584     if (flags & M_CM_WEED)
585       s.flags |= M_WEED;
586     if (flags & M_CM_CHARCONV)
587       s.flags |= M_CHARCONV;
588     if (flags & M_CM_REPLYING)
589       s.flags |= M_REPLYING;
590
591     if (flags & M_CM_VERIFY)
592       s.flags |= M_VERIFY;
593
594     rc = mutt_body_handler (body, &s);
595   }
596   else if ((flags & M_CM_DECODE_CRYPT) && (hdr->security & ENCRYPT)) {
597     BODY *cur;
598     FILE *fp;
599
600     if ((flags & M_CM_DECODE_PGP) && (hdr->security & APPLICATION_PGP) &&
601         hdr->content->type == TYPEMULTIPART) {
602       if (crypt_pgp_decrypt_mime (fpin, &fp, hdr->content, &cur))
603         return (-1);
604       fputs ("MIME-Version: 1.0\n", fpout);
605     }
606
607     if ((flags & M_CM_DECODE_SMIME) && (hdr->security & APPLICATION_SMIME)
608         && hdr->content->type == TYPEAPPLICATION) {
609       if (crypt_smime_decrypt_mime (fpin, &fp, hdr->content, &cur))
610         return (-1);
611     }
612
613     mutt_write_mime_header (cur, fpout);
614     fputc ('\n', fpout);
615
616     fseeko (fp, cur->offset, 0);
617     if (mutt_copy_bytes (fp, fpout, cur->length) == -1) {
618       fclose (fp);
619       body_list_wipe(&cur);
620       return (-1);
621     }
622     body_list_wipe(&cur);
623     fclose (fp);
624   }
625   else {
626     fseeko (fpin, body->offset, 0);
627     if (flags & M_CM_PREFIX) {
628       int c;
629       size_t bytes = body->length;
630
631       fputs (prefix, fpout);
632
633       while ((c = fgetc (fpin)) != EOF && bytes--) {
634         fputc (c, fpout);
635         if (c == '\n') {
636           fputs (prefix, fpout);
637         }
638       }
639     }
640     else if (mutt_copy_bytes (fpin, fpout, body->length) == -1)
641       return -1;
642   }
643
644   if ((flags & M_CM_UPDATE) && (flags & M_CM_NOHEADER) == 0
645       && new_offset != -1) {
646     body->offset = new_offset;
647     body_list_wipe(&body->parts);
648   }
649
650   return rc;
651 }
652
653 int
654 mutt_copy_message (FILE * fpout, CONTEXT * src, HEADER * hdr, int flags,
655                    int chflags)
656 {
657   MESSAGE *msg;
658   int r;
659
660   if ((msg = mx_open_message (src, hdr->msgno)) == NULL)
661     return -1;
662   if ((r =
663        _mutt_copy_message (fpout, msg->fp, hdr, hdr->content, flags,
664                            chflags)) == 0 && (ferror (fpout)
665                                               || feof (fpout))) {
666     r = -1;
667   }
668   mx_close_message (&msg);
669   return r;
670 }
671
672 /* appends a copy of the given message to a mailbox
673  *
674  * dest         destination mailbox
675  * fpin         where to get input
676  * src          source mailbox
677  * hdr          message being copied
678  * body         structure of message being copied
679  * flags        mutt_copy_message() flags
680  * chflags      mutt_copy_header() flags
681  */
682
683 int
684 _mutt_append_message (CONTEXT * dest, FILE * fpin, CONTEXT * src __attribute__ ((unused)),
685                       HEADER * hdr, BODY * body, int flags, int chflags) {
686   char buf[STRING];
687   MESSAGE *msg;
688   int r;
689
690   fseeko(fpin, hdr->offset, 0);
691   if (fgets (buf, sizeof (buf), fpin) == NULL)
692     return (-1);
693   if ((msg = mx_open_new_message (dest, hdr, is_from (buf, NULL, 0, NULL) ? 0 : M_ADD_FROM)) == NULL)
694     return (-1);
695   if (dest->magic == M_MBOX || dest->magic == M_MMDF)
696     chflags |= CH_FROM | CH_FORCE_FROM;
697   chflags |= (dest->magic == M_MAILDIR ? CH_NOSTATUS : CH_UPDATE);
698   r = _mutt_copy_message (msg->fp, fpin, hdr, body, flags, chflags);
699   if (mx_commit_message (msg, dest) != 0)
700     r = -1;
701
702   mx_close_message (&msg);
703   return r;
704 }
705
706 int
707 mutt_append_message (CONTEXT * dest, CONTEXT * src, HEADER * hdr, int cmflags,
708                      int chflags)
709 {
710   MESSAGE *msg;
711   int r;
712
713   if ((msg = mx_open_message (src, hdr->msgno)) == NULL)
714     return -1;
715   r =
716     _mutt_append_message (dest, msg->fp, src, hdr, hdr->content, cmflags,
717                           chflags);
718   mx_close_message (&msg);
719   return r;
720 }
721
722 /*
723  * This function copies a message body, while deleting _in_the_copy_
724  * any attachments which are marked for deletion.
725  * Nothing is changed in the original message -- this is left to the caller.
726  *
727  * The function will return 0 on success and -1 on failure.
728  */
729 static int copy_delete_attach (BODY * b, FILE * fpin, FILE * fpout,
730                                char *date)
731 {
732   BODY *part;
733
734   for (part = b->parts; part; part = part->next) {
735     if (part->deleted || part->parts) {
736       /* Copy till start of this part */
737       if (mutt_copy_bytes (fpin, fpout, part->hdr_offset - ftello (fpin)))
738         return -1;
739
740       if (part->deleted) {
741         fprintf (fpout,
742                  "Content-Type: message/external-body; access-type=x-mutt-deleted;\n"
743                  "\texpiration=%s; length=%lld\n"
744                  "\n", date + 5, part->length);
745         if (ferror (fpout))
746           return -1;
747
748         /* Copy the original mime headers */
749         if (mutt_copy_bytes (fpin, fpout, part->offset - ftello (fpin)))
750           return -1;
751
752         /* Skip the deleted body */
753         fseeko (fpin, part->offset + part->length, SEEK_SET);
754       }
755       else {
756         if (copy_delete_attach (part, fpin, fpout, date))
757           return -1;
758       }
759     }
760   }
761
762   /* Copy the last parts */
763   if (mutt_copy_bytes (fpin, fpout, b->offset + b->length - ftello (fpin)))
764     return -1;
765
766   return 0;
767 }
768
769 /* 
770  * This function is the equivalent of mutt_write_address_list(),
771  * but writes to a buffer instead of writing to a stream.
772  * mutt_write_address_list could be re-used if we wouldn't store
773  * all the decoded headers in a huge array, first. 
774  *
775  * XXX - fix that. 
776  */
777
778 static void format_address_header (char **h, address_t * a)
779 {
780   char buf[HUGE_STRING];
781   char cbuf[STRING];
782   char c2buf[STRING];
783
784   int l, linelen, buflen, count;
785
786   linelen = m_strlen(*h);
787   buflen = linelen + 3;
788
789
790   p_realloc(h, buflen);
791   for (count = 0; a; a = a->next, count++) {
792     address_t *tmp = a->next;
793
794     a->next = NULL;
795     *buf = *cbuf = *c2buf = '\0';
796     rfc822_write_address (buf, sizeof (buf), a, 0);
797     a->next = tmp;
798
799     l = m_strlen(buf);
800     if (count && linelen + l > 74) {
801       strcpy (cbuf, "\n\t");    /* __STRCPY_CHECKED__ */
802       linelen = l + 8;
803     }
804     else {
805       if (a->mailbox) {
806         strcpy (cbuf, " ");     /* __STRCPY_CHECKED__ */
807         linelen++;
808       }
809       linelen += l;
810     }
811     if (!a->group && a->next && a->next->mailbox) {
812       linelen++;
813       buflen++;
814       strcpy (c2buf, ",");      /* __STRCPY_CHECKED__ */
815     }
816
817     buflen += l + m_strlen(cbuf) + m_strlen(c2buf);
818     p_realloc(h, buflen);
819     strcat (*h, cbuf);          /* __STRCAT_CHECKED__ */
820     strcat (*h, buf);           /* __STRCAT_CHECKED__ */
821     strcat (*h, c2buf);         /* __STRCAT_CHECKED__ */
822   }
823
824   /* Space for this was allocated in the beginning of this function. */
825   strcat (*h, "\n");            /* __STRCAT_CHECKED__ */
826 }
827
828 static int address_header_decode (char **h)
829 {
830   char *s = *h;
831   int l;
832
833   address_t *a = NULL;
834
835   switch (tolower ((unsigned char) *s)) {
836   case 'r':
837     {
838       if (ascii_strncasecmp (s, "return-path:", 12) == 0) {
839         l = 12;
840         break;
841       }
842       else if (ascii_strncasecmp (s, "reply-to:", 9) == 0) {
843         l = 9;
844         break;
845       }
846       return 0;
847     }
848   case 'f':
849     {
850       if (ascii_strncasecmp (s, "from:", 5))
851         return 0;
852       l = 5;
853       break;
854     }
855   case 'c':
856     {
857       if (ascii_strncasecmp (s, "cc:", 3))
858         return 0;
859       l = 3;
860       break;
861
862     }
863   case 'b':
864     {
865       if (ascii_strncasecmp (s, "bcc:", 4))
866         return 0;
867       l = 4;
868       break;
869     }
870   case 's':
871     {
872       if (ascii_strncasecmp (s, "sender:", 7))
873         return 0;
874       l = 7;
875       break;
876     }
877   case 't':
878     {
879       if (ascii_strncasecmp (s, "to:", 3))
880         return 0;
881       l = 3;
882       break;
883     }
884   case 'm':
885     {
886       if (ascii_strncasecmp (s, "mail-followup-to:", 17))
887         return 0;
888       l = 17;
889       break;
890     }
891   default:
892     return 0;
893   }
894
895   if ((a = rfc822_parse_adrlist (a, s + l)) == NULL)
896     return 0;
897
898   mutt_addrlist_to_local (a);
899   rfc2047_decode_adrlist (a);
900
901   *h = p_dupstr(s, l + 1);
902
903   format_address_header (h, a);
904
905   address_list_wipe(&a);
906
907   p_delete(&s);
908   return 1;
909 }