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