Lay off the ground of our very efficient event loop.
[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   return error ? -1 : 0;
317 }
318
319 /* flags
320         CH_DECODE       RFC2047 header decoding
321         CH_FROM         retain the "From " message separator
322         CH_FORCE_FROM   give CH_FROM precedence over CH_WEED
323         CH_MIME         ignore MIME fields
324         CH_NOLEN        don't write Content-Length: and Lines:
325         CH_NONEWLINE    don't output a newline after the header
326         CH_NOSTATUS     ignore the Status: and X-Status:
327         CH_PREFIX       quote header with $indent_str
328         CH_REORDER      output header in order specified by `hdr_order'
329         CH_TXTPLAIN     generate text/plain MIME headers [hack alert.]
330         CH_UPDATE       write new Status: and X-Status:
331         CH_UPDATE_LEN   write new Content-Length: and Lines:
332         CH_XMIT         ignore Lines: and Content-Length:
333         CH_WEED         do header weeding
334         CH_NOQFROM      ignore ">From " line
335         CH_UPDATE_IRT   update the In-Reply-To: header
336         CH_UPDATE_REFS  update the References: header
337
338    prefix
339         string to use if CH_PREFIX is set
340  */
341
342 int
343 mutt_copy_header (FILE * in, HEADER * h, FILE * out, int flags,
344                   const char *prefix)
345 {
346   char buffer[STRING];
347
348   if (h->env)
349     flags |= (h->env->irt_changed ? CH_UPDATE_IRT : 0) |
350       (h->env->refs_changed ? CH_UPDATE_REFS : 0);
351
352   if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) ==
353       -1)
354     return -1;
355
356   if (flags & CH_TXTPLAIN) {
357     char chsbuf[STRING];
358
359     fputs ("MIME-Version: 1.0\n", out);
360     fputs ("Content-Transfer-Encoding: 8bit\n", out);
361     fputs ("Content-Type: text/plain; charset=", out);
362     charset_canonicalize(chsbuf, sizeof (chsbuf), mod_cset.charset);
363     rfc822_strcpy(buffer, sizeof(buffer), chsbuf, MimeSpecials);
364     fputs (buffer, out);
365     fputc ('\n', out);
366
367     if (ferror (out) != 0 || feof (out) != 0)
368       return -1;
369
370   }
371
372   if (flags & CH_UPDATE) {
373     if ((flags & CH_NOSTATUS) == 0) {
374       if (h->env->irt_changed && h->env->in_reply_to) {
375         string_list_t *listp = h->env->in_reply_to;
376
377         if (fputs ("In-Reply-To: ", out) == EOF)
378           return -1;
379
380         for (; listp; listp = listp->next)
381           if ((fputs (listp->data, out) == EOF) || (fputc (' ', out) == EOF))
382             return -1;
383
384         if (fputc ('\n', out) == EOF)
385           return -1;
386       }
387
388       if (h->env->refs_changed && h->env->references) {
389         string_list_t *listp = h->env->references, *refs = NULL, *t;
390
391         if (fputs ("References: ", out) == EOF)
392           return -1;
393
394         /* Mutt stores references in reverse order, thus we create
395          * a reordered refs list that we can put in the headers */
396         for (; listp; listp = listp->next, refs = t) {
397           t = p_new(string_list_t, 1);
398           t->data = listp->data;
399           t->next = refs;
400         }
401
402         for (; refs; refs = refs->next)
403           if ((fputs (refs->data, out) == EOF) || (fputc (' ', out) == EOF))
404             return -1;
405
406         /* clearing refs from memory */
407         for (t = refs; refs; refs = t->next, t = refs)
408           p_delete(&refs);
409
410         if (fputc ('\n', out) == EOF)
411           return -1;
412       }
413
414       if (h->old || h->read) {
415         if (fputs ("Status: ", out) == EOF)
416           return -1;
417
418         if (h->read) {
419           if (fputs ("RO", out) == EOF)
420             return -1;
421         }
422         else if (h->old) {
423           if (fputc ('O', out) == EOF)
424             return -1;
425         }
426
427         if (fputc ('\n', out) == EOF)
428           return -1;
429       }
430
431       if (h->flagged || h->replied) {
432         if (fputs ("X-Status: ", out) == EOF)
433           return -1;
434
435         if (h->replied) {
436           if (fputc ('A', out) == EOF)
437             return -1;
438         }
439
440         if (h->flagged) {
441           if (fputc ('F', out) == EOF)
442             return -1;
443         }
444
445         if (fputc ('\n', out) == EOF)
446           return -1;
447       }
448     }
449   }
450
451   if (flags & CH_UPDATE_LEN && (flags & CH_NOLEN) == 0) {
452     fprintf (out, "Content-Length: %zu\n", h->content->length);
453     if (h->lines != 0 || h->content->length == 0)
454       fprintf (out, "Lines: %d\n", h->lines);
455   }
456
457   if ((flags & CH_NONEWLINE) == 0) {
458     if (flags & CH_PREFIX)
459       fputs (prefix, out);
460     if (fputc ('\n', out) == EOF)       /* add header terminator */
461       return -1;
462   }
463
464   if (ferror (out) || feof (out))
465     return -1;
466
467   return 0;
468 }
469
470 /* Count the number of lines and bytes to be deleted in this body*/
471 static int count_delete_lines (FILE * fp, BODY * b, off_t *length,
472                                size_t datelen)
473 {
474   int dellines = 0;
475   long l;
476   int ch;
477
478   if (b->deleted) {
479     fseeko (fp, b->offset, SEEK_SET);
480     for (l = b->length; l; l--) {
481       ch = getc (fp);
482       if (ch == EOF)
483         break;
484       if (ch == '\n')
485         dellines++;
486     }
487     dellines -= 3;
488     *length -= b->length - (84 + datelen);
489     /* Count the number of digits exceeding the first one to write the size */
490     for (l = 10; b->length >= l; l *= 10)
491       (*length)++;
492   }
493   else {
494     for (b = b->parts; b; b = b->next)
495       dellines += count_delete_lines (fp, b, length, datelen);
496   }
497   return dellines;
498 }
499
500 /* make a copy of a message
501  * 
502  * fpout        where to write output
503  * fpin         where to get input
504  * hdr          header of message being copied
505  * body         structure of message being copied
506  * flags
507  *      M_CM_NOHEADER   don't copy header
508  *      M_CM_PREFIX     quote header and body
509  *      M_CM_DECODE     decode message body to text/plain
510  *      M_CM_DISPLAY    displaying output to the user
511  *      M_CM_PRINTING   printing the message
512  *      M_CM_UPDATE     update structures in memory after syncing
513  *      M_CM_DECODE_PGP used for decoding PGP messages
514  *      M_CM_CHARCONV   perform character set conversion 
515  * chflags      flags to mutt_copy_header()
516  */
517
518 int
519 _mutt_copy_message (FILE * fpout, FILE * fpin, HEADER * hdr, BODY * body,
520                     int flags, int chflags)
521 {
522   char prefix[STRING];
523   STATE s;
524   off_t new_offset = -1;
525   int rc = 0;
526
527   if (flags & M_CM_PREFIX) {
528     if (option (OPTTEXTFLOWED))
529       m_strcpy(prefix, sizeof(prefix), ">");
530     else
531       _mutt_make_string (prefix, sizeof (prefix), NONULL (Prefix), Context,
532                          hdr, 0);
533   }
534
535   if ((flags & M_CM_NOHEADER) == 0) {
536     if (flags & M_CM_PREFIX)
537       chflags |= CH_PREFIX;
538
539     else if (hdr->attach_del && (chflags & CH_UPDATE_LEN)) {
540       int new_lines;
541       off_t new_length = body->length;
542       char date[STRING];
543
544       mutt_make_date (date, sizeof (date));
545       date[5] = date[m_strlen(date) - 1] = '\"';
546
547       /* Count the number of lines and bytes to be deleted */
548       fseeko (fpin, body->offset, SEEK_SET);
549       new_lines = hdr->lines -
550         count_delete_lines (fpin, body, &new_length, m_strlen(date));
551
552       /* Copy the headers */
553       if (mutt_copy_header (fpin, hdr, fpout,
554                             chflags | CH_NOLEN | CH_NONEWLINE, NULL))
555         return -1;
556       fprintf (fpout, "Content-Length: %zu\n", new_length);
557       if (new_lines <= 0)
558         new_lines = 0;
559       else
560         fprintf (fpout, "Lines: %d\n\n", new_lines);
561       if (ferror (fpout) || feof (fpout))
562         return -1;
563       new_offset = ftello (fpout);
564
565       /* Copy the body */
566       fseeko (fpin, body->offset, SEEK_SET);
567       if (copy_delete_attach (body, fpin, fpout, date))
568         return -1;
569
570       /* Update original message if we are sync'ing a mailfolder */
571       if (flags & M_CM_UPDATE) {
572         hdr->attach_del = 0;
573         hdr->lines = new_lines;
574         body->offset = new_offset;
575
576         /* update the total size of the mailbox to reflect this deletion */
577         Context->size -= body->length - new_length;
578         /*
579          * if the message is visible, update the visible size of the mailbox
580          * as well.
581          */
582         if (Context->v2r[hdr->msgno] != -1)
583           Context->vsize -= body->length - new_length;
584
585         body->length = new_length;
586         body_list_wipe(&body->parts);
587       }
588
589       return 0;
590     }
591
592     if (mutt_copy_header (fpin, hdr, fpout, chflags,
593                           (chflags & CH_PREFIX) ? prefix : NULL) == -1)
594       return -1;
595
596     new_offset = ftello (fpout);
597   }
598
599   if (flags & M_CM_DECODE) {
600     /* now make a text/plain version of the message */
601     p_clear(&s, 1);
602     s.fpin = fpin;
603     s.fpout = fpout;
604     if (flags & M_CM_PREFIX)
605       s.prefix = prefix;
606     if (flags & M_CM_DISPLAY)
607       s.flags |= M_DISPLAY;
608     if (flags & M_CM_PRINTING)
609       s.flags |= M_PRINTING;
610     if (flags & M_CM_WEED)
611       s.flags |= M_WEED;
612     if (flags & M_CM_CHARCONV)
613       s.flags |= M_CHARCONV;
614     if (flags & M_CM_REPLYING)
615       s.flags |= M_REPLYING;
616
617     if (flags & M_CM_VERIFY)
618       s.flags |= M_VERIFY;
619
620     rc = mutt_body_handler (body, &s);
621   }
622   else if ((flags & M_CM_DECODE_CRYPT) && (hdr->security & ENCRYPT)) {
623     BODY *cur;
624     FILE *fp;
625
626     if ((flags & M_CM_DECODE_PGP) && (hdr->security & APPLICATION_PGP) &&
627         hdr->content->type == TYPEMULTIPART) {
628       if (crypt_pgp_decrypt_mime (fpin, &fp, hdr->content, &cur))
629         return -1;
630       fputs ("MIME-Version: 1.0\n", fpout);
631     }
632
633     if ((flags & M_CM_DECODE_SMIME) && (hdr->security & APPLICATION_SMIME)
634         && hdr->content->type == TYPEAPPLICATION) {
635       if (crypt_smime_decrypt_mime (fpin, &fp, hdr->content, &cur))
636         return -1;
637     }
638
639     mutt_write_mime_header (cur, fpout);
640     fputc ('\n', fpout);
641
642     fseeko (fp, cur->offset, 0);
643     if (mutt_copy_bytes (fp, fpout, cur->length) == -1) {
644       m_fclose(&fp);
645       body_list_wipe(&cur);
646       return -1;
647     }
648     body_list_wipe(&cur);
649     m_fclose(&fp);
650   }
651   else {
652     fseeko (fpin, body->offset, 0);
653     if (flags & M_CM_PREFIX) {
654       int c;
655       size_t bytes = body->length;
656
657       fputs (prefix, fpout);
658
659       while ((c = fgetc (fpin)) != EOF && bytes--) {
660         fputc (c, fpout);
661         if (c == '\n') {
662           fputs (prefix, fpout);
663         }
664       }
665     }
666     else if (mutt_copy_bytes (fpin, fpout, body->length) == -1)
667       return -1;
668   }
669
670   if ((flags & M_CM_UPDATE) && (flags & M_CM_NOHEADER) == 0
671       && new_offset != -1) {
672     body->offset = new_offset;
673     body_list_wipe(&body->parts);
674   }
675
676   return rc;
677 }
678
679 int
680 mutt_copy_message (FILE * fpout, CONTEXT * src, HEADER * hdr, int flags,
681                    int chflags)
682 {
683   MESSAGE *msg;
684   int r;
685
686   if (!(msg = mx_open_message (src, hdr->msgno)))
687     return -1;
688
689   r = _mutt_copy_message(fpout, msg->fp, hdr, hdr->content, flags,
690                          chflags);
691   if (!r && (ferror(fpout) || feof (fpout))) {
692     r = -1;
693   }
694   mx_close_message (&msg);
695   return r;
696 }
697
698 /* appends a copy of the given message to a mailbox
699  *
700  * dest         destination mailbox
701  * fpin         where to get input
702  * src          source mailbox
703  * hdr          message being copied
704  * body         structure of message being copied
705  * flags        mutt_copy_message() flags
706  * chflags      mutt_copy_header() flags
707  */
708
709 int
710 _mutt_append_message (CONTEXT * dest, FILE * fpin, CONTEXT * src __attribute__ ((unused)),
711                       HEADER * hdr, BODY * body, int flags, int chflags) {
712   char buf[STRING];
713   MESSAGE *msg;
714   int r;
715
716   fseeko(fpin, hdr->offset, 0);
717   if (fgets (buf, sizeof (buf), fpin) == NULL)
718     return -1;
719   if ((msg = mx_open_new_message (dest, hdr, is_from (buf, NULL, 0, NULL) ? 0 : M_ADD_FROM)) == NULL)
720     return -1;
721   if (dest->magic == M_MBOX)
722     chflags |= CH_FROM | CH_FORCE_FROM;
723   chflags |= (dest->magic == M_MAILDIR ? CH_NOSTATUS : CH_UPDATE);
724   r = _mutt_copy_message (msg->fp, fpin, hdr, body, flags, chflags);
725   if (mx_commit_message (msg, dest) != 0)
726     r = -1;
727
728   mx_close_message (&msg);
729   return r;
730 }
731
732 int
733 mutt_append_message (CONTEXT * dest, CONTEXT * src, HEADER * hdr, int cmflags,
734                      int chflags)
735 {
736   MESSAGE *msg;
737   int r;
738
739   if ((msg = mx_open_message (src, hdr->msgno)) == NULL)
740     return -1;
741   r =
742     _mutt_append_message (dest, msg->fp, src, hdr, hdr->content, cmflags,
743                           chflags);
744   mx_close_message (&msg);
745   return r;
746 }
747
748 /*
749  * This function copies a message body, while deleting _in_the_copy_
750  * any attachments which are marked for deletion.
751  * Nothing is changed in the original message -- this is left to the caller.
752  *
753  * The function will return 0 on success and -1 on failure.
754  */
755 static int copy_delete_attach (BODY * b, FILE * fpin, FILE * fpout,
756                                char *date)
757 {
758   BODY *part;
759
760   for (part = b->parts; part; part = part->next) {
761     if (part->deleted || part->parts) {
762       /* Copy till start of this part */
763       if (mutt_copy_bytes (fpin, fpout, part->hdr_offset - ftello (fpin)))
764         return -1;
765
766       if (part->deleted) {
767         fprintf (fpout,
768                  "Content-Type: message/external-body; access-type=x-mutt-deleted;\n"
769                  "\texpiration=%s; length=%zu\n"
770                  "\n", date + 5, part->length);
771         if (ferror (fpout))
772           return -1;
773
774         /* Copy the original mime headers */
775         if (mutt_copy_bytes (fpin, fpout, part->offset - ftello (fpin)))
776           return -1;
777
778         /* Skip the deleted body */
779         fseeko (fpin, part->offset + part->length, SEEK_SET);
780       }
781       else {
782         if (copy_delete_attach (part, fpin, fpout, date))
783           return -1;
784       }
785     }
786   }
787
788   /* Copy the last parts */
789   if (mutt_copy_bytes (fpin, fpout, b->offset + b->length - ftello (fpin)))
790     return -1;
791
792   return 0;
793 }
794
795 /* 
796  * This function is the equivalent of mutt_write_address_list(),
797  * but writes to a buffer instead of writing to a stream.
798  * mutt_write_address_list could be re-used if we wouldn't store
799  * all the decoded headers in a huge array, first. 
800  *
801  * XXX - fix that. 
802  */
803
804 static void format_address_header (char **h, address_t * a)
805 {
806   char buf[HUGE_STRING];
807   char cbuf[STRING];
808   char c2buf[STRING];
809
810   int l, linelen, buflen, count;
811
812   linelen = m_strlen(*h);
813   buflen = linelen + 3;
814
815
816   p_realloc(h, buflen);
817   for (count = 0; a; a = a->next, count++) {
818     address_t *tmp = a->next;
819
820     a->next = NULL;
821     *buf = *cbuf = *c2buf = '\0';
822     rfc822_addrcat(buf, sizeof (buf), a, 0);
823     a->next = tmp;
824
825     l = m_strlen(buf);
826     if (count && linelen + l > 74) {
827       m_strcpy(cbuf, sizeof(cbuf), "\n\t");
828       linelen = l + 8;
829     }
830     else {
831       if (a->mailbox) {
832         m_strcpy(cbuf, sizeof(cbuf), " ");
833         linelen++;
834       }
835       linelen += l;
836     }
837     if (!a->group && a->next && a->next->mailbox) {
838       linelen++;
839       buflen++;
840       m_strcpy(c2buf, sizeof(c2buf), ",");
841     }
842
843     buflen += l + m_strlen(cbuf) + m_strlen(c2buf);
844     p_realloc(h, buflen);
845     m_strcat(*h, buflen, cbuf);
846     m_strcat(*h, buflen, buf);
847     m_strcat(*h, buflen, c2buf);
848   }
849
850   /* Space for this was allocated in the beginning of this function. */
851   m_strcat(*h, buflen, "\n");
852 }
853
854 static int address_header_decode(char **h)
855 {
856     address_t *a;
857     char *p, *s = *h;
858
859     p = strchr(s, ':');
860     if (!p)
861         return 0;
862
863     switch (mime_which_token(s, p - s)) {
864       case MIME_RETURN_PATH:
865       case MIME_REPLY_TO:
866       case MIME_FROM:
867       case MIME_CC:
868       case MIME_BCC:
869       case MIME_SENDER:
870       case MIME_TO:
871       case MIME_MAIL_FOLLOWUP_TO:
872         p++;
873         break;
874
875       default:
876         return 0;
877     }
878
879     a = rfc822_parse_adrlist(NULL, p);
880     if (!a)
881         return 0;
882
883     mutt_addrlist_to_local(a);
884     rfc2047_decode_adrlist(a);
885
886     *h = p_dupstr(s, p - s);
887     format_address_header(h, a);
888     address_list_wipe(&a);
889
890     p_delete(&s);
891     return 1;
892 }