3 * Save/restore and GUI list postponed emails
6 * Copyright (C) 1996-2002,2012-2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1999-2002,2004 Thomas Roessler <roessler@does-not-exist.org>
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
25 * @page postpone Save/restore and GUI list postponed emails
27 * Save/restore and GUI list postponed emails
38 #include "mutt/mutt.h"
39 #include "config/lib.h"
40 #include "email/lib.h"
44 #include "format_flags.h"
49 #include "mutt_logging.h"
50 #include "mutt_menu.h"
51 #include "mutt_thread.h"
52 #include "mutt_window.h"
55 #include "ncrypt/ncrypt.h"
65 #include "imap/imap.h"
68 static const struct Mapping PostponeHelp[] = {
69 { N_("Exit"), OP_EXIT },
70 { N_("Del"), OP_DELETE },
71 { N_("Undel"), OP_UNDELETE },
72 { N_("Help"), OP_HELP },
76 static short PostCount = 0;
77 static bool UpdateNumPostponed = false;
80 * mutt_num_postponed - Return the number of postponed messages
81 * @param m currently selected mailbox
83 * * false Use a cached value if costly to get a fresh count (IMAP)
85 * @retval num Postponed messages
87 int mutt_num_postponed(struct Mailbox *m, bool force)
91 static time_t LastModify = 0;
92 static char *OldPostponed = NULL;
94 if (UpdateNumPostponed)
96 UpdateNumPostponed = false;
100 if (mutt_str_strcmp(C_Postponed, OldPostponed) != 0)
103 OldPostponed = mutt_str_strdup(C_Postponed);
111 // We currently are in the C_Postponed mailbox so just pick the current status
112 if (m && (mutt_str_strcmp(C_Postponed, m->realpath) == 0))
114 PostCount = m->msg_count - m->msg_deleted;
119 /* LastModify is useless for IMAP */
120 if (imap_path_probe(C_Postponed, NULL) == MUTT_IMAP)
126 newpc = imap_path_status(C_Postponed, false);
130 mutt_debug(LL_DEBUG3, "%d postponed IMAP messages found\n", PostCount);
133 mutt_debug(LL_DEBUG3, "using old IMAP postponed count\n");
139 if (stat(C_Postponed, &st) == -1)
146 if (S_ISDIR(st.st_mode))
148 /* if we have a maildir mailbox, we need to stat the "new" dir */
149 struct Buffer *buf = mutt_buffer_pool_get();
151 mutt_buffer_printf(buf, "%s/new", C_Postponed);
152 if ((access(mutt_b2s(buf), F_OK) == 0) && (stat(mutt_b2s(buf), &st) == -1))
156 mutt_buffer_pool_release(&buf);
159 mutt_buffer_pool_release(&buf);
162 if (LastModify < st.st_mtime)
165 int optnews = OptNews;
167 LastModify = st.st_mtime;
169 if (access(C_Postponed, R_OK | F_OK) != 0)
170 return PostCount = 0;
175 struct Mailbox *m_post = mx_path_resolve(C_Postponed);
176 struct Context *ctx = mx_mbox_open(m_post, MUTT_NOSORT | MUTT_QUIET);
179 mailbox_free(&m_post);
183 PostCount = ctx->mailbox->msg_count;
184 mx_fastclose_mailbox(m_post);
196 * mutt_update_num_postponed - Force the update of the number of postponed messages
198 void mutt_update_num_postponed(void)
200 UpdateNumPostponed = true;
204 * post_make_entry - Format a menu item for the email list - Implements Menu::menu_make_entry()
206 static void post_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
208 struct Context *ctx = menu->data;
210 mutt_make_string_flags(buf, buflen, menu->indexwin->cols,
211 NONULL(C_IndexFormat), ctx, ctx->mailbox,
212 ctx->mailbox->emails[line], MUTT_FORMAT_ARROWCURSOR);
216 * select_msg - Create a Menu to select a postponed message
219 static struct Email *select_msg(struct Context *ctx)
225 struct Menu *menu = mutt_menu_new(MENU_POSTPONE);
226 menu->menu_make_entry = post_make_entry;
227 menu->max = ctx->mailbox->msg_count;
228 menu->title = _("Postponed Messages");
230 menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_POSTPONE, PostponeHelp);
231 mutt_menu_push_current(menu);
233 /* The postponed mailbox is setup to have sorting disabled, but the global
234 * C_Sort variable may indicate something different. Sorting has to be
235 * disabled while the postpone menu is being displayed. */
236 const short orig_sort = C_Sort;
241 const int op = mutt_menu_loop(menu);
246 /* should deleted draft messages be saved in the trash folder? */
247 mutt_set_flag(ctx->mailbox, ctx->mailbox->emails[menu->current],
248 MUTT_DELETE, (op == OP_DELETE));
249 PostCount = ctx->mailbox->msg_count - ctx->mailbox->msg_deleted;
250 if (C_Resolve && (menu->current < menu->max - 1))
252 menu->oldcurrent = menu->current;
254 if (menu->current >= menu->top + menu->pagelen)
256 menu->top = menu->current;
257 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
260 menu->redraw |= REDRAW_MOTION_RESYNC;
263 menu->redraw |= REDRAW_CURRENT;
266 case OP_GENERIC_SELECT_ENTRY:
278 mutt_menu_pop_current(menu);
279 mutt_menu_free(&menu);
280 return (r > -1) ? ctx->mailbox->emails[r] : NULL;
284 * mutt_get_postponed - Recall a postponed message
285 * @param[in] ctx Context info, used when recalling a message to which we reply
286 * @param[in] hdr envelope/attachment info for recalled message
287 * @param[out] cur if message was a reply, 'cur' is set to the message which 'hdr' is in reply to
288 * @param[in] fcc fcc for the recalled message
289 * @param[in] fcclen max length of fcc
290 * @retval -1 Error/no messages
291 * @retval 0 Normal exit
292 * @retval #SEND_REPLY Recalled message is a reply
294 int mutt_get_postponed(struct Context *ctx, struct Email *hdr,
295 struct Email **cur, char *fcc, size_t fcclen)
300 struct Email *e = NULL;
301 int rc = SEND_POSTPONED;
302 const char *p = NULL;
303 struct Context *ctx_post = NULL;
305 struct Mailbox *m = mx_path_resolve(C_Postponed);
306 if (ctx->mailbox == m)
309 ctx_post = mx_mbox_open(m, MUTT_NOSORT);
314 mutt_error(_("No postponed messages"));
319 if (ctx_post->mailbox->msg_count == 0)
325 mx_mbox_close(&ctx_post);
326 mutt_error(_("No postponed messages"));
330 if (ctx_post->mailbox->msg_count == 1)
332 /* only one message, so just use that one. */
333 e = ctx_post->mailbox->emails[0];
335 else if (!(e = select_msg(ctx_post)))
340 mx_mbox_close(&ctx_post);
344 if (mutt_prepare_template(NULL, ctx_post->mailbox, hdr, e, false) < 0)
348 mx_fastclose_mailbox(ctx_post->mailbox);
354 /* finished with this message, so delete it. */
355 mutt_set_flag(ctx_post->mailbox, e, MUTT_DELETE, true);
356 mutt_set_flag(ctx_post->mailbox, e, MUTT_PURGE, true);
358 /* update the count for the status display */
359 PostCount = ctx_post->mailbox->msg_count - ctx_post->mailbox->msg_deleted;
361 /* avoid the "purge deleted messages" prompt */
362 int opt_delete = C_Delete;
367 mx_mbox_close(&ctx_post);
368 C_Delete = opt_delete;
370 struct ListNode *np = NULL, *tmp = NULL;
371 STAILQ_FOREACH_SAFE(np, &hdr->env->userhdrs, entries, tmp)
373 size_t plen = mutt_str_startswith(np->data, "X-Mutt-References:", CASE_IGNORE);
376 /* if a mailbox is currently open, look to see if the original message
377 * the user attempted to reply to is in this mailbox */
378 p = mutt_str_skip_email_wsp(np->data + plen);
379 if (!ctx->mailbox->id_hash)
380 ctx->mailbox->id_hash = mutt_make_id_hash(ctx->mailbox);
381 *cur = mutt_hash_find(ctx->mailbox->id_hash, p);
386 else if ((plen = mutt_str_startswith(np->data, "X-Mutt-Fcc:", CASE_IGNORE)))
388 p = mutt_str_skip_email_wsp(np->data + plen);
389 mutt_str_strfcpy(fcc, p, fcclen);
390 mutt_pretty_mailbox(fcc, fcclen);
392 /* note that x-mutt-fcc was present. we do this because we want to add a
393 * default fcc if the header was missing, but preserve the request of the
394 * user to not make a copy if the header field is present, but empty.
395 * see http://dev.mutt.org/trac/ticket/3653 */
396 rc |= SEND_POSTPONED_FCC;
398 else if (((WithCrypto & APPLICATION_PGP) != 0) &&
399 /* this is generated by old neomutt versions */
400 (mutt_str_startswith(np->data, "Pgp:", CASE_MATCH) ||
401 /* this is the new way */
402 mutt_str_startswith(np->data, "X-Mutt-PGP:", CASE_MATCH)))
404 hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_PGP);
405 hdr->security |= APPLICATION_PGP;
407 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
408 mutt_str_startswith(np->data, "X-Mutt-SMIME:", CASE_MATCH))
410 hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_SMIME);
411 hdr->security |= APPLICATION_SMIME;
415 else if (mutt_str_startswith(np->data, "X-Mutt-Mix:", CASE_MATCH))
417 mutt_list_free(&hdr->chain);
419 char *t = strtok(np->data + 11, " \t\n");
422 mutt_list_insert_tail(&hdr->chain, mutt_str_strdup(t));
423 t = strtok(NULL, " \t\n");
430 // skip header removal
435 STAILQ_REMOVE(&hdr->env->userhdrs, np, ListNode, entries);
440 if (C_CryptOpportunisticEncrypt)
441 crypt_opportunistic_encrypt(hdr);
447 * mutt_parse_crypt_hdr - Parse a crypto header string
448 * @param p Header string to parse
449 * @param set_empty_signas Allow an empty "Sign as"
450 * @param crypt_app App, e.g. #APPLICATION_PGP
451 * @retval num SecurityFlags, see #SecurityFlags
453 SecurityFlags mutt_parse_crypt_hdr(const char *p, bool set_empty_signas, SecurityFlags crypt_app)
455 char smime_cryptalg[1024] = { 0 };
456 char sign_as[1024] = { 0 };
458 SecurityFlags flags = SEC_NO_FLAGS;
463 p = mutt_str_skip_email_wsp(p);
464 for (; p[0] != '\0'; p++)
474 for (p += 2; (p[0] != '\0') && (p[0] != '>') &&
475 (q < (smime_cryptalg + sizeof(smime_cryptalg) - 1));
482 mutt_error(_("Illegal S/MIME header"));
492 flags |= SEC_ENCRYPT;
500 /* This used to be the micalg parameter.
502 * It's no longer needed, so we just skip the parameter in order
503 * to be able to recall old messages. */
508 for (p += 2; (p[0] != '\0') && (p[0] != '>'); p++)
512 mutt_error(_("Illegal crypto header"));
521 flags |= SEC_OPPENCRYPT;
527 flags |= SEC_AUTOCRYPT;
534 flags |= SEC_AUTOCRYPT_OVERRIDE;
546 (p[0] != '\0') && (*p != '>') && (q < (sign_as + sizeof(sign_as) - 1));
553 mutt_error(_("Illegal crypto header"));
562 mutt_error(_("Illegal crypto header"));
567 /* the cryptalg field must not be empty */
568 if (((WithCrypto & APPLICATION_SMIME) != 0) && *smime_cryptalg)
569 mutt_str_replace(&C_SmimeEncryptWith, smime_cryptalg);
571 /* Set {Smime,Pgp}SignAs, if desired. */
573 if (((WithCrypto & APPLICATION_PGP) != 0) && (crypt_app == APPLICATION_PGP) &&
574 (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
576 mutt_str_replace(&C_PgpSignAs, sign_as);
579 if (((WithCrypto & APPLICATION_SMIME) != 0) && (crypt_app == APPLICATION_SMIME) &&
580 (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
582 mutt_str_replace(&C_SmimeSignAs, sign_as);
589 * mutt_prepare_template - Prepare a message template
590 * @param fp If not NULL, file containing the template
591 * @param m If fp is NULL, the Mailbox containing the header with the template
592 * @param e_new The template is read into this Header
593 * @param e Email to recall/resend
594 * @param resend Set if resending (as opposed to recalling a postponed msg)
595 * Resent messages enable header weeding, and also
596 * discard any existing Message-ID and Mail-Followup-To
600 int mutt_prepare_template(FILE *fp, struct Mailbox *m, struct Email *e_new,
601 struct Email *e, bool resend)
603 struct Message *msg = NULL;
604 struct Body *b = NULL;
605 FILE *fp_body = NULL;
607 struct State s = { 0 };
608 SecurityFlags sec_type;
609 struct Envelope *protected_headers = NULL;
611 if (!fp && !(msg = mx_msg_open(m, e->msgno)))
619 /* parse the message header and MIME structure */
621 fseeko(fp, e->offset, SEEK_SET);
622 e_new->offset = e->offset;
623 /* enable header weeding for resent messages */
624 e_new->env = mutt_rfc822_read_header(fp, e_new, true, resend);
625 e_new->content->length = e->content->length;
626 mutt_parse_part(fp, e_new->content);
628 /* If resending a message, don't keep message_id or mail_followup_to.
629 * Otherwise, we are resuming a postponed message, and want to keep those
630 * headers if they exist. */
633 FREE(&e_new->env->message_id);
634 FREE(&e_new->env->mail_followup_to);
637 /* decrypt pgp/mime encoded messages */
639 if (((WithCrypto & APPLICATION_PGP) != 0) &&
640 (sec_type = mutt_is_multipart_encrypted(e_new->content)))
642 e_new->security |= sec_type;
643 if (!crypt_valid_passphrase(sec_type))
646 mutt_message(_("Decrypting message..."));
647 if ((crypt_pgp_decrypt_mime(fp, &fp_body, e_new->content, &b) == -1) || !b)
652 mutt_body_free(&e_new->content);
657 protected_headers = b->mime_headers;
658 b->mime_headers = NULL;
664 /* remove a potential multipart/signed layer - useful when
665 * resending messages */
666 if ((WithCrypto != 0) && mutt_is_multipart_signed(e_new->content))
668 e_new->security |= SEC_SIGN;
669 if (((WithCrypto & APPLICATION_PGP) != 0) &&
670 (mutt_str_strcasecmp(
671 mutt_param_get(&e_new->content->parameter, "protocol"),
672 "application/pgp-signature") == 0))
674 e_new->security |= APPLICATION_PGP;
676 else if (WithCrypto & APPLICATION_SMIME)
677 e_new->security |= APPLICATION_SMIME;
679 /* destroy the signature */
680 mutt_body_free(&e_new->content->parts->next);
681 e_new->content = mutt_remove_multipart(e_new->content);
683 if (e_new->content->mime_headers)
685 mutt_env_free(&protected_headers);
686 protected_headers = e_new->content->mime_headers;
687 e_new->content->mime_headers = NULL;
691 /* We don't need no primary multipart.
692 * Note: We _do_ preserve messages!
694 * XXX - we don't handle multipart/alternative in any
695 * smart way when sending messages. However, one may
696 * consider this a feature. */
697 if (e_new->content->type == TYPE_MULTIPART)
698 e_new->content = mutt_remove_multipart(e_new->content);
702 struct Buffer *file = mutt_buffer_pool_get();
704 /* create temporary files for all attachments */
705 for (b = e_new->content; b; b = b->next)
707 /* what follows is roughly a receive-mode variant of
708 * mutt_get_tmp_attachment () from muttlib.c */
710 mutt_buffer_reset(file);
713 mutt_buffer_strcpy(file, b->filename);
714 b->d_filename = mutt_str_strdup(b->filename);
718 /* avoid Content-Disposition: header with temporary filename */
722 /* set up state flags */
726 if (b->type == TYPE_TEXT)
728 if (mutt_str_strcasecmp("yes",
729 mutt_param_get(&b->parameter, "x-mutt-noconv")) == 0)
735 s.flags |= MUTT_CHARCONV;
739 mutt_param_delete(&b->parameter, "x-mutt-noconv");
742 mutt_adv_mktemp(file);
743 s.fp_out = mutt_file_fopen(mutt_b2s(file), "w");
747 if (((WithCrypto & APPLICATION_PGP) != 0) &&
748 ((sec_type = mutt_is_application_pgp(b)) & (SEC_ENCRYPT | SEC_SIGN)))
750 if (sec_type & SEC_ENCRYPT)
752 if (!crypt_valid_passphrase(APPLICATION_PGP))
754 mutt_message(_("Decrypting message..."));
757 if (mutt_body_handler(b, &s) < 0)
759 mutt_error(_("Decryption failed"));
763 if ((b == e_new->content) && !protected_headers)
765 protected_headers = b->mime_headers;
766 b->mime_headers = NULL;
769 e_new->security |= sec_type;
771 mutt_str_replace(&b->subtype, "plain");
772 mutt_param_delete(&b->parameter, "x-action");
774 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
775 ((sec_type = mutt_is_application_smime(b)) & (SEC_ENCRYPT | SEC_SIGN)))
777 if (sec_type & SEC_ENCRYPT)
779 if (!crypt_valid_passphrase(APPLICATION_SMIME))
781 crypt_smime_getkeys(e_new->env);
782 mutt_message(_("Decrypting message..."));
785 if (mutt_body_handler(b, &s) < 0)
787 mutt_error(_("Decryption failed"));
791 e_new->security |= sec_type;
793 mutt_str_replace(&b->subtype, "plain");
796 mutt_decode_attachment(b, &s);
798 if (mutt_file_fclose(&s.fp_out) != 0)
801 mutt_str_replace(&b->filename, mutt_b2s(file));
804 mutt_stamp_attachment(b);
806 mutt_body_free(&b->parts);
808 b->email->content = NULL; /* avoid dangling pointer */
811 if (C_CryptProtectedHeadersRead && protected_headers && protected_headers->subject &&
812 (mutt_str_strcmp(e_new->env->subject, protected_headers->subject) != 0))
814 mutt_str_replace(&e_new->env->subject, protected_headers->subject);
816 mutt_env_free(&protected_headers);
818 /* Fix encryption flags. */
820 /* No inline if multipart. */
821 if ((WithCrypto != 0) && (e_new->security & SEC_INLINE) && e_new->content->next)
822 e_new->security &= ~SEC_INLINE;
824 /* Do we even support multiple mechanisms? */
825 e_new->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
827 /* Theoretically, both could be set. Take the one the user wants to set by default. */
828 if ((e_new->security & APPLICATION_PGP) && (e_new->security & APPLICATION_SMIME))
830 if (C_SmimeIsDefault)
831 e_new->security &= ~APPLICATION_PGP;
833 e_new->security &= ~APPLICATION_SMIME;
836 mutt_rfc3676_space_unstuff(e_new);
843 mutt_buffer_pool_release(&file);
845 mutt_file_fclose(&fp_body);
847 mx_msg_close(m, &msg);
851 mutt_env_free(&e_new->env);
852 mutt_body_free(&e_new->content);