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