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