1 /**
2  * @file
3  * Manage where the email is piped to external commands
4  *
5  * @authors
6  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2000-2004,2006 Thomas Roessler <roessler@does-not-exist.org>
8  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
9  *
10  * @copyright
11  * This program is free software: you can redistribute it and/or modify it under
12  * the terms of the GNU General Public License as published by the Free Software
13  * Foundation, either version 2 of the License, or (at your option) any later
14  * version.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License along with
22  * this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 /**
26  * @page neo_commands Manage where the email is piped to external commands
27  *
28  * Manage where the email is piped to external commands
29  */
30 
31 #include "config.h"
32 #include <assert.h>
33 #include <limits.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39 #include "mutt/lib.h"
40 #include "address/lib.h"
41 #include "config/lib.h"
42 #include "email/lib.h"
43 #include "core/lib.h"
44 #include "alias/lib.h"
45 #include "gui/lib.h"
46 #include "mutt.h"
47 #include "commands.h"
48 #include "attach/lib.h"
49 #include "ncrypt/lib.h"
50 #include "progress/lib.h"
51 #include "question/lib.h"
52 #include "send/lib.h"
53 #include "browser.h"
54 #include "copy.h"
55 #include "hook.h"
56 #include "icommands.h"
57 #include "init.h"
58 #include "mutt_logging.h"
59 #include "mutt_mailbox.h"
60 #include "mutt_thread.h"
61 #include "muttlib.h"
62 #include "mx.h"
63 #include "options.h"
64 #include "protos.h"
65 #ifdef USE_IMAP
66 #include "imap/lib.h"
67 #endif
68 #ifdef USE_NOTMUCH
69 #include "notmuch/lib.h"
70 #endif
71 #ifdef ENABLE_NLS
72 #include <libintl.h>
73 #endif
74 
75 /** The folder the user last saved to.  Used by ci_save_message() */
76 static struct Buffer LastSaveFolder = { 0 };
77 
78 /**
79  * mutt_commands_cleanup - Clean up commands globals
80  */
mutt_commands_cleanup(void)81 void mutt_commands_cleanup(void)
82 {
83   mutt_buffer_dealloc(&LastSaveFolder);
84 }
85 
86 /**
87  * ci_bounce_message - Bounce an email
88  * @param m  Mailbox
89  * @param el List of Emails to bounce
90  */
ci_bounce_message(struct Mailbox * m,struct EmailList * el)91 void ci_bounce_message(struct Mailbox *m, struct EmailList *el)
92 {
93   if (!m || !el || STAILQ_EMPTY(el))
94     return;
95 
96   char prompt[8193];
97   char scratch[8192];
98   char buf[8192] = { 0 };
99   struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
100   char *err = NULL;
101   int rc;
102   int msg_count = 0;
103 
104   struct EmailNode *en = NULL;
105   STAILQ_FOREACH(en, el, entries)
106   {
107     /* RFC5322 mandates a From: header,
108      * so warn before bouncing messages without one */
109     if (TAILQ_EMPTY(&en->email->env->from))
110       mutt_error(_("Warning: message contains no From: header"));
111 
112     msg_count++;
113   }
114 
115   if (msg_count == 1)
116     mutt_str_copy(prompt, _("Bounce message to: "), sizeof(prompt));
117   else
118     mutt_str_copy(prompt, _("Bounce tagged messages to: "), sizeof(prompt));
119 
120   rc = mutt_get_field(prompt, buf, sizeof(buf), MUTT_ALIAS, false, NULL, NULL);
121   if (rc || (buf[0] == '\0'))
122     return;
123 
124   mutt_addrlist_parse2(&al, buf);
125   if (TAILQ_EMPTY(&al))
126   {
127     mutt_error(_("Error parsing address"));
128     return;
129   }
130 
131   mutt_expand_aliases(&al);
132 
133   if (mutt_addrlist_to_intl(&al, &err) < 0)
134   {
135     mutt_error(_("Bad IDN: '%s'"), err);
136     FREE(&err);
137     mutt_addrlist_clear(&al);
138     return;
139   }
140 
141   buf[0] = '\0';
142   mutt_addrlist_write(&al, buf, sizeof(buf), true);
143 
144 #define EXTRA_SPACE (15 + 7 + 2)
145   snprintf(scratch, sizeof(scratch),
146            ngettext("Bounce message to %s?", "Bounce messages to %s?", msg_count), buf);
147 
148   const size_t width = msgwin_get_width();
149   if (mutt_strwidth(scratch) > (width - EXTRA_SPACE))
150   {
151     mutt_simple_format(prompt, sizeof(prompt), 0, width - EXTRA_SPACE,
152                        JUSTIFY_LEFT, 0, scratch, sizeof(scratch), false);
153     mutt_str_cat(prompt, sizeof(prompt), "...?");
154   }
155   else
156     mutt_str_copy(prompt, scratch, sizeof(prompt));
157 
158   const enum QuadOption c_bounce = cs_subset_quad(NeoMutt->sub, "bounce");
159   if (query_quadoption(c_bounce, prompt) != MUTT_YES)
160   {
161     mutt_addrlist_clear(&al);
162     msgwin_clear_text();
163     mutt_message(ngettext("Message not bounced", "Messages not bounced", msg_count));
164     return;
165   }
166 
167   msgwin_clear_text();
168 
169   struct Message *msg = NULL;
170   STAILQ_FOREACH(en, el, entries)
171   {
172     msg = mx_msg_open(m, en->email->msgno);
173     if (!msg)
174     {
175       rc = -1;
176       break;
177     }
178 
179     rc = mutt_bounce_message(msg->fp, m, en->email, &al, NeoMutt->sub);
180     mx_msg_close(m, &msg);
181 
182     if (rc < 0)
183       break;
184   }
185 
186   mutt_addrlist_clear(&al);
187   /* If no error, or background, display message. */
188   if ((rc == 0) || (rc == S_BKG))
189     mutt_message(ngettext("Message bounced", "Messages bounced", msg_count));
190 }
191 
192 /**
193  * pipe_set_flags - Generate flags for copy header/message
194  * @param[in]  decode  If true decode the message
195  * @param[in]  print   If true, mark the message for printing
196  * @param[out] cmflags Flags, see #CopyMessageFlags
197  * @param[out] chflags Flags, see #CopyHeaderFlags
198  */
pipe_set_flags(bool decode,bool print,CopyMessageFlags * cmflags,CopyHeaderFlags * chflags)199 static void pipe_set_flags(bool decode, bool print, CopyMessageFlags *cmflags,
200                            CopyHeaderFlags *chflags)
201 {
202   if (decode)
203   {
204     *chflags |= CH_DECODE | CH_REORDER;
205     *cmflags |= MUTT_CM_DECODE | MUTT_CM_CHARCONV;
206 
207     const bool c_print_decode_weed =
208         cs_subset_bool(NeoMutt->sub, "print_decode_weed");
209     const bool c_pipe_decode_weed =
210         cs_subset_bool(NeoMutt->sub, "pipe_decode_weed");
211     if (print ? c_print_decode_weed : c_pipe_decode_weed)
212     {
213       *chflags |= CH_WEED;
214       *cmflags |= MUTT_CM_WEED;
215     }
216 
217     /* Just as with copy-decode, we need to update the mime fields to avoid
218      * confusing programs that may process the email.  However, we don't want
219      * to force those fields to appear in printouts. */
220     if (!print)
221       *chflags |= CH_MIME | CH_TXTPLAIN;
222   }
223 
224   if (print)
225     *cmflags |= MUTT_CM_PRINTING;
226 }
227 
228 /**
229  * pipe_msg - Pipe a message
230  * @param m      Mailbox
231  * @param e      Email to pipe
232  * @param msg    Message
233  * @param fp     File to write to
234  * @param decode If true, decode the message
235  * @param print  If true, message is for printing
236  */
pipe_msg(struct Mailbox * m,struct Email * e,struct Message * msg,FILE * fp,bool decode,bool print)237 static void pipe_msg(struct Mailbox *m, struct Email *e, struct Message *msg,
238                      FILE *fp, bool decode, bool print)
239 {
240   CopyMessageFlags cmflags = MUTT_CM_NO_FLAGS;
241   CopyHeaderFlags chflags = CH_FROM;
242 
243   pipe_set_flags(decode, print, &cmflags, &chflags);
244 
245   if ((WithCrypto != 0) && decode && e->security & SEC_ENCRYPT)
246   {
247     if (!crypt_valid_passphrase(e->security))
248       return;
249     endwin();
250   }
251 
252   const bool own_msg = !msg;
253   if (own_msg)
254   {
255     msg = mx_msg_open(m, e->msgno);
256     if (!msg)
257     {
258       return;
259     }
260   }
261 
262   if (decode)
263   {
264     mutt_parse_mime_message(e, msg->fp);
265   }
266 
267   mutt_copy_message(fp, e, msg, cmflags, chflags, 0);
268 
269   if (own_msg)
270   {
271     mx_msg_close(m, &msg);
272   }
273 }
274 
275 /**
276  * pipe_message - Pipe message to a command
277  * @param m      Mailbox
278  * @param el     List of Emails to pipe
279  * @param cmd    Command to pipe to
280  * @param decode Should the message be decrypted
281  * @param print  True if this is a print job
282  * @param split  Should a separator be sent between messages?
283  * @param sep    Separator string
284  * @retval  0 Success
285  * @retval  1 Error
286  *
287  * The following code is shared between printing and piping.
288  */
pipe_message(struct Mailbox * m,struct EmailList * el,const char * cmd,bool decode,bool print,bool split,const char * sep)289 static int pipe_message(struct Mailbox *m, struct EmailList *el, const char *cmd,
290                         bool decode, bool print, bool split, const char *sep)
291 {
292   if (!m || !el)
293     return 1;
294 
295   struct EmailNode *en = STAILQ_FIRST(el);
296   if (!en)
297     return 1;
298 
299   int rc = 0;
300   pid_t pid;
301   FILE *fp_out = NULL;
302 
303   if (!STAILQ_NEXT(en, entries))
304   {
305     /* handle a single message */
306     mutt_message_hook(m, en->email, MUTT_MESSAGE_HOOK);
307 
308     struct Message *msg = mx_msg_open(m, en->email->msgno);
309     if (msg && (WithCrypto != 0) && decode)
310     {
311       mutt_parse_mime_message(en->email, msg->fp);
312       if ((en->email->security & SEC_ENCRYPT) &&
313           !crypt_valid_passphrase(en->email->security))
314       {
315         mx_msg_close(m, &msg);
316         return 1;
317       }
318     }
319     mutt_endwin();
320 
321     pid = filter_create(cmd, &fp_out, NULL, NULL);
322     if (pid < 0)
323     {
324       mutt_perror(_("Can't create filter process"));
325       mx_msg_close(m, &msg);
326       return 1;
327     }
328 
329     OptKeepQuiet = true;
330     pipe_msg(m, en->email, msg, fp_out, decode, print);
331     mx_msg_close(m, &msg);
332     mutt_file_fclose(&fp_out);
333     rc = filter_wait(pid);
334     OptKeepQuiet = false;
335   }
336   else
337   {
338     /* handle tagged messages */
339     if ((WithCrypto != 0) && decode)
340     {
341       STAILQ_FOREACH(en, el, entries)
342       {
343         struct Message *msg = mx_msg_open(m, en->email->msgno);
344         if (msg)
345         {
346           mutt_parse_mime_message(en->email, msg->fp);
347           mutt_message_hook(m, en->email, MUTT_MESSAGE_HOOK);
348           mx_msg_close(m, &msg);
349         }
350         if ((en->email->security & SEC_ENCRYPT) &&
351             !crypt_valid_passphrase(en->email->security))
352         {
353           return 1;
354         }
355       }
356     }
357 
358     if (split)
359     {
360       STAILQ_FOREACH(en, el, entries)
361       {
362         mutt_message_hook(m, en->email, MUTT_MESSAGE_HOOK);
363         mutt_endwin();
364         pid = filter_create(cmd, &fp_out, NULL, NULL);
365         if (pid < 0)
366         {
367           mutt_perror(_("Can't create filter process"));
368           return 1;
369         }
370         OptKeepQuiet = true;
371         pipe_msg(m, en->email, NULL, fp_out, decode, print);
372         /* add the message separator */
373         if (sep)
374           fputs(sep, fp_out);
375         mutt_file_fclose(&fp_out);
376         if (filter_wait(pid) != 0)
377           rc = 1;
378         OptKeepQuiet = false;
379       }
380     }
381     else
382     {
383       mutt_endwin();
384       pid = filter_create(cmd, &fp_out, NULL, NULL);
385       if (pid < 0)
386       {
387         mutt_perror(_("Can't create filter process"));
388         return 1;
389       }
390       OptKeepQuiet = true;
391       STAILQ_FOREACH(en, el, entries)
392       {
393         mutt_message_hook(m, en->email, MUTT_MESSAGE_HOOK);
394         pipe_msg(m, en->email, NULL, fp_out, decode, print);
395         /* add the message separator */
396         if (sep)
397           fputs(sep, fp_out);
398       }
399       mutt_file_fclose(&fp_out);
400       if (filter_wait(pid) != 0)
401         rc = 1;
402       OptKeepQuiet = false;
403     }
404   }
405 
406   const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
407   if ((rc != 0) || c_wait_key)
408     mutt_any_key_to_continue(NULL);
409   return rc;
410 }
411 
412 /**
413  * mutt_pipe_message - Pipe a message
414  * @param m  Mailbox
415  * @param el List of Emails to pipe
416  */
mutt_pipe_message(struct Mailbox * m,struct EmailList * el)417 void mutt_pipe_message(struct Mailbox *m, struct EmailList *el)
418 {
419   if (!m || !el)
420     return;
421 
422   struct Buffer *buf = mutt_buffer_pool_get();
423 
424   if (mutt_buffer_get_field(_("Pipe to command: "), buf, MUTT_CMD, false, NULL,
425                             NULL, NULL) != 0)
426   {
427     goto cleanup;
428   }
429 
430   if (mutt_buffer_len(buf) == 0)
431     goto cleanup;
432 
433   mutt_buffer_expand_path(buf);
434   const bool c_pipe_decode = cs_subset_bool(NeoMutt->sub, "pipe_decode");
435   const bool c_pipe_split = cs_subset_bool(NeoMutt->sub, "pipe_split");
436   const char *const c_pipe_sep = cs_subset_string(NeoMutt->sub, "pipe_sep");
437   pipe_message(m, el, mutt_buffer_string(buf), c_pipe_decode, false, c_pipe_split, c_pipe_sep);
438 
439 cleanup:
440   mutt_buffer_pool_release(&buf);
441 }
442 
443 /**
444  * mutt_print_message - Print a message
445  * @param m  Mailbox
446  * @param el List of Emails to print
447  */
mutt_print_message(struct Mailbox * m,struct EmailList * el)448 void mutt_print_message(struct Mailbox *m, struct EmailList *el)
449 {
450   if (!m || !el)
451     return;
452 
453   const enum QuadOption c_print = cs_subset_quad(NeoMutt->sub, "print");
454   const char *const c_print_command =
455       cs_subset_string(NeoMutt->sub, "print_command");
456   if (c_print && !c_print_command)
457   {
458     mutt_message(_("No printing command has been defined"));
459     return;
460   }
461 
462   int msg_count = 0;
463   struct EmailNode *en = NULL;
464   STAILQ_FOREACH(en, el, entries)
465   {
466     msg_count++;
467   }
468 
469   if (query_quadoption(c_print, (msg_count == 1) ?
470                                     _("Print message?") :
471                                     _("Print tagged messages?")) != MUTT_YES)
472   {
473     return;
474   }
475 
476   const bool c_print_decode = cs_subset_bool(NeoMutt->sub, "print_decode");
477   const bool c_print_split = cs_subset_bool(NeoMutt->sub, "print_split");
478   if (pipe_message(m, el, c_print_command, c_print_decode, true, c_print_split, "\f") == 0)
479     mutt_message(ngettext("Message printed", "Messages printed", msg_count));
480   else
481   {
482     mutt_message(ngettext("Message could not be printed",
483                           "Messages could not be printed", msg_count));
484   }
485 }
486 
487 /**
488  * mutt_select_sort - Ask the user for a sort method
489  * @param reverse If true make it a reverse sort
490  * @retval true The sort type changed
491  */
mutt_select_sort(bool reverse)492 bool mutt_select_sort(bool reverse)
493 {
494   enum SortType sort = SORT_DATE;
495 
496   switch (mutt_multi_choice(reverse ?
497                                 /* L10N: The highlighted letters must match the "Sort" options */
498                                 _("Rev-Sort "
499                                   "(d)ate,(f)rm,(r)ecv,(s)ubj,t(o),(t)hread,(u)"
500                                   "nsort,si(z)e,s(c)ore,s(p)am,(l)abel?") :
501                                 /* L10N: The highlighted letters must match the "Rev-Sort" options */
502                                 _("Sort "
503                                   "(d)ate,(f)rm,(r)ecv,(s)ubj,t(o),(t)hread,(u)"
504                                   "nsort,si(z)e,s(c)ore,s(p)am,(l)abel?"),
505                             /* L10N: These must match the highlighted letters from "Sort" and "Rev-Sort" */
506                             _("dfrsotuzcpl")))
507   {
508     case -1: /* abort - don't resort */
509       return -1;
510 
511     case 1: /* (d)ate */
512       sort = SORT_DATE;
513       break;
514 
515     case 2: /* (f)rm */
516       sort = SORT_FROM;
517       break;
518 
519     case 3: /* (r)ecv */
520       sort = SORT_RECEIVED;
521       break;
522 
523     case 4: /* (s)ubj */
524       sort = SORT_SUBJECT;
525       break;
526 
527     case 5: /* t(o) */
528       sort = SORT_TO;
529       break;
530 
531     case 6: /* (t)hread */
532       sort = SORT_THREADS;
533       break;
534 
535     case 7: /* (u)nsort */
536       sort = SORT_ORDER;
537       break;
538 
539     case 8: /* si(z)e */
540       sort = SORT_SIZE;
541       break;
542 
543     case 9: /* s(c)ore */
544       sort = SORT_SCORE;
545       break;
546 
547     case 10: /* s(p)am */
548       sort = SORT_SPAM;
549       break;
550 
551     case 11: /* (l)abel */
552       sort = SORT_LABEL;
553       break;
554   }
555 
556   const unsigned char c_use_threads =
557       cs_subset_enum(NeoMutt->sub, "use_threads");
558   const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
559   int rc = CSR_ERR_CODE;
560   if ((sort != SORT_THREADS) || (c_use_threads == UT_UNSET))
561   {
562     if ((sort != SORT_THREADS) && (c_sort & SORT_LAST))
563       sort |= SORT_LAST;
564     if (reverse)
565       sort |= SORT_REVERSE;
566 
567     rc = cs_subset_str_native_set(NeoMutt->sub, "sort", sort, NULL);
568   }
569   else
570   {
571     assert((c_sort & SORT_MASK) != SORT_THREADS); /* See index_config_observer() */
572     /* Preserve the value of $sort, and toggle whether we are threaded. */
573     switch (c_use_threads)
574     {
575       case UT_FLAT:
576         rc = cs_subset_str_native_set(NeoMutt->sub, "use_threads",
577                                       reverse ? UT_REVERSE : UT_THREADS, NULL);
578         break;
579       case UT_THREADS:
580         rc = cs_subset_str_native_set(NeoMutt->sub, "use_threads",
581                                       reverse ? UT_REVERSE : UT_FLAT, NULL);
582         break;
583       case UT_REVERSE:
584         rc = cs_subset_str_native_set(NeoMutt->sub, "use_threads",
585                                       reverse ? UT_FLAT : UT_THREADS, NULL);
586         break;
587       default:
588         assert(false);
589     }
590   }
591 
592   return ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE));
593 }
594 
595 /**
596  * mutt_shell_escape - Invoke a command in a subshell
597  * @retval true A command was invoked (no matter what its result)
598  * @retval false No command was invoked
599  */
mutt_shell_escape(void)600 bool mutt_shell_escape(void)
601 {
602   char buf[1024];
603 
604   buf[0] = '\0';
605   if (mutt_get_field(_("Shell command: "), buf, sizeof(buf), MUTT_CMD, false, NULL, NULL) != 0)
606   {
607     return false;
608   }
609 
610   const char *const c_shell = cs_subset_string(NeoMutt->sub, "shell");
611   if ((buf[0] == '\0') && c_shell)
612     mutt_str_copy(buf, c_shell, sizeof(buf));
613   if (buf[0] == '\0')
614   {
615     return false;
616   }
617 
618   msgwin_clear_text();
619   mutt_endwin();
620   fflush(stdout);
621   int rc = mutt_system(buf);
622   if (rc == -1)
623     mutt_debug(LL_DEBUG1, "Error running \"%s\"", buf);
624 
625   const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
626   if ((rc != 0) || c_wait_key)
627     mutt_any_key_to_continue(NULL);
628 
629   return true;
630 }
631 
632 /**
633  * mutt_enter_command - Enter a neomutt command
634  */
mutt_enter_command(void)635 void mutt_enter_command(void)
636 {
637   char buf[1024] = { 0 };
638 
639   window_redraw(NULL);
640   /* if enter is pressed after : with no command, just return */
641   if ((mutt_get_field(":", buf, sizeof(buf), MUTT_COMMAND, false, NULL, NULL) != 0) ||
642       (buf[0] == '\0'))
643   {
644     return;
645   }
646 
647   struct Buffer err = mutt_buffer_make(256);
648 
649   /* check if buf is a valid icommand, else fall back quietly to parse_rc_lines */
650   enum CommandResult rc = mutt_parse_icommand(buf, &err);
651   if (!mutt_buffer_is_empty(&err))
652   {
653     /* since errbuf could potentially contain printf() sequences in it,
654      * we must call mutt_error() in this fashion so that vsprintf()
655      * doesn't expect more arguments that we passed */
656     if (rc == MUTT_CMD_ERROR)
657       mutt_error("%s", err.data);
658     else
659       mutt_warning("%s", err.data);
660   }
661   else if (rc != MUTT_CMD_SUCCESS)
662   {
663     rc = mutt_parse_rc_line(buf, &err);
664     if (!mutt_buffer_is_empty(&err))
665     {
666       if (rc == MUTT_CMD_SUCCESS) /* command succeeded with message */
667         mutt_message("%s", err.data);
668       else if (rc == MUTT_CMD_ERROR)
669         mutt_error("%s", err.data);
670       else if (rc == MUTT_CMD_WARNING)
671         mutt_warning("%s", err.data);
672     }
673   }
674   /* else successful command */
675 
676   mutt_buffer_dealloc(&err);
677 }
678 
679 /**
680  * mutt_display_address - Display the address of a message
681  * @param env Envelope containing address
682  */
mutt_display_address(struct Envelope * env)683 void mutt_display_address(struct Envelope *env)
684 {
685   const char *pfx = NULL;
686   char buf[128];
687 
688   struct AddressList *al = mutt_get_address(env, &pfx);
689   if (!al)
690     return;
691 
692   /* Note: We don't convert IDNA to local representation this time.
693    * That is intentional, so the user has an opportunity to copy &
694    * paste the on-the-wire form of the address to other, IDN-unable
695    * software.  */
696   buf[0] = '\0';
697   mutt_addrlist_write(al, buf, sizeof(buf), false);
698   mutt_message("%s: %s", pfx, buf);
699 }
700 
701 /**
702  * set_copy_flags - Set the flags for a message copy
703  * @param[in]  e               Email
704  * @param[in]  transform_opt   Transformation, e.g. #TRANSFORM_DECRYPT
705  * @param[out] cmflags         Flags, see #CopyMessageFlags
706  * @param[out] chflags         Flags, see #CopyHeaderFlags
707  */
set_copy_flags(struct Email * e,enum MessageTransformOpt transform_opt,CopyMessageFlags * cmflags,CopyHeaderFlags * chflags)708 static void set_copy_flags(struct Email *e, enum MessageTransformOpt transform_opt,
709                            CopyMessageFlags *cmflags, CopyHeaderFlags *chflags)
710 {
711   *cmflags = MUTT_CM_NO_FLAGS;
712   *chflags = CH_UPDATE_LEN;
713 
714   const bool need_decrypt =
715       (transform_opt == TRANSFORM_DECRYPT) && (e->security & SEC_ENCRYPT);
716   const bool want_pgp = (WithCrypto & APPLICATION_PGP);
717   const bool want_smime = (WithCrypto & APPLICATION_SMIME);
718   const bool is_pgp = mutt_is_application_pgp(e->body) & SEC_ENCRYPT;
719   const bool is_smime = mutt_is_application_smime(e->body) & SEC_ENCRYPT;
720 
721   if (need_decrypt && want_pgp && mutt_is_multipart_encrypted(e->body))
722   {
723     *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
724     *cmflags = MUTT_CM_DECODE_PGP;
725   }
726   else if (need_decrypt && want_pgp && is_pgp)
727   {
728     *chflags = CH_XMIT | CH_MIME | CH_TXTPLAIN;
729     *cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
730   }
731   else if (need_decrypt && want_smime && is_smime)
732   {
733     *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
734     *cmflags = MUTT_CM_DECODE_SMIME;
735   }
736   else if (transform_opt == TRANSFORM_DECODE)
737   {
738     *chflags = CH_XMIT | CH_MIME | CH_TXTPLAIN | CH_DECODE; // then decode RFC2047
739     *cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
740     const bool c_copy_decode_weed =
741         cs_subset_bool(NeoMutt->sub, "copy_decode_weed");
742     if (c_copy_decode_weed)
743     {
744       *chflags |= CH_WEED; // and respect $weed
745       *cmflags |= MUTT_CM_WEED;
746     }
747   }
748 }
749 
750 /**
751  * mutt_save_message_ctx - Save a message to a given mailbox
752  * @param m_src            Mailbox to copy from
753  * @param e                Email
754  * @param save_opt         Copy or move, e.g. #SAVE_MOVE
755  * @param transform_opt    Transformation, e.g. #TRANSFORM_DECRYPT
756  * @param m_dst            Mailbox to save to
757  * @retval  0 Success
758  * @retval -1 Error
759  */
mutt_save_message_ctx(struct Mailbox * m_src,struct Email * e,enum MessageSaveOpt save_opt,enum MessageTransformOpt transform_opt,struct Mailbox * m_dst)760 int mutt_save_message_ctx(struct Mailbox *m_src, struct Email *e, enum MessageSaveOpt save_opt,
761                           enum MessageTransformOpt transform_opt, struct Mailbox *m_dst)
762 {
763   CopyMessageFlags cmflags = MUTT_CM_NO_FLAGS;
764   CopyHeaderFlags chflags = CH_NO_FLAGS;
765   int rc;
766 
767   set_copy_flags(e, transform_opt, &cmflags, &chflags);
768 
769   struct Message *msg = mx_msg_open(m_src, e->msgno);
770   if (msg && transform_opt != TRANSFORM_NONE)
771   {
772     mutt_parse_mime_message(e, msg->fp);
773   }
774 
775   rc = mutt_append_message(m_dst, m_src, e, msg, cmflags, chflags);
776   mx_msg_close(m_src, &msg);
777   if (rc != 0)
778     return rc;
779 
780   if (save_opt == SAVE_MOVE)
781   {
782     mutt_set_flag(m_src, e, MUTT_DELETE, true);
783     mutt_set_flag(m_src, e, MUTT_PURGE, true);
784     const bool c_delete_untag = cs_subset_bool(NeoMutt->sub, "delete_untag");
785     if (c_delete_untag)
786       mutt_set_flag(m_src, e, MUTT_TAG, false);
787   }
788 
789   return 0;
790 }
791 
792 /**
793  * mutt_save_message - Save an email
794  * @param m                Mailbox
795  * @param el               List of Emails to save
796  * @param save_opt         Copy or move, e.g. #SAVE_MOVE
797  * @param transform_opt    Transformation, e.g. #TRANSFORM_DECRYPT
798  * @retval  0 Copy/save was successful
799  * @retval -1 Error/abort
800  */
mutt_save_message(struct Mailbox * m,struct EmailList * el,enum MessageSaveOpt save_opt,enum MessageTransformOpt transform_opt)801 int mutt_save_message(struct Mailbox *m, struct EmailList *el,
802                       enum MessageSaveOpt save_opt, enum MessageTransformOpt transform_opt)
803 {
804   if (!el || STAILQ_EMPTY(el))
805     return -1;
806 
807   int rc = -1;
808   int tagged_progress_count = 0;
809   unsigned int msg_count = 0;
810   struct Mailbox *m_save = NULL;
811 
812   struct Buffer *buf = mutt_buffer_pool_get();
813   struct stat st = { 0 };
814   struct EmailNode *en = NULL;
815 
816   STAILQ_FOREACH(en, el, entries)
817   {
818     msg_count++;
819   }
820   en = STAILQ_FIRST(el);
821 
822   const SecurityFlags security_flags = WithCrypto ? en->email->security : SEC_NO_FLAGS;
823   const bool is_passphrase_needed = security_flags & SEC_ENCRYPT;
824 
825   const char *prompt = NULL;
826   const char *progress_msg = NULL;
827 
828   // Set prompt and progress_msg
829   switch (save_opt)
830   {
831     case SAVE_COPY:
832       // L10N: Progress meter message when copying tagged messages
833       progress_msg = (msg_count > 1) ? _("Copying tagged messages...") : NULL;
834       switch (transform_opt)
835       {
836         case TRANSFORM_NONE:
837           prompt = (msg_count > 1) ? _("Copy tagged to mailbox") : _("Copy to mailbox");
838           break;
839         case TRANSFORM_DECRYPT:
840           prompt = (msg_count > 1) ? _("Decrypt-copy tagged to mailbox") :
841                                      _("Decrypt-copy to mailbox");
842           break;
843         case TRANSFORM_DECODE:
844           prompt = (msg_count > 1) ? _("Decode-copy tagged to mailbox") :
845                                      _("Decode-copy to mailbox");
846           break;
847       }
848       break;
849 
850     case SAVE_MOVE:
851       // L10N: Progress meter message when saving tagged messages
852       progress_msg = (msg_count > 1) ? _("Saving tagged messages...") : NULL;
853       switch (transform_opt)
854       {
855         case TRANSFORM_NONE:
856           prompt = (msg_count > 1) ? _("Save tagged to mailbox") : _("Save to mailbox");
857           break;
858         case TRANSFORM_DECRYPT:
859           prompt = (msg_count > 1) ? _("Decrypt-save tagged to mailbox") :
860                                      _("Decrypt-save to mailbox");
861           break;
862         case TRANSFORM_DECODE:
863           prompt = (msg_count > 1) ? _("Decode-save tagged to mailbox") :
864                                      _("Decode-save to mailbox");
865           break;
866       }
867       break;
868   }
869 
870   mutt_message_hook(m, en->email, MUTT_MESSAGE_HOOK);
871   mutt_default_save(buf->data, buf->dsize, en->email);
872   mutt_buffer_fix_dptr(buf);
873   mutt_buffer_pretty_mailbox(buf);
874 
875   if (mutt_buffer_enter_fname(prompt, buf, false, NULL, false, NULL, NULL,
876                               MUTT_SEL_NO_FLAGS) == -1)
877   {
878     goto cleanup;
879   }
880 
881   size_t pathlen = mutt_buffer_len(buf);
882   if (pathlen == 0)
883     goto cleanup;
884 
885   /* Trim any trailing '/' */
886   if (buf->data[pathlen - 1] == '/')
887     buf->data[pathlen - 1] = '\0';
888 
889   /* This is an undocumented feature of ELM pointed out to me by Felix von
890    * Leitner <leitner@prz.fu-berlin.de> */
891   if (mutt_buffer_len(&LastSaveFolder) == 0)
892     mutt_buffer_alloc(&LastSaveFolder, PATH_MAX);
893   if (mutt_str_equal(mutt_buffer_string(buf), "."))
894     mutt_buffer_copy(buf, &LastSaveFolder);
895   else
896     mutt_buffer_strcpy(&LastSaveFolder, mutt_buffer_string(buf));
897 
898   mutt_buffer_expand_path(buf);
899 
900   /* check to make sure that this file is really the one the user wants */
901   if (mutt_save_confirm(mutt_buffer_string(buf), &st) != 0)
902     goto cleanup;
903 
904   if (is_passphrase_needed && (transform_opt != TRANSFORM_NONE) &&
905       !crypt_valid_passphrase(security_flags))
906   {
907     rc = -1;
908     goto errcleanup;
909   }
910 
911   mutt_message(_("Copying to %s..."), mutt_buffer_string(buf));
912 
913 #ifdef USE_IMAP
914   enum MailboxType mailbox_type = imap_path_probe(mutt_buffer_string(buf), NULL);
915   if ((m->type == MUTT_IMAP) && (transform_opt == TRANSFORM_NONE) && (mailbox_type == MUTT_IMAP))
916   {
917     rc = imap_copy_messages(m, el, mutt_buffer_string(buf), save_opt);
918     switch (rc)
919     {
920       /* success */
921       case 0:
922         mutt_clear_error();
923         rc = 0;
924         goto cleanup;
925       /* non-fatal error: continue to fetch/append */
926       case 1:
927         break;
928       /* fatal error, abort */
929       case -1:
930         goto errcleanup;
931     }
932   }
933 #endif
934 
935   mutt_file_resolve_symlink(buf);
936   m_save = mx_path_resolve(mutt_buffer_string(buf));
937   bool old_append = m_save->append;
938   OpenMailboxFlags mbox_flags = MUTT_NEWFOLDER;
939   /* Display a tagged message progress counter, rather than (for
940    * IMAP) a per-message progress counter */
941   if (msg_count > 1)
942     mbox_flags |= MUTT_QUIET;
943   if (!mx_mbox_open(m_save, mbox_flags))
944   {
945     rc = -1;
946     mailbox_free(&m_save);
947     goto errcleanup;
948   }
949   m_save->append = true;
950 
951 #ifdef USE_COMP_MBOX
952   /* If we're saving to a compressed mailbox, the stats won't be updated
953    * until the next open.  Until then, improvise. */
954   struct Mailbox *m_comp = NULL;
955   if (m_save->compress_info)
956   {
957     m_comp = mailbox_find(m_save->realpath);
958   }
959   /* We probably haven't been opened yet */
960   if (m_comp && (m_comp->msg_count == 0))
961     m_comp = NULL;
962 #endif
963   if (msg_count == 1)
964   {
965     rc = mutt_save_message_ctx(m, en->email, save_opt, transform_opt, m_save);
966     if (rc != 0)
967     {
968       mx_mbox_close(m_save);
969       m_save->append = old_append;
970       goto errcleanup;
971     }
972 #ifdef USE_COMP_MBOX
973     if (m_comp)
974     {
975       m_comp->msg_count++;
976       if (!en->email->read)
977       {
978         m_comp->msg_unread++;
979         if (!en->email->old)
980           m_comp->msg_new++;
981       }
982       if (en->email->flagged)
983         m_comp->msg_flagged++;
984     }
985 #endif
986   }
987   else
988   {
989     rc = 0;
990 
991 #ifdef USE_NOTMUCH
992     if (m->type == MUTT_NOTMUCH)
993       nm_db_longrun_init(m, true);
994 #endif
995     struct Progress *progress = progress_new(progress_msg, MUTT_PROGRESS_WRITE, msg_count);
996     STAILQ_FOREACH(en, el, entries)
997     {
998       progress_update(progress, ++tagged_progress_count, -1);
999       mutt_message_hook(m, en->email, MUTT_MESSAGE_HOOK);
1000       rc = mutt_save_message_ctx(m, en->email, save_opt, transform_opt, m_save);
1001       if (rc != 0)
1002         break;
1003 #ifdef USE_COMP_MBOX
1004       if (m_comp)
1005       {
1006         struct Email *e2 = en->email;
1007         m_comp->msg_count++;
1008         if (!e2->read)
1009         {
1010           m_comp->msg_unread++;
1011           if (!e2->old)
1012             m_comp->msg_new++;
1013         }
1014         if (e2->flagged)
1015           m_comp->msg_flagged++;
1016       }
1017 #endif
1018     }
1019     progress_free(&progress);
1020 
1021 #ifdef USE_NOTMUCH
1022     if (m->type == MUTT_NOTMUCH)
1023       nm_db_longrun_done(m);
1024 #endif
1025     if (rc != 0)
1026     {
1027       mx_mbox_close(m_save);
1028       m_save->append = old_append;
1029       goto errcleanup;
1030     }
1031   }
1032 
1033   const bool need_mailbox_cleanup =
1034       ((m_save->type == MUTT_MBOX) || (m_save->type == MUTT_MMDF));
1035 
1036   mx_mbox_close(m_save);
1037   m_save->append = old_append;
1038 
1039   if (need_mailbox_cleanup)
1040     mutt_mailbox_cleanup(mutt_buffer_string(buf), &st);
1041 
1042   mutt_clear_error();
1043   rc = 0;
1044 
1045 errcleanup:
1046   if (rc != 0)
1047   {
1048     switch (save_opt)
1049     {
1050       case SAVE_MOVE:
1051         if (msg_count > 1)
1052         {
1053           // L10N: Message when an index tagged save operation fails for some reason
1054           mutt_error(_("Error saving tagged messages"));
1055         }
1056         else
1057         {
1058           // L10N: Message when an index/pager save operation fails for some reason
1059           mutt_error(_("Error saving message"));
1060         }
1061         break;
1062       case SAVE_COPY:
1063         if (msg_count > 1)
1064         {
1065           // L10N: Message when an index tagged copy operation fails for some reason
1066           mutt_error(_("Error copying tagged messages"));
1067         }
1068         else
1069         {
1070           // L10N: Message when an index/pager copy operation fails for some reason
1071           mutt_error(_("Error copying message"));
1072         }
1073         break;
1074     }
1075   }
1076 
1077   if (m_save && (m_save->flags == MB_HIDDEN))
1078     mailbox_free(&m_save);
1079 
1080 cleanup:
1081   mutt_buffer_pool_release(&buf);
1082   return rc;
1083 }
1084 
1085 /**
1086  * mutt_edit_content_type - Edit the content type of an attachment
1087  * @param e  Email
1088  * @param b  Attachment
1089  * @param fp File handle to the attachment
1090  * @retval true A Any change is made
1091  *
1092  * recvattach requires the return code to know when to regenerate the actx.
1093  */
mutt_edit_content_type(struct Email * e,struct Body * b,FILE * fp)1094 bool mutt_edit_content_type(struct Email *e, struct Body *b, FILE *fp)
1095 {
1096   char buf[1024];
1097   char obuf[1024];
1098   char tmp[256];
1099   char charset[256];
1100 
1101   bool charset_changed = false;
1102   bool type_changed = false;
1103   bool structure_changed = false;
1104 
1105   char *cp = mutt_param_get(&b->parameter, "charset");
1106   mutt_str_copy(charset, cp, sizeof(charset));
1107 
1108   snprintf(buf, sizeof(buf), "%s/%s", TYPE(b), b->subtype);
1109   mutt_str_copy(obuf, buf, sizeof(obuf));
1110   if (!TAILQ_EMPTY(&b->parameter))
1111   {
1112     size_t l = strlen(buf);
1113     struct Parameter *np = NULL;
1114     TAILQ_FOREACH(np, &b->parameter, entries)
1115     {
1116       mutt_addr_cat(tmp, sizeof(tmp), np->value, MimeSpecials);
1117       l += snprintf(buf + l, sizeof(buf) - l, "; %s=%s", np->attribute, tmp);
1118       if (l >= sizeof(buf))
1119       {
1120         // L10N: e.g. "text/plain; charset=UTF-8; ..."
1121         mutt_error(_("Content type is too long"));
1122         return false;
1123       }
1124     }
1125   }
1126 
1127   if ((mutt_get_field("Content-Type: ", buf, sizeof(buf), MUTT_COMP_NO_FLAGS,
1128                       false, NULL, NULL) != 0) ||
1129       (buf[0] == '\0'))
1130   {
1131     return false;
1132   }
1133 
1134   /* clean up previous junk */
1135   mutt_param_free(&b->parameter);
1136   FREE(&b->subtype);
1137 
1138   mutt_parse_content_type(buf, b);
1139 
1140   snprintf(tmp, sizeof(tmp), "%s/%s", TYPE(b), NONULL(b->subtype));
1141   type_changed = !mutt_istr_equal(tmp, obuf);
1142   charset_changed =
1143       !mutt_istr_equal(charset, mutt_param_get(&b->parameter, "charset"));
1144 
1145   /* if in send mode, check for conversion - current setting is default. */
1146 
1147   if (!e && (b->type == TYPE_TEXT) && charset_changed)
1148   {
1149     snprintf(tmp, sizeof(tmp), _("Convert to %s upon sending?"),
1150              mutt_param_get(&b->parameter, "charset"));
1151     enum QuadOption ans = mutt_yesorno(tmp, b->noconv ? MUTT_NO : MUTT_YES);
1152     if (ans != MUTT_ABORT)
1153       b->noconv = (ans == MUTT_NO);
1154   }
1155 
1156   /* inform the user */
1157 
1158   snprintf(tmp, sizeof(tmp), "%s/%s", TYPE(b), NONULL(b->subtype));
1159   if (type_changed)
1160     mutt_message(_("Content-Type changed to %s"), tmp);
1161   if ((b->type == TYPE_TEXT) && charset_changed)
1162   {
1163     if (type_changed)
1164       mutt_sleep(1);
1165     mutt_message(b->noconv ? _("Character set changed to %s; not converting") :
1166                              _("Character set changed to %s; converting"),
1167                  mutt_param_get(&b->parameter, "charset"));
1168   }
1169 
1170   b->force_charset |= charset_changed;
1171 
1172   if (!is_multipart(b) && b->parts)
1173   {
1174     structure_changed = true;
1175     mutt_body_free(&b->parts);
1176   }
1177   if (!mutt_is_message_type(b->type, b->subtype) && b->email)
1178   {
1179     structure_changed = true;
1180     b->email->body = NULL;
1181     email_free(&b->email);
1182   }
1183 
1184   if (fp && !b->parts && (is_multipart(b) || mutt_is_message_type(b->type, b->subtype)))
1185   {
1186     structure_changed = true;
1187     mutt_parse_part(fp, b);
1188   }
1189 
1190   if ((WithCrypto != 0) && e)
1191   {
1192     if (e->body == b)
1193       e->security = SEC_NO_FLAGS;
1194 
1195     e->security |= crypt_query(b);
1196   }
1197 
1198   return structure_changed | type_changed;
1199 }
1200 
1201 /**
1202  * check_traditional_pgp - Check for an inline PGP content
1203  * @param m Mailbox
1204  * @param e Email to check
1205  * @retval true Message contains inline PGP content
1206  */
check_traditional_pgp(struct Mailbox * m,struct Email * e)1207 static bool check_traditional_pgp(struct Mailbox *m, struct Email *e)
1208 {
1209   bool rc = false;
1210 
1211   e->security |= PGP_TRADITIONAL_CHECKED;
1212 
1213   struct Message *msg = mx_msg_open(m, e->msgno);
1214   if (msg)
1215   {
1216     mutt_parse_mime_message(e, msg->fp);
1217     if (crypt_pgp_check_traditional(msg->fp, e->body, false))
1218     {
1219       e->security = crypt_query(e->body);
1220       rc = true;
1221     }
1222 
1223     e->security |= PGP_TRADITIONAL_CHECKED;
1224     mx_msg_close(m, &msg);
1225   }
1226   return rc;
1227 }
1228 
1229 /**
1230  * mutt_check_traditional_pgp - Check if a message has inline PGP content
1231  * @param m  Mailbox
1232  * @param el List of Emails to check
1233  * @retval true Message contains inline PGP content
1234  */
mutt_check_traditional_pgp(struct Mailbox * m,struct EmailList * el)1235 bool mutt_check_traditional_pgp(struct Mailbox *m, struct EmailList *el)
1236 {
1237   bool rc = false;
1238   struct EmailNode *en = NULL;
1239   STAILQ_FOREACH(en, el, entries)
1240   {
1241     if (!(en->email->security & PGP_TRADITIONAL_CHECKED))
1242       rc = check_traditional_pgp(m, en->email) || rc;
1243   }
1244 
1245   return rc;
1246 }
1247 
1248 /**
1249  * mutt_check_stats - Forcibly update mailbox stats
1250  */
mutt_check_stats(struct Mailbox * m)1251 void mutt_check_stats(struct Mailbox *m)
1252 {
1253   mutt_mailbox_check(m, MUTT_MAILBOX_CHECK_FORCE | MUTT_MAILBOX_CHECK_FORCE_STATS);
1254 }
1255