Nico Golde:
[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   int rc = 0;
508
509   if (flags & M_CM_PREFIX) {
510     if (option (OPTTEXTFLOWED))
511       strfcpy (prefix, ">", sizeof (prefix));
512     else
513       _mutt_make_string (prefix, sizeof (prefix), NONULL (Prefix), Context,
514                          hdr, 0);
515   }
516
517   if ((flags & M_CM_NOHEADER) == 0) {
518     if (flags & M_CM_PREFIX)
519       chflags |= CH_PREFIX;
520
521     else if (hdr->attach_del && (chflags & CH_UPDATE_LEN)) {
522       int new_lines;
523       long new_length = body->length;
524       char date[SHORT_STRING];
525
526       mutt_make_date (date, sizeof (date));
527       date[5] = date[str_len (date) - 1] = '\"';
528
529       /* Count the number of lines and bytes to be deleted */
530       fseek (fpin, body->offset, SEEK_SET);
531       new_lines = hdr->lines -
532         count_delete_lines (fpin, body, &new_length, str_len (date));
533
534       /* Copy the headers */
535       if (mutt_copy_header (fpin, hdr, fpout,
536                             chflags | CH_NOLEN | CH_NONEWLINE, NULL))
537         return -1;
538       fprintf (fpout, "Content-Length: %ld\n", new_length);
539       if (new_lines <= 0)
540         new_lines = 0;
541       else
542         fprintf (fpout, "Lines: %d\n\n", new_lines);
543       if (ferror (fpout) || feof (fpout))
544         return -1;
545       new_offset = ftell (fpout);
546
547       /* Copy the body */
548       fseek (fpin, body->offset, SEEK_SET);
549       if (copy_delete_attach (body, fpin, fpout, date))
550         return -1;
551
552 #ifdef DEBUG
553       {
554         long fail = ((ftell (fpout) - new_offset) - new_length);
555
556         if (fail) {
557           mutt_error ("The length calculation was wrong by %ld bytes", fail);
558           new_length += fail;
559           mutt_sleep (1);
560         }
561       }
562 #endif
563
564       /* Update original message if we are sync'ing a mailfolder */
565       if (flags & M_CM_UPDATE) {
566         hdr->attach_del = 0;
567         hdr->lines = new_lines;
568         body->offset = new_offset;
569
570         /* update the total size of the mailbox to reflect this deletion */
571         Context->size -= body->length - new_length;
572         /*
573          * if the message is visible, update the visible size of the mailbox
574          * as well.
575          */
576         if (Context->v2r[hdr->msgno] != -1)
577           Context->vsize -= body->length - new_length;
578
579         body->length = new_length;
580         mutt_free_body (&body->parts);
581       }
582
583       return 0;
584     }
585
586     if (mutt_copy_header (fpin, hdr, fpout, chflags,
587                           (chflags & CH_PREFIX) ? prefix : NULL) == -1)
588       return -1;
589
590     new_offset = ftell (fpout);
591   }
592
593   if (flags & M_CM_DECODE) {
594     /* now make a text/plain version of the message */
595     memset (&s, 0, sizeof (STATE));
596     s.fpin = fpin;
597     s.fpout = fpout;
598     if (flags & M_CM_PREFIX)
599       s.prefix = prefix;
600     if (flags & M_CM_DISPLAY)
601       s.flags |= M_DISPLAY;
602     if (flags & M_CM_PRINTING)
603       s.flags |= M_PRINTING;
604     if (flags & M_CM_WEED)
605       s.flags |= M_WEED;
606     if (flags & M_CM_CHARCONV)
607       s.flags |= M_CHARCONV;
608     if (flags & M_CM_REPLYING)
609       s.flags |= M_REPLYING;
610
611     if (WithCrypto && flags & M_CM_VERIFY)
612       s.flags |= M_VERIFY;
613
614     rc = mutt_body_handler (body, &s);
615   }
616   else if (WithCrypto
617            && (flags & M_CM_DECODE_CRYPT) && (hdr->security & ENCRYPT)) {
618     BODY *cur;
619     FILE *fp;
620
621     if ((WithCrypto & APPLICATION_PGP)
622         && (flags & M_CM_DECODE_PGP) && (hdr->security & APPLICATION_PGP) &&
623         hdr->content->type == TYPEMULTIPART) {
624       if (crypt_pgp_decrypt_mime (fpin, &fp, hdr->content, &cur))
625         return (-1);
626       fputs ("Mime-Version: 1.0\n", fpout);
627     }
628
629     if ((WithCrypto & APPLICATION_SMIME)
630         && (flags & M_CM_DECODE_SMIME) && (hdr->security & APPLICATION_SMIME)
631         && hdr->content->type == TYPEAPPLICATION) {
632       if (crypt_smime_decrypt_mime (fpin, &fp, hdr->content, &cur))
633         return (-1);
634     }
635
636     mutt_write_mime_header (cur, fpout);
637     fputc ('\n', fpout);
638
639     fseek (fp, cur->offset, 0);
640     if (mutt_copy_bytes (fp, fpout, cur->length) == -1) {
641       fclose (fp);
642       mutt_free_body (&cur);
643       return (-1);
644     }
645     mutt_free_body (&cur);
646     fclose (fp);
647   }
648   else {
649     fseek (fpin, body->offset, 0);
650     if (flags & M_CM_PREFIX) {
651       int c;
652       size_t bytes = body->length;
653
654       fputs (prefix, fpout);
655
656       while ((c = fgetc (fpin)) != EOF && bytes--) {
657         fputc (c, fpout);
658         if (c == '\n') {
659           fputs (prefix, fpout);
660         }
661       }
662     }
663     else if (mutt_copy_bytes (fpin, fpout, body->length) == -1)
664       return -1;
665   }
666
667   if ((flags & M_CM_UPDATE) && (flags & M_CM_NOHEADER) == 0
668       && new_offset != -1) {
669     body->offset = new_offset;
670     mutt_free_body (&body->parts);
671   }
672
673   return rc;
674 }
675
676 int
677 mutt_copy_message (FILE * fpout, CONTEXT * src, HEADER * hdr, int flags,
678                    int chflags)
679 {
680   MESSAGE *msg;
681   int r;
682
683   if ((msg = mx_open_message (src, hdr->msgno)) == NULL)
684     return -1;
685   if ((r =
686        _mutt_copy_message (fpout, msg->fp, hdr, hdr->content, flags,
687                            chflags)) == 0 && (ferror (fpout)
688                                               || feof (fpout))) {
689     debug_print (1, ("_mutt_copy_message failed to detect EOF!\n"));
690     r = -1;
691   }
692   mx_close_message (&msg);
693   return r;
694 }
695
696 /* appends a copy of the given message to a mailbox
697  *
698  * dest         destination mailbox
699  * fpin         where to get input
700  * src          source mailbox
701  * hdr          message being copied
702  * body         structure of message being copied
703  * flags        mutt_copy_message() flags
704  * chflags      mutt_copy_header() flags
705  */
706
707 int
708 _mutt_append_message (CONTEXT * dest, FILE * fpin, CONTEXT * src,
709                       HEADER * hdr, BODY * body, int flags, int chflags) {
710   char buf[STRING];
711   MESSAGE *msg;
712   int r;
713
714   fseek(fpin, hdr->offset, 0);
715   if (fgets (buf, sizeof (buf), fpin) == NULL)
716     return (-1);
717   if ((msg = mx_open_new_message (dest, hdr, is_from (buf, NULL, 0, NULL) ? 0 : M_ADD_FROM)) == NULL)
718     return (-1);
719   if (dest->magic == M_MBOX || dest->magic == M_MMDF)
720     chflags |= CH_FROM | CH_FORCE_FROM;
721   chflags |= (dest->magic == M_MAILDIR ? CH_NOSTATUS : CH_UPDATE);
722   r = _mutt_copy_message (msg->fp, fpin, hdr, body, flags, chflags);
723   if (mx_commit_message (msg, dest) != 0)
724     r = -1;
725
726   mx_close_message (&msg);
727   return r;
728 }
729
730 int
731 mutt_append_message (CONTEXT * dest, CONTEXT * src, HEADER * hdr, int cmflags,
732                      int chflags)
733 {
734   MESSAGE *msg;
735   int r;
736
737   if ((msg = mx_open_message (src, hdr->msgno)) == NULL)
738     return -1;
739   r =
740     _mutt_append_message (dest, msg->fp, src, hdr, hdr->content, cmflags,
741                           chflags);
742   mx_close_message (&msg);
743   return r;
744 }
745
746 /*
747  * This function copies a message body, while deleting _in_the_copy_
748  * any attachments which are marked for deletion.
749  * Nothing is changed in the original message -- this is left to the caller.
750  *
751  * The function will return 0 on success and -1 on failure.
752  */
753 static int copy_delete_attach (BODY * b, FILE * fpin, FILE * fpout,
754                                char *date)
755 {
756   BODY *part;
757
758   for (part = b->parts; part; part = part->next) {
759     if (part->deleted || part->parts) {
760       /* Copy till start of this part */
761       if (mutt_copy_bytes (fpin, fpout, part->hdr_offset - ftell (fpin)))
762         return -1;
763
764       if (part->deleted) {
765         fprintf (fpout,
766                  "Content-Type: message/external-body; access-type=x-mutt-deleted;\n"
767                  "\texpiration=%s; length=%ld\n"
768                  "\n", date + 5, part->length);
769         if (ferror (fpout))
770           return -1;
771
772         /* Copy the original mime headers */
773         if (mutt_copy_bytes (fpin, fpout, part->offset - ftell (fpin)))
774           return -1;
775
776         /* Skip the deleted body */
777         fseek (fpin, part->offset + part->length, SEEK_SET);
778       }
779       else {
780         if (copy_delete_attach (part, fpin, fpout, date))
781           return -1;
782       }
783     }
784   }
785
786   /* Copy the last parts */
787   if (mutt_copy_bytes (fpin, fpout, b->offset + b->length - ftell (fpin)))
788     return -1;
789
790   return 0;
791 }
792
793 /* 
794  * This function is the equivalent of mutt_write_address_list(),
795  * but writes to a buffer instead of writing to a stream.
796  * mutt_write_address_list could be re-used if we wouldn't store
797  * all the decoded headers in a huge array, first. 
798  *
799  * XXX - fix that. 
800  */
801
802 static void format_address_header (char **h, ADDRESS * a)
803 {
804   char buf[HUGE_STRING];
805   char cbuf[STRING];
806   char c2buf[STRING];
807
808   int l, linelen, buflen, count;
809
810   linelen = str_len (*h);
811   buflen = linelen + 3;
812
813
814   mem_realloc (h, buflen);
815   for (count = 0; a; a = a->next, count++) {
816     ADDRESS *tmp = a->next;
817
818     a->next = NULL;
819     *buf = *cbuf = *c2buf = '\0';
820     rfc822_write_address (buf, sizeof (buf), a, 0);
821     a->next = tmp;
822
823     l = str_len (buf);
824     if (count && linelen + l > 74) {
825       strcpy (cbuf, "\n\t");    /* __STRCPY_CHECKED__ */
826       linelen = l + 8;
827     }
828     else {
829       if (a->mailbox) {
830         strcpy (cbuf, " ");     /* __STRCPY_CHECKED__ */
831         linelen++;
832       }
833       linelen += l;
834     }
835     if (!a->group && a->next && a->next->mailbox) {
836       linelen++;
837       buflen++;
838       strcpy (c2buf, ",");      /* __STRCPY_CHECKED__ */
839     }
840
841     buflen += l + str_len (cbuf) + str_len (c2buf);
842     mem_realloc (h, buflen);
843     strcat (*h, cbuf);          /* __STRCAT_CHECKED__ */
844     strcat (*h, buf);           /* __STRCAT_CHECKED__ */
845     strcat (*h, c2buf);         /* __STRCAT_CHECKED__ */
846   }
847
848   /* Space for this was allocated in the beginning of this function. */
849   strcat (*h, "\n");            /* __STRCAT_CHECKED__ */
850 }
851
852 static int address_header_decode (char **h)
853 {
854   char *s = *h;
855   int l;
856
857   ADDRESS *a = NULL;
858
859   switch (tolower ((unsigned char) *s)) {
860   case 'r':
861     {
862       if (ascii_strncasecmp (s, "return-path:", 12) == 0) {
863         l = 12;
864         break;
865       }
866       else if (ascii_strncasecmp (s, "reply-to:", 9) == 0) {
867         l = 9;
868         break;
869       }
870       return 0;
871     }
872   case 'f':
873     {
874       if (ascii_strncasecmp (s, "from:", 5))
875         return 0;
876       l = 5;
877       break;
878     }
879   case 'c':
880     {
881       if (ascii_strncasecmp (s, "cc:", 3))
882         return 0;
883       l = 3;
884       break;
885
886     }
887   case 'b':
888     {
889       if (ascii_strncasecmp (s, "bcc:", 4))
890         return 0;
891       l = 4;
892       break;
893     }
894   case 's':
895     {
896       if (ascii_strncasecmp (s, "sender:", 7))
897         return 0;
898       l = 7;
899       break;
900     }
901   case 't':
902     {
903       if (ascii_strncasecmp (s, "to:", 3))
904         return 0;
905       l = 3;
906       break;
907     }
908   case 'm':
909     {
910       if (ascii_strncasecmp (s, "mail-followup-to:", 17))
911         return 0;
912       l = 17;
913       break;
914     }
915   default:
916     return 0;
917   }
918
919   if ((a = rfc822_parse_adrlist (a, s + l)) == NULL)
920     return 0;
921
922   mutt_addrlist_to_local (a);
923   rfc2047_decode_adrlist (a);
924
925   *h = mem_calloc (1, l + 2);
926
927   strfcpy (*h, s, l + 1);
928
929   format_address_header (h, a);
930
931   rfc822_free_address (&a);
932
933   mem_free (&s);
934   return 1;
935 }