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