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 PostCount = ctx->mailbox->msg_count;
183 mailbox_free(&m_post);
186 mx_fastclose_mailbox(m_post);
198 * mutt_update_num_postponed - Force the update of the number of postponed messages
200 void mutt_update_num_postponed(void)
202 UpdateNumPostponed = true;
206 * post_make_entry - Format a menu item for the email list - Implements Menu::menu_make_entry()
208 static void post_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
210 struct Context *ctx = menu->data;
212 mutt_make_string_flags(buf, buflen, menu->indexwin->cols,
213 NONULL(C_IndexFormat), ctx, ctx->mailbox,
214 ctx->mailbox->emails[line], MUTT_FORMAT_ARROWCURSOR);
218 * select_msg - Create a Menu to select a postponed message
221 static struct Email *select_msg(struct Context *ctx)
227 struct Menu *menu = mutt_menu_new(MENU_POSTPONE);
228 menu->menu_make_entry = post_make_entry;
229 menu->max = ctx->mailbox->msg_count;
230 menu->title = _("Postponed Messages");
232 menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_POSTPONE, PostponeHelp);
233 mutt_menu_push_current(menu);
235 /* The postponed mailbox is setup to have sorting disabled, but the global
236 * C_Sort variable may indicate something different. Sorting has to be
237 * disabled while the postpone menu is being displayed. */
238 const short orig_sort = C_Sort;
243 const int op = mutt_menu_loop(menu);
248 /* should deleted draft messages be saved in the trash folder? */
249 mutt_set_flag(ctx->mailbox, ctx->mailbox->emails[menu->current],
250 MUTT_DELETE, (op == OP_DELETE));
251 PostCount = ctx->mailbox->msg_count - ctx->mailbox->msg_deleted;
252 if (C_Resolve && (menu->current < menu->max - 1))
254 menu->oldcurrent = menu->current;
256 if (menu->current >= menu->top + menu->pagelen)
258 menu->top = menu->current;
259 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
262 menu->redraw |= REDRAW_MOTION_RESYNC;
265 menu->redraw |= REDRAW_CURRENT;
268 case OP_GENERIC_SELECT_ENTRY:
280 mutt_menu_pop_current(menu);
281 mutt_menu_free(&menu);
282 return (r > -1) ? ctx->mailbox->emails[r] : NULL;
286 * mutt_get_postponed - Recall a postponed message
287 * @param[in] ctx Context info, used when recalling a message to which we reply
288 * @param[in] hdr envelope/attachment info for recalled message
289 * @param[out] cur if message was a reply, 'cur' is set to the message which 'hdr' is in reply to
290 * @param[in] fcc fcc for the recalled message
291 * @retval -1 Error/no messages
292 * @retval 0 Normal exit
293 * @retval #SEND_REPLY Recalled message is a reply
295 int mutt_get_postponed(struct Context *ctx, struct Email *hdr,
296 struct Email **cur, struct Buffer *fcc)
301 struct Email *e = NULL;
302 int rc = SEND_POSTPONED;
303 const char *p = NULL;
304 struct Context *ctx_post = NULL;
306 struct Mailbox *m = mx_path_resolve(C_Postponed);
307 if (ctx->mailbox == m)
310 ctx_post = mx_mbox_open(m, MUTT_NOSORT);
315 mutt_error(_("No postponed messages"));
320 if (ctx_post->mailbox->msg_count == 0)
326 mx_mbox_close(&ctx_post);
327 mutt_error(_("No postponed messages"));
331 if (ctx_post->mailbox->msg_count == 1)
333 /* only one message, so just use that one. */
334 e = ctx_post->mailbox->emails[0];
336 else if (!(e = select_msg(ctx_post)))
341 mx_mbox_close(&ctx_post);
345 if (mutt_prepare_template(NULL, ctx_post->mailbox, hdr, e, false) < 0)
349 mx_fastclose_mailbox(ctx_post->mailbox);
355 /* finished with this message, so delete it. */
356 mutt_set_flag(ctx_post->mailbox, e, MUTT_DELETE, true);
357 mutt_set_flag(ctx_post->mailbox, e, MUTT_PURGE, true);
359 /* update the count for the status display */
360 PostCount = ctx_post->mailbox->msg_count - ctx_post->mailbox->msg_deleted;
362 /* avoid the "purge deleted messages" prompt */
363 int opt_delete = C_Delete;
368 mx_mbox_close(&ctx_post);
369 C_Delete = opt_delete;
371 struct ListNode *np = NULL, *tmp = NULL;
372 STAILQ_FOREACH_SAFE(np, &hdr->env->userhdrs, entries, tmp)
374 size_t plen = mutt_str_startswith(np->data, "X-Mutt-References:", CASE_IGNORE);
377 /* if a mailbox is currently open, look to see if the original message
378 * the user attempted to reply to is in this mailbox */
379 p = mutt_str_skip_email_wsp(np->data + plen);
380 if (!ctx->mailbox->id_hash)
381 ctx->mailbox->id_hash = mutt_make_id_hash(ctx->mailbox);
382 *cur = mutt_hash_find(ctx->mailbox->id_hash, p);
387 else if ((plen = mutt_str_startswith(np->data, "X-Mutt-Fcc:", CASE_IGNORE)))
389 p = mutt_str_skip_email_wsp(np->data + plen);
390 mutt_buffer_strcpy(fcc, p);
391 mutt_buffer_pretty_mailbox(fcc);
393 /* note that x-mutt-fcc was present. we do this because we want to add a
394 * default fcc if the header was missing, but preserve the request of the
395 * user to not make a copy if the header field is present, but empty.
396 * see http://dev.mutt.org/trac/ticket/3653 */
397 rc |= SEND_POSTPONED_FCC;
399 else if (((WithCrypto & APPLICATION_PGP) != 0) &&
400 /* this is generated by old neomutt versions */
401 (mutt_str_startswith(np->data, "Pgp:", CASE_MATCH) ||
402 /* this is the new way */
403 mutt_str_startswith(np->data, "X-Mutt-PGP:", CASE_MATCH)))
405 hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_PGP);
406 hdr->security |= APPLICATION_PGP;
408 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
409 mutt_str_startswith(np->data, "X-Mutt-SMIME:", CASE_MATCH))
411 hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_SMIME);
412 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");
429 // skip header removal
434 STAILQ_REMOVE(&hdr->env->userhdrs, np, ListNode, entries);
439 if (C_CryptOpportunisticEncrypt)
440 crypt_opportunistic_encrypt(hdr);
446 * mutt_parse_crypt_hdr - Parse a crypto header string
447 * @param p Header string to parse
448 * @param set_empty_signas Allow an empty "Sign as"
449 * @param crypt_app App, e.g. #APPLICATION_PGP
450 * @retval num SecurityFlags, see #SecurityFlags
452 SecurityFlags mutt_parse_crypt_hdr(const char *p, bool set_empty_signas, SecurityFlags crypt_app)
454 char smime_cryptalg[1024] = { 0 };
455 char sign_as[1024] = { 0 };
457 SecurityFlags flags = SEC_NO_FLAGS;
462 p = mutt_str_skip_email_wsp(p);
463 for (; p[0] != '\0'; p++)
473 for (p += 2; (p[0] != '\0') && (p[0] != '>') &&
474 (q < (smime_cryptalg + sizeof(smime_cryptalg) - 1));
481 mutt_error(_("Illegal S/MIME header"));
491 flags |= SEC_ENCRYPT;
499 /* This used to be the micalg parameter.
501 * It's no longer needed, so we just skip the parameter in order
502 * to be able to recall old messages. */
507 for (p += 2; (p[0] != '\0') && (p[0] != '>'); p++)
511 mutt_error(_("Illegal crypto header"));
520 flags |= SEC_OPPENCRYPT;
526 flags |= SEC_AUTOCRYPT;
533 flags |= SEC_AUTOCRYPT_OVERRIDE;
545 (p[0] != '\0') && (*p != '>') && (q < (sign_as + sizeof(sign_as) - 1));
552 mutt_error(_("Illegal crypto header"));
561 mutt_error(_("Illegal crypto header"));
566 /* the cryptalg field must not be empty */
567 if (((WithCrypto & APPLICATION_SMIME) != 0) && *smime_cryptalg)
568 mutt_str_replace(&C_SmimeEncryptWith, smime_cryptalg);
570 /* Set {Smime,Pgp}SignAs, if desired. */
572 if (((WithCrypto & APPLICATION_PGP) != 0) && (crypt_app == APPLICATION_PGP) &&
573 (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
575 mutt_str_replace(&C_PgpSignAs, sign_as);
578 if (((WithCrypto & APPLICATION_SMIME) != 0) && (crypt_app == APPLICATION_SMIME) &&
579 (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
581 mutt_str_replace(&C_SmimeSignAs, sign_as);
588 * mutt_prepare_template - Prepare a message template
589 * @param fp If not NULL, file containing the template
590 * @param m If fp is NULL, the Mailbox containing the header with the template
591 * @param e_new The template is read into this Header
592 * @param e Email to recall/resend
593 * @param resend Set if resending (as opposed to recalling a postponed msg)
594 * Resent messages enable header weeding, and also
595 * discard any existing Message-ID and Mail-Followup-To
599 int mutt_prepare_template(FILE *fp, struct Mailbox *m, struct Email *e_new,
600 struct Email *e, bool resend)
602 struct Message *msg = NULL;
603 struct Body *b = NULL;
604 FILE *fp_body = NULL;
606 struct State s = { 0 };
607 SecurityFlags sec_type;
608 struct Envelope *protected_headers = NULL;
610 if (!fp && !(msg = mx_msg_open(m, e->msgno)))
618 /* parse the message header and MIME structure */
620 fseeko(fp, e->offset, SEEK_SET);
621 e_new->offset = e->offset;
622 /* enable header weeding for resent messages */
623 e_new->env = mutt_rfc822_read_header(fp, e_new, true, resend);
624 e_new->content->length = e->content->length;
625 mutt_parse_part(fp, e_new->content);
627 /* If resending a message, don't keep message_id or mail_followup_to.
628 * Otherwise, we are resuming a postponed message, and want to keep those
629 * headers if they exist. */
632 FREE(&e_new->env->message_id);
633 FREE(&e_new->env->mail_followup_to);
636 /* decrypt pgp/mime encoded messages */
638 if (((WithCrypto & APPLICATION_PGP) != 0) &&
639 (sec_type = mutt_is_multipart_encrypted(e_new->content)))
641 e_new->security |= sec_type;
642 if (!crypt_valid_passphrase(sec_type))
645 mutt_message(_("Decrypting message..."));
646 if ((crypt_pgp_decrypt_mime(fp, &fp_body, e_new->content, &b) == -1) || !b)
651 mutt_body_free(&e_new->content);
656 protected_headers = b->mime_headers;
657 b->mime_headers = NULL;
663 /* remove a potential multipart/signed layer - useful when
664 * resending messages */
665 if ((WithCrypto != 0) && mutt_is_multipart_signed(e_new->content))
667 e_new->security |= SEC_SIGN;
668 if (((WithCrypto & APPLICATION_PGP) != 0) &&
669 (mutt_str_strcasecmp(
670 mutt_param_get(&e_new->content->parameter, "protocol"),
671 "application/pgp-signature") == 0))
673 e_new->security |= APPLICATION_PGP;
675 else if (WithCrypto & APPLICATION_SMIME)
676 e_new->security |= APPLICATION_SMIME;
678 /* destroy the signature */
679 mutt_body_free(&e_new->content->parts->next);
680 e_new->content = mutt_remove_multipart(e_new->content);
682 if (e_new->content->mime_headers)
684 mutt_env_free(&protected_headers);
685 protected_headers = e_new->content->mime_headers;
686 e_new->content->mime_headers = NULL;
690 /* We don't need no primary multipart.
691 * Note: We _do_ preserve messages!
693 * XXX - we don't handle multipart/alternative in any
694 * smart way when sending messages. However, one may
695 * consider this a feature. */
696 if (e_new->content->type == TYPE_MULTIPART)
697 e_new->content = mutt_remove_multipart(e_new->content);
701 struct Buffer *file = mutt_buffer_pool_get();
703 /* create temporary files for all attachments */
704 for (b = e_new->content; b; b = b->next)
706 /* what follows is roughly a receive-mode variant of
707 * mutt_get_tmp_attachment () from muttlib.c */
709 mutt_buffer_reset(file);
712 mutt_buffer_strcpy(file, b->filename);
713 b->d_filename = mutt_str_strdup(b->filename);
717 /* avoid Content-Disposition: header with temporary filename */
721 /* set up state flags */
725 if (b->type == TYPE_TEXT)
727 if (mutt_str_strcasecmp("yes",
728 mutt_param_get(&b->parameter, "x-mutt-noconv")) == 0)
734 s.flags |= MUTT_CHARCONV;
738 mutt_param_delete(&b->parameter, "x-mutt-noconv");
741 mutt_adv_mktemp(file);
742 s.fp_out = mutt_file_fopen(mutt_b2s(file), "w");
746 if (((WithCrypto & APPLICATION_PGP) != 0) &&
747 ((sec_type = mutt_is_application_pgp(b)) & (SEC_ENCRYPT | SEC_SIGN)))
749 if (sec_type & SEC_ENCRYPT)
751 if (!crypt_valid_passphrase(APPLICATION_PGP))
753 mutt_message(_("Decrypting message..."));
756 if (mutt_body_handler(b, &s) < 0)
758 mutt_error(_("Decryption failed"));
762 if ((b == e_new->content) && !protected_headers)
764 protected_headers = b->mime_headers;
765 b->mime_headers = NULL;
768 e_new->security |= sec_type;
770 mutt_str_replace(&b->subtype, "plain");
771 mutt_param_delete(&b->parameter, "x-action");
773 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
774 ((sec_type = mutt_is_application_smime(b)) & (SEC_ENCRYPT | SEC_SIGN)))
776 if (sec_type & SEC_ENCRYPT)
778 if (!crypt_valid_passphrase(APPLICATION_SMIME))
780 crypt_smime_getkeys(e_new->env);
781 mutt_message(_("Decrypting message..."));
784 if (mutt_body_handler(b, &s) < 0)
786 mutt_error(_("Decryption failed"));
790 e_new->security |= sec_type;
792 mutt_str_replace(&b->subtype, "plain");
795 mutt_decode_attachment(b, &s);
797 if (mutt_file_fclose(&s.fp_out) != 0)
800 mutt_str_replace(&b->filename, mutt_b2s(file));
803 mutt_stamp_attachment(b);
805 mutt_body_free(&b->parts);
807 b->email->content = NULL; /* avoid dangling pointer */
810 if (C_CryptProtectedHeadersRead && protected_headers && protected_headers->subject &&
811 (mutt_str_strcmp(e_new->env->subject, protected_headers->subject) != 0))
813 mutt_str_replace(&e_new->env->subject, protected_headers->subject);
815 mutt_env_free(&protected_headers);
817 /* Fix encryption flags. */
819 /* No inline if multipart. */
820 if ((WithCrypto != 0) && (e_new->security & SEC_INLINE) && e_new->content->next)
821 e_new->security &= ~SEC_INLINE;
823 /* Do we even support multiple mechanisms? */
824 e_new->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
826 /* Theoretically, both could be set. Take the one the user wants to set by default. */
827 if ((e_new->security & APPLICATION_PGP) && (e_new->security & APPLICATION_SMIME))
829 if (C_SmimeIsDefault)
830 e_new->security &= ~APPLICATION_PGP;
832 e_new->security &= ~APPLICATION_SMIME;
835 mutt_rfc3676_space_unstuff(e_new);
842 mutt_buffer_pool_release(&file);
844 mutt_file_fclose(&fp_body);
846 mx_msg_close(m, &msg);
850 mutt_env_free(&e_new->env);
851 mutt_body_free(&e_new->content);