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