mappings as standalone module
[apps/madmutt.git] / commands.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include <lib-lib/mem.h>
16 #include <lib-lib/file.h>
17 #include <lib-lib/macros.h>
18 #include <lib-lib/ascii.h>
19
20 #include "mutt.h"
21 #include "enter.h"
22 #include "recvattach.h"
23 #include "mutt_curses.h"
24 #include "mutt_menu.h"
25 #include "mime.h"
26 #include "sort.h"
27 #include "copy.h"
28 #include "mx.h"
29 #include "pager.h"
30 #include "mutt_crypt.h"
31 #include "mutt_idna.h"
32 #include "rfc1524.h"
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36
37 #ifdef USE_IMAP
38 #include "imap.h"
39 #endif
40
41 #ifdef BUFFY_SIZE
42 #include "buffy.h"
43 #endif
44
45 #include "lib/debug.h"
46
47 #include <errno.h>
48 #include <unistd.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sys/wait.h>
52 #include <sys/stat.h>
53 #include <sys/types.h>
54 #include <utime.h>
55
56 /* The folder the user last saved to.  Used by ci_save_message() */
57 static char LastSaveFolder[_POSIX_PATH_MAX] = "";
58
59 int mutt_display_message (HEADER * cur)
60 {
61   char tempfile[_POSIX_PATH_MAX], buf[LONG_STRING];
62   int rc = 0, builtin = 0;
63   int cmflags = M_CM_DECODE | M_CM_DISPLAY | M_CM_CHARCONV;
64   FILE *fpout = NULL;
65   FILE *fpfilterout = NULL;
66   MESSAGE *msg = NULL;
67   pid_t filterpid = -1;
68   int res = 0;
69
70   snprintf (buf, sizeof (buf), "%s/%s", TYPE (cur->content),
71             cur->content->subtype);
72
73   mutt_parse_mime_message (Context, cur);
74   mutt_message_hook (Context, cur, M_MESSAGEHOOK);
75
76   mutt_mktemp (tempfile);
77   if ((fpout = safe_fopen (tempfile, "w")) == NULL) {
78     mutt_error _("Could not create temporary file!");
79
80     return (0);
81   }
82
83   if (DisplayFilter && *DisplayFilter) {
84     fpfilterout = fpout;
85     fpout = NULL;
86     /* mutt_endwin (NULL); */
87     filterpid = mutt_create_filter_fd (DisplayFilter, &fpout, NULL, NULL,
88                                        -1, fileno (fpfilterout), -1);
89     if (filterpid < 0) {
90       mutt_error (_("Cannot create display filter"));
91       safe_fclose (&fpfilterout);
92       unlink (tempfile);
93       return 0;
94     }
95   }
96
97   if (!Pager || m_strcmp(Pager, "builtin") == 0)
98     builtin = 1;
99   else {
100     mutt_make_string (buf, sizeof (buf), NONULL (PagerFmt), Context, cur);
101     fputs (buf, fpout);
102     fputs ("\n\n", fpout);
103   }
104
105   msg = mx_open_message (Context, cur->msgno);
106   if (msg == NULL) res = -1;
107
108   if (res != -1) {
109     /* see if crytpo is needed for this message.  if so, we should exit curses */
110     if (WithCrypto && cur->security) {
111       if (cur->security & ENCRYPT) {
112         if (cur->security & APPLICATION_SMIME)
113           crypt_smime_getkeys (cur->env);
114         if (!crypt_valid_passphrase (cur->security))
115           return 0;
116
117         cmflags |= M_CM_VERIFY;
118       }
119       else if (cur->security & SIGN) {
120         /* find out whether or not the verify signature */
121         if (query_quadoption (OPT_VERIFYSIG, _("Verify PGP signature?")) ==
122             M_YES) {
123           cmflags |= M_CM_VERIFY;
124         }
125       }
126     }
127
128     if (cmflags & M_CM_VERIFY || cur->security & ENCRYPT) {
129       if (cur->security & APPLICATION_PGP) {
130         if (cur->env->from)
131           crypt_pgp_invoke_getkeys (cur->env->from);
132
133         crypt_invoke_message (APPLICATION_PGP);
134       }
135
136       if (cur->security & APPLICATION_SMIME)
137         crypt_invoke_message (APPLICATION_SMIME);
138     }
139
140     res = _mutt_copy_message (fpout, msg->fp, cur, cur->content, cmflags,
141                              (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
142                              CH_DECODE | CH_FROM);
143     if (res == 0 && (ferror(fpout) || feof(fpout))) {
144       debug_print (1, ("_mutt_copy_message failed to detect EOF!\n"));
145       res = -1;
146     }
147
148     mx_close_message (&msg);
149   }
150
151   if ((safe_fclose (&fpout) != 0 && errno != EPIPE) || res == -1) {
152     mutt_error (_("Could not copy message"));
153     if (fpfilterout != NULL) {
154       mutt_wait_filter (filterpid);
155       safe_fclose (&fpfilterout);
156     }
157 #if 0
158     /* this is maybe just plain wrong but it makes the pager display
159      * what we have; i.e. for the crypto stuff we only get
160      * 'Could not copy message' for invalid passphrases, no PGP output
161      * not nothing; so just display what we have...
162      * - pdmef
163      */
164     mutt_unlink (tempfile);
165     return 0;
166 #endif
167   }
168
169   if (fpfilterout != NULL && mutt_wait_filter (filterpid) != 0)
170     mutt_any_key_to_continue (NULL);
171
172   safe_fclose (&fpfilterout);   /* XXX - check result? */
173
174
175   if (WithCrypto) {
176     /* update crypto information for this message */
177     cur->security &= ~(GOODSIGN|BADSIGN);
178     cur->security |= crypt_query (cur->content);
179
180     /* Remove color cache for this message, in case there
181        are color patterns for both ~g and ~V */
182     cur->pair = 0;
183   }
184
185   if (builtin) {
186     pager_t info;
187
188     if (WithCrypto
189         && (cur->security & APPLICATION_SMIME) && (cmflags & M_CM_VERIFY)) {
190       if (cur->security & GOODSIGN) {
191         if (!crypt_smime_verify_sender (cur))
192           mutt_message (_("S/MIME signature successfully verified."));
193         else
194           mutt_error (_("S/MIME certificate owner does not match sender."));
195       }
196       else if (cur->security & PARTSIGN)
197         mutt_message (_
198                       ("Warning: Part of this message has not been signed."));
199       else if (cur->security & SIGN || cur->security & BADSIGN)
200         mutt_error (_("S/MIME signature could NOT be verified."));
201     }
202
203     if (WithCrypto
204         && (cur->security & APPLICATION_PGP) && (cmflags & M_CM_VERIFY)) {
205       if (cur->security & GOODSIGN)
206         mutt_message (_("PGP signature successfully verified."));
207       else if (cur->security & PARTSIGN)
208         mutt_message (_
209                       ("Warning: Part of this message has not been signed."));
210       else if (cur->security & SIGN)
211         mutt_message (_("PGP signature could NOT be verified."));
212     }
213
214     /* Invoke the builtin pager */
215     p_clear(&info, 1);
216     info.hdr = cur;
217     info.ctx = Context;
218     rc = mutt_pager (NULL, tempfile, M_PAGER_MESSAGE, &info);
219   }
220   else {
221     int r;
222
223     mutt_endwin (NULL);
224     snprintf (buf, sizeof (buf), "%s %s", NONULL (Pager), tempfile);
225     if ((r = mutt_system (buf)) == -1)
226       mutt_error (_("Error running \"%s\"!"), buf);
227     unlink (tempfile);
228     keypad (stdscr, TRUE);
229     if (r != -1)
230       mutt_set_flag (Context, cur, M_READ, 1);
231     if (r != -1 && option (OPTPROMPTAFTER)) {
232       mutt_ungetch (mutt_any_key_to_continue _("Command: "), 0);
233       rc = km_dokey (MENU_PAGER);
234     }
235     else
236       rc = 0;
237   }
238
239   return rc;
240 }
241
242 void ci_bounce_message (HEADER * h, int *redraw)
243 {
244   char prompt[SHORT_STRING];
245   char buf[HUGE_STRING] = { 0 };
246   ADDRESS *adr = NULL;
247   char *err = NULL;
248   int rc;
249
250   if (h)
251     m_strcpy(prompt, sizeof(prompt), _("Bounce message to: "));
252   else
253     m_strcpy(prompt, sizeof(prompt), _("Bounce tagged messages to: "));
254
255   rc = mutt_get_field (prompt, buf, sizeof (buf), M_ALIAS);
256
257   if (option (OPTNEEDREDRAW)) {
258     unset_option (OPTNEEDREDRAW);
259     *redraw = REDRAW_FULL;
260   }
261
262   if (rc || !buf[0])
263     return;
264
265   if (!(adr = rfc822_parse_adrlist (adr, buf))) {
266     mutt_error _("Error parsing address!");
267
268     return;
269   }
270
271   adr = mutt_expand_aliases (adr);
272
273   if (mutt_addrlist_to_idna (adr, &err) < 0) {
274     mutt_error (_("Bad IDN: '%s'"), err);
275     p_delete(&err);
276     rfc822_free_address (&adr);
277     return;
278   }
279
280   buf[0] = 0;
281   rfc822_write_address (buf, sizeof (buf), adr, 1);
282
283 #define extra_space (15 + 7 + 2)
284   snprintf (prompt, sizeof (prompt),
285             (h ? _("Bounce message to %s") : _("Bounce messages to %s")),
286             buf);
287
288   if (mutt_strwidth (prompt) > COLS - extra_space) {
289     mutt_format_string(prompt, sizeof(prompt), 0, COLS - extra_space, 0, 0,
290                        prompt, sizeof(prompt), 0);
291     m_strcat(prompt, sizeof(prompt), "...?");
292   } else {
293     m_strcat(prompt, sizeof(prompt), "?");
294   }
295
296   if (query_quadoption (OPT_BOUNCE, prompt) != M_YES) {
297     rfc822_free_address (&adr);
298     CLEARLINE (LINES - 1);
299     mutt_message (h ? _("Message not bounced.") : _("Messages not bounced."));
300     return;
301   }
302
303   CLEARLINE (LINES - 1);
304
305   rc = mutt_bounce_message (NULL, h, adr);
306   rfc822_free_address (&adr);
307   /* If no error, or background, display message. */
308   if ((rc == 0) || (rc == S_BKG))
309     mutt_message (h ? _("Message bounced.") : _("Messages bounced."));
310 }
311
312 static void pipe_set_flags (int decode, int print, int *cmflags, int *chflags)
313 {
314   if (decode) {
315     *cmflags |= M_CM_DECODE | M_CM_CHARCONV;
316     *chflags |= CH_DECODE | CH_REORDER;
317
318     if (option (OPTWEED)) {
319       *chflags |= CH_WEED;
320       *cmflags |= M_CM_WEED;
321     }
322   }
323
324   if (print)
325     *cmflags |= M_CM_PRINTING;
326
327 }
328
329 static void pipe_msg (HEADER * h, FILE * fp, int decode, int print)
330 {
331   int cmflags = 0;
332   int chflags = CH_FROM;
333
334   pipe_set_flags (decode, print, &cmflags, &chflags);
335
336   if (WithCrypto && decode && h->security & ENCRYPT) {
337     if (!crypt_valid_passphrase (h->security))
338       return;
339     endwin ();
340   }
341
342   if (decode)
343     mutt_parse_mime_message (Context, h);
344
345   mutt_copy_message (fp, Context, h, cmflags, chflags);
346 }
347
348
349 /* the following code is shared between printing and piping */
350
351 static int _mutt_pipe_message(HEADER * h, char *cmd, int decode, int print,
352                               int split, const char *sep)
353 {
354
355   int i, rc = 0;
356   pid_t thepid;
357   FILE *fpout;
358
359 /*   mutt_endwin (NULL); 
360
361      is this really needed here ? 
362      it makes the screen flicker on pgp and s/mime messages,
363      before asking for a passphrase...
364                                      Oliver Ehli */
365   if (h) {
366
367     mutt_message_hook (Context, h, M_MESSAGEHOOK);
368
369     if (WithCrypto && decode) {
370       mutt_parse_mime_message (Context, h);
371       if (h->security & ENCRYPT && !crypt_valid_passphrase (h->security))
372         return 1;
373     }
374     mutt_endwin (NULL);
375
376     if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0) {
377       mutt_perror (_("Can't create filter process"));
378
379       return 1;
380     }
381
382     pipe_msg (h, fpout, decode, print);
383     fclose (fpout);
384     rc = mutt_wait_filter (thepid);
385   }
386   else {                        /* handle tagged messages */
387
388     if (WithCrypto && decode) {
389       for (i = 0; i < Context->vcount; i++)
390         if (Context->hdrs[Context->v2r[i]]->tagged) {
391           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
392                              M_MESSAGEHOOK);
393           mutt_parse_mime_message (Context, Context->hdrs[Context->v2r[i]]);
394           if (Context->hdrs[Context->v2r[i]]->security & ENCRYPT &&
395               !crypt_valid_passphrase (Context->hdrs[Context->v2r[i]]->
396                                        security))
397             return 1;
398         }
399     }
400
401     if (split) {
402       for (i = 0; i < Context->vcount; i++) {
403         if (Context->hdrs[Context->v2r[i]]->tagged) {
404           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
405                              M_MESSAGEHOOK);
406           mutt_endwin (NULL);
407           if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0) {
408             mutt_perror (_("Can't create filter process"));
409
410             return 1;
411           }
412           pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
413           /* add the message separator */
414           if (sep)
415             fputs (sep, fpout);
416           safe_fclose (&fpout);
417           if (mutt_wait_filter (thepid) != 0)
418             rc = 1;
419         }
420       }
421     }
422     else {
423       mutt_endwin (NULL);
424       if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0) {
425         mutt_perror (_("Can't create filter process"));
426
427         return 1;
428       }
429       for (i = 0; i < Context->vcount; i++) {
430         if (Context->hdrs[Context->v2r[i]]->tagged) {
431           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
432                              M_MESSAGEHOOK);
433           pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
434           /* add the message separator */
435           if (sep)
436             fputs (sep, fpout);
437         }
438       }
439       safe_fclose (&fpout);
440       if (mutt_wait_filter (thepid) != 0)
441         rc = 1;
442     }
443   }
444
445   if (rc || option (OPTWAITKEY))
446     mutt_any_key_to_continue (NULL);
447   return rc;
448 }
449
450 void mutt_pipe_message (HEADER * h)
451 {
452   char buffer[LONG_STRING];
453
454   buffer[0] = 0;
455   if (mutt_get_field (_("Pipe to command: "), buffer, sizeof (buffer), M_CMD)
456       != 0 || !buffer[0])
457     return;
458
459   mutt_expand_path (buffer, sizeof (buffer));
460   _mutt_pipe_message (h, buffer,
461                       option (OPTPIPEDECODE),
462                       0, option (OPTPIPESPLIT), PipeSep);
463 }
464
465 void mutt_print_message (HEADER * h)
466 {
467
468   if (quadoption (OPT_PRINT) && (!PrintCmd || !*PrintCmd)) {
469     mutt_message (_("No printing command has been defined."));
470     return;
471   }
472
473   if (query_quadoption (OPT_PRINT,
474                         h ? _("Print message?") : _("Print tagged messages?"))
475       != M_YES)
476     return;
477
478   if (_mutt_pipe_message (h, PrintCmd,
479                           option (OPTPRINTDECODE),
480                           1, option (OPTPRINTSPLIT), "\f") == 0)
481     mutt_message (h ? _("Message printed") : _("Messages printed"));
482   else
483     mutt_message (h ? _("Message could not be printed") :
484                   _("Messages could not be printed"));
485 }
486
487
488 int mutt_select_sort (int reverse)
489 {
490   int method = Sort;            /* save the current method in case of abort */
491
492   switch (mutt_multi_choice (reverse ?
493                              _
494                              ("Rev-Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: ")
495                              :
496                              _
497                              ("Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: "),
498                              _("dfrsotuzcp"))) {
499   case -1:                     /* abort - don't resort */
500     return -1;
501
502   case 1:                      /* (d)ate */
503     Sort = SORT_DATE;
504     break;
505
506   case 2:                      /* (f)rm */
507     Sort = SORT_FROM;
508     break;
509
510   case 3:                      /* (r)ecv */
511     Sort = SORT_RECEIVED;
512     break;
513
514   case 4:                      /* (s)ubj */
515     Sort = SORT_SUBJECT;
516     break;
517
518   case 5:                      /* t(o) */
519     Sort = SORT_TO;
520     break;
521
522   case 6:                      /* (t)hread */
523     Sort = SORT_THREADS;
524     break;
525
526   case 7:                      /* (u)nsort */
527     Sort = SORT_ORDER;
528     break;
529
530   case 8:                      /* si(z)e */
531     Sort = SORT_SIZE;
532     break;
533
534   case 9:                      /* s(c)ore */
535     Sort = SORT_SCORE;
536     break;
537
538   case 10:                     /* s(p)am */
539     Sort = SORT_SPAM;
540     break;
541   }
542   if (reverse)
543     Sort |= SORT_REVERSE;
544
545   return (Sort != method ? 0 : -1);     /* no need to resort if it's the same */
546 }
547
548 /* invoke a command in a subshell */
549 void mutt_shell_escape (void)
550 {
551   char buf[LONG_STRING];
552
553   buf[0] = 0;
554   if (mutt_get_field (_("Shell command: "), buf, sizeof (buf), M_CMD) == 0) {
555     if (!buf[0] && Shell)
556       m_strcpy(buf, sizeof(buf), Shell);
557     if (buf[0]) {
558       CLEARLINE (LINES - 1);
559       mutt_endwin (NULL);
560       fflush (stdout);
561       if (mutt_system (buf) != 0 || option (OPTWAITKEY))
562         mutt_any_key_to_continue (NULL);
563     }
564   }
565 }
566
567 /* enter a mutt command */
568 void mutt_enter_command (void)
569 {
570   BUFFER err, token;
571   char buffer[LONG_STRING], errbuf[SHORT_STRING];
572   int r;
573
574   buffer[0] = 0;
575   if (mutt_get_field (":", buffer, sizeof (buffer), M_COMMAND) != 0
576       || !buffer[0])
577     return;
578   err.data = errbuf;
579   err.dsize = sizeof (errbuf);
580   p_clear(&token, 1);
581   r = mutt_parse_rc_line (buffer, &token, &err);
582   p_delete(&token.data);
583   if (errbuf[0]) {
584     /* since errbuf could potentially contain printf() sequences in it,
585        we must call mutt_error() in this fashion so that vsprintf()
586        doesn't expect more arguments that we passed */
587     if (r == 0)
588       mutt_message ("%s", errbuf);
589     else
590       mutt_error ("%s", errbuf);
591   }
592 }
593
594 void mutt_display_address (ENVELOPE * env)
595 {
596   const char *pfx = NULL;
597   char buf[SHORT_STRING];
598   ADDRESS *adr = NULL;
599
600   adr = mutt_get_address(env, &pfx);
601
602   if (!adr)
603     return;
604
605   /* 
606    * Note: We don't convert IDNA to local representation this time.
607    * That is intentional, so the user has an opportunity to copy &
608    * paste the on-the-wire form of the address to other, IDN-unable
609    * software. 
610    */
611
612   buf[0] = 0;
613   rfc822_write_address (buf, sizeof (buf), adr, 0);
614   mutt_message ("%s: %s", pfx, buf);
615 }
616
617 static void set_copy_flags (HEADER * hdr, int decode, int decrypt,
618                             int *cmflags, int *chflags)
619 {
620   *cmflags = 0;
621   *chflags = CH_UPDATE_LEN;
622
623   if (WithCrypto && !decode && decrypt && (hdr->security & ENCRYPT)) {
624     if ((WithCrypto & APPLICATION_PGP)
625         && mutt_is_multipart_encrypted (hdr->content)) {
626       *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
627       *cmflags = M_CM_DECODE_PGP;
628     }
629     else if ((WithCrypto & APPLICATION_PGP)
630              && mutt_is_application_pgp (hdr->content) & ENCRYPT)
631       decode = 1;
632     else if ((WithCrypto & APPLICATION_SMIME)
633              && mutt_is_application_smime (hdr->content) & ENCRYPT) {
634       *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
635       *cmflags = M_CM_DECODE_SMIME;
636     }
637   }
638
639   if (decode) {
640     *chflags = CH_XMIT | CH_MIME | CH_TXTPLAIN;
641     *cmflags = M_CM_DECODE | M_CM_CHARCONV;
642
643     if (!decrypt) {             /* If decode doesn't kick in for decrypt, */
644       *chflags |= CH_DECODE;    /* then decode RFC 2047 headers, */
645
646       if (option (OPTWEED)) {
647         *chflags |= CH_WEED;    /* and respect $weed. */
648         *cmflags |= M_CM_WEED;
649       }
650     }
651   }
652 }
653
654 int _mutt_save_message (HEADER * h, CONTEXT * ctx, int delete, int decode,
655                          int decrypt) {
656   int cmflags, chflags;
657   int rc;
658
659   set_copy_flags (h, decode, decrypt, &cmflags, &chflags);
660
661   if (decode || decrypt)
662     mutt_parse_mime_message (Context, h);
663
664   if ((rc = mutt_append_message (ctx, Context, h, cmflags, chflags)) != 0)
665     return rc;
666
667   if (delete) {
668     mutt_set_flag (Context, h, M_DELETE, 1);
669     if (option (OPTDELETEUNTAG))
670       mutt_set_flag (Context, h, M_TAG, 0);
671     mutt_set_flag (Context, h, M_APPENDED, 1);
672   }
673   return (0);
674 }
675
676 /* returns 0 if the copy/save was successful, or -1 on error/abort */
677 int mutt_save_message (HEADER * h, int delete,
678                        int decode, int decrypt, int *redraw) {
679   int i, need_buffy_cleanup;
680   int need_passphrase = 0, app = 0;
681   char prompt[SHORT_STRING], buf[_POSIX_PATH_MAX];
682   CONTEXT ctx;
683   struct stat st;
684
685 #ifdef BUFFY_SIZE
686   BUFFY *tmp = NULL;
687 #else
688   struct utimbuf ut;
689 #endif
690
691   *redraw = 0;
692
693
694   snprintf (prompt, sizeof (prompt),
695             decode ? (delete ? _("Decode-save%s to mailbox") :
696                       _("Decode-copy%s to mailbox")) :
697             (decrypt ? (delete ? _("Decrypt-save%s to mailbox") :
698                         _("Decrypt-copy%s to mailbox")) :
699              (delete ? _("Save%s to mailbox") : _("Copy%s to mailbox"))),
700             h ? "" : _(" tagged"));
701
702
703   if (h) {
704     if (WithCrypto) {
705       need_passphrase = h->security & ENCRYPT;
706       app = h->security;
707     }
708     mutt_message_hook (Context, h, M_MESSAGEHOOK);
709     mutt_default_save (buf, sizeof (buf), h);
710   }
711   else {
712     /* look for the first tagged message */
713
714     for (i = 0; i < Context->vcount; i++) {
715       if (Context->hdrs[Context->v2r[i]]->tagged) {
716         h = Context->hdrs[Context->v2r[i]];
717         break;
718       }
719     }
720
721
722     if (h) {
723       mutt_message_hook (Context, h, M_MESSAGEHOOK);
724       mutt_default_save (buf, sizeof (buf), h);
725       if (WithCrypto) {
726         need_passphrase = h->security & ENCRYPT;
727         app = h->security;
728       }
729       h = NULL;
730     }
731   }
732
733   mutt_pretty_mailbox (buf);
734   if (mutt_enter_fname (prompt, buf, sizeof (buf), redraw, 0) == -1)
735     return (-1);
736
737   if (*redraw != REDRAW_FULL) {
738     if (!h)
739       *redraw = REDRAW_INDEX | REDRAW_STATUS;
740     else
741       *redraw = REDRAW_STATUS;
742   }
743
744   if (!buf[0])
745     return (-1);
746
747   /* This is an undocumented feature of ELM pointed out to me by Felix von
748    * Leitner <leitner@prz.fu-berlin.de>
749    */
750   if (m_strcmp(buf, ".") == 0)
751     m_strcpy(buf, sizeof(buf), LastSaveFolder);
752   else
753     m_strcpy(LastSaveFolder, sizeof(LastSaveFolder), buf);
754
755   mutt_expand_path (buf, sizeof (buf));
756
757   /* check to make sure that this file is really the one the user wants */
758   if (mutt_save_confirm (buf, &st) != 0)
759     return -1;
760
761   if (WithCrypto && need_passphrase && (decode || decrypt)
762       && !crypt_valid_passphrase (app))
763     return -1;
764
765   mutt_message (_("Copying to %s..."), buf);
766
767 #ifdef USE_IMAP
768   if (Context->magic == M_IMAP && !(decode || decrypt) && mx_get_magic (buf) == M_IMAP) {
769     switch (imap_copy_messages (Context, h, buf, delete)) {
770       /* success */
771     case 0:
772       mutt_clear_error ();
773       return 0;
774       /* non-fatal error: fall through to fetch/append */
775     case 1:
776       break;
777       /* fatal error, abort */
778     case -1:
779       return -1;
780     }
781   }
782 #endif
783
784   if (mx_open_mailbox (buf, M_APPEND, &ctx) != NULL) {
785     if (h) {
786       if (_mutt_save_message (h, &ctx, delete, decode, decrypt) != 0) {
787         mx_close_mailbox (&ctx, NULL);
788         return (-1);
789       }
790     } else {
791       for (i = 0; i < Context->vcount; i++) {
792         if (Context->hdrs[Context->v2r[i]]->tagged) {
793           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
794                              M_MESSAGEHOOK);
795           if (_mutt_save_message (Context->hdrs[Context->v2r[i]], &ctx, delete,
796                                   decode, decrypt) != 0) {
797             mx_close_mailbox (&ctx, NULL);
798             return (-1);
799           }
800         }
801       }
802     }
803
804     need_buffy_cleanup = (ctx.magic == M_MBOX || ctx.magic == M_MMDF);
805
806     mx_close_mailbox (&ctx, NULL);
807
808     if (need_buffy_cleanup) {
809 #ifdef BUFFY_SIZE
810       tmp = buffy_find_mailbox (buf);
811       if (tmp && tmp->new <= 0)
812         buffy_update_mailbox (tmp);
813 #else
814       /* fix up the times so buffy won't get confused */
815       if (st.st_mtime > st.st_atime) {
816         ut.actime = st.st_atime;
817         ut.modtime = time (NULL);
818         utime (buf, &ut);
819       }
820       else
821         utime (buf, NULL);
822 #endif
823     }
824
825     mutt_clear_error ();
826     return (0);
827   }
828
829   return -1;
830 }
831
832 void mutt_version (void)
833 {
834   mutt_message (mutt_make_version (1));
835 }
836
837 void mutt_edit_content_type (HEADER * h, BODY * b, FILE * fp)
838 {
839   char buf[LONG_STRING];
840   char obuf[LONG_STRING];
841   char tmp[STRING];
842   PARAMETER *p;
843
844   char charset[STRING];
845   char *cp;
846
847   short charset_changed = 0;
848   short type_changed = 0;
849
850   cp = mutt_get_parameter ("charset", b->parameter);
851   m_strcpy(charset, sizeof(charset), NONULL(cp));
852
853   snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
854   m_strcpy(obuf, sizeof(obuf), buf);
855   if (b->parameter) {
856     size_t l;
857
858     for (p = b->parameter; p; p = p->next) {
859       l = m_strlen(buf);
860
861       rfc822_cat (tmp, sizeof (tmp), p->value, MimeSpecials);
862       snprintf (buf + l, sizeof (buf) - l, "; %s=%s", p->attribute, tmp);
863     }
864   }
865
866   if (mutt_get_field ("Content-Type: ", buf, sizeof (buf), 0) != 0 ||
867       buf[0] == 0)
868     return;
869
870   /* clean up previous junk */
871   mutt_free_parameter (&b->parameter);
872   p_delete(&b->subtype);
873
874   mutt_parse_content_type (buf, b);
875
876
877   snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
878   type_changed = ascii_strcasecmp (tmp, obuf);
879   charset_changed =
880     ascii_strcasecmp (charset, mutt_get_parameter ("charset", b->parameter));
881
882   /* if in send mode, check for conversion - current setting is default. */
883
884   if (!h && b->type == TYPETEXT && charset_changed) {
885     int r;
886
887     snprintf (tmp, sizeof (tmp), _("Convert to %s upon sending?"),
888               mutt_get_parameter ("charset", b->parameter));
889     if ((r = mutt_yesorno (tmp, !b->noconv)) != -1)
890       b->noconv = (r == M_NO);
891   }
892
893   /* inform the user */
894
895   snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
896   if (type_changed)
897     mutt_message (_("Content-Type changed to %s."), tmp);
898   if (b->type == TYPETEXT && charset_changed) {
899     if (type_changed)
900       mutt_sleep (1);
901     mutt_message (_("Character set changed to %s; %s."),
902                   mutt_get_parameter ("charset", b->parameter),
903                   b->noconv ? _("not converting") : _("converting"));
904   }
905
906   b->force_charset |= charset_changed ? 1 : 0;
907
908   if (!is_multipart (b) && b->parts)
909     mutt_free_body (&b->parts);
910   if (!mutt_is_message_type (b->type, b->subtype) && b->hdr) {
911     b->hdr->content = NULL;
912     mutt_free_header (&b->hdr);
913   }
914
915   if (fp && (is_multipart (b) || mutt_is_message_type (b->type, b->subtype)))
916     mutt_parse_part (fp, b);
917
918   if (WithCrypto && h) {
919     if (h->content == b)
920       h->security = 0;
921
922     h->security |= crypt_query (b);
923   }
924 }
925
926
927 static int _mutt_check_traditional_pgp (HEADER * h, int *redraw)
928 {
929   MESSAGE *msg;
930   int rv = 0;
931
932   h->security |= PGP_TRADITIONAL_CHECKED;
933
934   mutt_parse_mime_message (Context, h);
935   if ((msg = mx_open_message (Context, h->msgno)) == NULL)
936     return 0;
937   if (crypt_pgp_check_traditional (msg->fp, h->content, 0)) {
938     h->security = crypt_query (h->content);
939     *redraw |= REDRAW_FULL;
940     rv = 1;
941   }
942
943   h->security |= PGP_TRADITIONAL_CHECKED;
944   mx_close_message (&msg);
945   return rv;
946 }
947
948 int mutt_check_traditional_pgp (HEADER * h, int *redraw)
949 {
950   int i;
951   int rv = 0;
952
953   if (h && !(h->security & PGP_TRADITIONAL_CHECKED))
954     rv = _mutt_check_traditional_pgp (h, redraw);
955   else {
956     for (i = 0; i < Context->vcount; i++)
957       if (Context->hdrs[Context->v2r[i]]->tagged &&
958           !(Context->hdrs[Context->v2r[i]]->
959             security & PGP_TRADITIONAL_CHECKED))
960         rv =
961           _mutt_check_traditional_pgp (Context->hdrs[Context->v2r[i]], redraw)
962           || rv;
963   }
964   return rv;
965 }