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"
54 #include "ncrypt/ncrypt.h"
64 #include "imap/imap.h"
67 static const struct Mapping PostponeHelp[] = {
68 { N_("Exit"), OP_EXIT },
69 { N_("Del"), OP_DELETE },
70 { N_("Undel"), OP_UNDELETE },
71 { N_("Help"), OP_HELP },
75 static short PostCount = 0;
76 static bool UpdateNumPostponed = false;
79 * mutt_num_postponed - Return the number of postponed messages
80 * @param m currently selected mailbox
82 * * false Use a cached value if costly to get a fresh count (IMAP)
84 * @retval num Postponed messages
86 int mutt_num_postponed(struct Mailbox *m, bool force)
90 static time_t LastModify = 0;
91 static char *OldPostponed = NULL;
93 if (UpdateNumPostponed)
95 UpdateNumPostponed = false;
99 if (mutt_str_strcmp(C_Postponed, OldPostponed) != 0)
102 OldPostponed = mutt_str_strdup(C_Postponed);
110 // We currently are in the C_Postponed mailbox so just pick the current status
111 if (m && (mutt_str_strcmp(C_Postponed, m->realpath) == 0))
113 PostCount = m->msg_count - m->msg_deleted;
118 /* LastModify is useless for IMAP */
119 if (imap_path_probe(C_Postponed, NULL) == MUTT_IMAP)
125 newpc = imap_path_status(C_Postponed, false);
129 mutt_debug(LL_DEBUG3, "%d postponed IMAP messages found\n", PostCount);
132 mutt_debug(LL_DEBUG3, "using old IMAP postponed count\n");
138 if (stat(C_Postponed, &st) == -1)
145 if (S_ISDIR(st.st_mode))
147 /* if we have a maildir mailbox, we need to stat the "new" dir */
151 snprintf(buf, sizeof(buf), "%s/new", C_Postponed);
152 if ((access(buf, F_OK) == 0) && (stat(buf, &st) == -1))
160 if (LastModify < st.st_mtime)
163 int optnews = OptNews;
165 LastModify = st.st_mtime;
167 if (access(C_Postponed, R_OK | F_OK) != 0)
168 return PostCount = 0;
173 struct Mailbox *m_post = mx_path_resolve(C_Postponed);
174 struct Context *ctx = mx_mbox_open(m_post, MUTT_NOSORT | MUTT_QUIET);
177 mailbox_free(&m_post);
181 PostCount = ctx->mailbox->msg_count;
182 mx_fastclose_mailbox(m_post);
194 * mutt_update_num_postponed - Force the update of the number of postponed messages
196 void mutt_update_num_postponed(void)
198 UpdateNumPostponed = true;
202 * post_make_entry - Format a menu item for the email list - Implements Menu::menu_make_entry()
204 static void post_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
206 struct Context *ctx = menu->data;
208 mutt_make_string_flags(buf, buflen, NONULL(C_IndexFormat), ctx, ctx->mailbox,
209 ctx->mailbox->emails[line], MUTT_FORMAT_ARROWCURSOR);
213 * select_msg - Create a Menu to select a postponed message
216 static struct Email *select_msg(struct Context *ctx)
222 struct Menu *menu = mutt_menu_new(MENU_POSTPONE);
223 menu->menu_make_entry = post_make_entry;
224 menu->max = ctx->mailbox->msg_count;
225 menu->title = _("Postponed Messages");
227 menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_POSTPONE, PostponeHelp);
228 mutt_menu_push_current(menu);
230 /* The postponed mailbox is setup to have sorting disabled, but the global
231 * C_Sort variable may indicate something different. Sorting has to be
232 * disabled while the postpone menu is being displayed. */
233 const short orig_sort = C_Sort;
238 const int op = mutt_menu_loop(menu);
243 /* should deleted draft messages be saved in the trash folder? */
244 mutt_set_flag(ctx->mailbox, ctx->mailbox->emails[menu->current],
245 MUTT_DELETE, (op == OP_DELETE));
246 PostCount = ctx->mailbox->msg_count - ctx->mailbox->msg_deleted;
247 if (C_Resolve && (menu->current < menu->max - 1))
249 menu->oldcurrent = menu->current;
251 if (menu->current >= menu->top + menu->pagelen)
253 menu->top = menu->current;
254 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
257 menu->redraw |= REDRAW_MOTION_RESYNC;
260 menu->redraw |= REDRAW_CURRENT;
263 case OP_GENERIC_SELECT_ENTRY:
275 mutt_menu_pop_current(menu);
276 mutt_menu_free(&menu);
277 return (r > -1) ? ctx->mailbox->emails[r] : NULL;
281 * mutt_get_postponed - Recall a postponed message
282 * @param[in] ctx Context info, used when recalling a message to which we reply
283 * @param[in] hdr envelope/attachment info for recalled message
284 * @param[out] cur if message was a reply, 'cur' is set to the message which 'hdr' is in reply to
285 * @param[in] fcc fcc for the recalled message
286 * @param[in] fcclen max length of fcc
287 * @retval -1 Error/no messages
288 * @retval 0 Normal exit
289 * @retval #SEND_REPLY Recalled message is a reply
291 int mutt_get_postponed(struct Context *ctx, struct Email *hdr,
292 struct Email **cur, char *fcc, size_t fcclen)
297 struct Email *e = NULL;
298 int rc = SEND_POSTPONED;
299 const char *p = NULL;
300 struct Context *ctx_post = NULL;
302 struct Mailbox *m = mx_path_resolve(C_Postponed);
303 if (ctx->mailbox == m)
306 ctx_post = mx_mbox_open(m, MUTT_NOSORT);
311 mutt_error(_("No postponed messages"));
316 if (ctx_post->mailbox->msg_count == 0)
322 mx_mbox_close(&ctx_post);
323 mutt_error(_("No postponed messages"));
327 if (ctx_post->mailbox->msg_count == 1)
329 /* only one message, so just use that one. */
330 e = ctx_post->mailbox->emails[0];
332 else if (!(e = select_msg(ctx_post)))
337 mx_mbox_close(&ctx_post);
341 if (mutt_prepare_template(NULL, ctx_post->mailbox, hdr, e, false) < 0)
345 mx_fastclose_mailbox(ctx_post->mailbox);
351 /* finished with this message, so delete it. */
352 mutt_set_flag(ctx_post->mailbox, e, MUTT_DELETE, true);
353 mutt_set_flag(ctx_post->mailbox, e, MUTT_PURGE, true);
355 /* update the count for the status display */
356 PostCount = ctx_post->mailbox->msg_count - ctx_post->mailbox->msg_deleted;
358 /* avoid the "purge deleted messages" prompt */
359 int opt_delete = C_Delete;
364 mx_mbox_close(&ctx_post);
365 C_Delete = opt_delete;
367 struct ListNode *np = NULL, *tmp = NULL;
368 STAILQ_FOREACH_SAFE(np, &hdr->env->userhdrs, entries, tmp)
370 size_t plen = mutt_str_startswith(np->data, "X-Mutt-References:", CASE_IGNORE);
373 /* if a mailbox is currently open, look to see if the original message
374 * the user attempted to reply to is in this mailbox */
375 p = mutt_str_skip_email_wsp(np->data + plen);
376 if (!ctx->mailbox->id_hash)
377 ctx->mailbox->id_hash = mutt_make_id_hash(ctx->mailbox);
378 *cur = mutt_hash_find(ctx->mailbox->id_hash, p);
383 else if ((plen = mutt_str_startswith(np->data, "X-Mutt-Fcc:", CASE_IGNORE)))
385 p = mutt_str_skip_email_wsp(np->data + plen);
386 mutt_str_strfcpy(fcc, p, fcclen);
387 mutt_pretty_mailbox(fcc, fcclen);
389 /* note that x-mutt-fcc was present. we do this because we want to add a
390 * default fcc if the header was missing, but preserve the request of the
391 * user to not make a copy if the header field is present, but empty.
392 * see http://dev.mutt.org/trac/ticket/3653 */
393 rc |= SEND_POSTPONED_FCC;
395 else if (((WithCrypto & APPLICATION_PGP) != 0) &&
396 /* this is generated by old neomutt versions */
397 (mutt_str_startswith(np->data, "Pgp:", CASE_MATCH) ||
398 /* this is the new way */
399 mutt_str_startswith(np->data, "X-Mutt-PGP:", CASE_MATCH)))
401 hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_PGP);
402 hdr->security |= APPLICATION_PGP;
404 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
405 mutt_str_startswith(np->data, "X-Mutt-SMIME:", CASE_MATCH))
407 hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_SMIME);
408 hdr->security |= APPLICATION_SMIME;
412 else if (mutt_str_startswith(np->data, "X-Mutt-Mix:", CASE_MATCH))
414 mutt_list_free(&hdr->chain);
416 char *t = strtok(np->data + 11, " \t\n");
419 mutt_list_insert_tail(&hdr->chain, mutt_str_strdup(t));
420 t = strtok(NULL, " \t\n");
427 // skip header removal
432 STAILQ_REMOVE(&hdr->env->userhdrs, np, ListNode, entries);
437 if (C_CryptOpportunisticEncrypt)
438 crypt_opportunistic_encrypt(hdr);
444 * mutt_parse_crypt_hdr - Parse a crypto header string
445 * @param p Header string to parse
446 * @param set_empty_signas Allow an empty "Sign as"
447 * @param crypt_app App, e.g. #APPLICATION_PGP
448 * @retval num SecurityFlags, see #SecurityFlags
450 SecurityFlags mutt_parse_crypt_hdr(const char *p, bool set_empty_signas, SecurityFlags crypt_app)
452 char smime_cryptalg[1024] = { 0 };
453 char sign_as[1024] = { 0 };
455 SecurityFlags flags = SEC_NO_FLAGS;
460 p = mutt_str_skip_email_wsp(p);
461 for (; p[0] != '\0'; p++)
471 for (p += 2; (p[0] != '\0') && (p[0] != '>') &&
472 (q < (smime_cryptalg + sizeof(smime_cryptalg) - 1));
479 mutt_error(_("Illegal S/MIME header"));
489 flags |= SEC_ENCRYPT;
497 /* This used to be the micalg parameter.
499 * It's no longer needed, so we just skip the parameter in order
500 * to be able to recall old messages. */
505 for (p += 2; (p[0] != '\0') && (p[0] != '>'); p++)
509 mutt_error(_("Illegal crypto header"));
518 flags |= SEC_OPPENCRYPT;
524 flags |= SEC_AUTOCRYPT;
531 flags |= SEC_AUTOCRYPT_OVERRIDE;
543 (p[0] != '\0') && (*p != '>') && (q < (sign_as + sizeof(sign_as) - 1));
550 mutt_error(_("Illegal crypto header"));
559 mutt_error(_("Illegal crypto header"));
564 /* the cryptalg field must not be empty */
565 if (((WithCrypto & APPLICATION_SMIME) != 0) && *smime_cryptalg)
566 mutt_str_replace(&C_SmimeEncryptWith, smime_cryptalg);
568 /* Set {Smime,Pgp}SignAs, if desired. */
570 if (((WithCrypto & APPLICATION_PGP) != 0) && (crypt_app == APPLICATION_PGP) &&
571 (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
573 mutt_str_replace(&C_PgpSignAs, sign_as);
576 if (((WithCrypto & APPLICATION_SMIME) != 0) && (crypt_app == APPLICATION_SMIME) &&
577 (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
579 mutt_str_replace(&C_SmimeSignAs, sign_as);
586 * mutt_prepare_template - Prepare a message template
587 * @param fp If not NULL, file containing the template
588 * @param m If fp is NULL, the Mailbox containing the header with the template
589 * @param e_new The template is read into this Header
590 * @param e Email to recall/resend
591 * @param resend Set if resending (as opposed to recalling a postponed msg)
592 * Resent messages enable header weeding, and also
593 * discard any existing Message-ID and Mail-Followup-To
597 int mutt_prepare_template(FILE *fp, struct Mailbox *m, struct Email *e_new,
598 struct Email *e, bool resend)
600 struct Message *msg = NULL;
601 struct Body *b = NULL;
602 FILE *fp_body = NULL;
604 struct State s = { 0 };
605 SecurityFlags sec_type;
606 struct Envelope *protected_headers = NULL;
608 if (!fp && !(msg = mx_msg_open(m, e->msgno)))
616 /* parse the message header and MIME structure */
618 fseeko(fp, e->offset, SEEK_SET);
619 e_new->offset = e->offset;
620 /* enable header weeding for resent messages */
621 e_new->env = mutt_rfc822_read_header(fp, e_new, true, resend);
622 e_new->content->length = e->content->length;
623 mutt_parse_part(fp, e_new->content);
625 /* If resending a message, don't keep message_id or mail_followup_to.
626 * Otherwise, we are resuming a postponed message, and want to keep those
627 * headers if they exist. */
630 FREE(&e_new->env->message_id);
631 FREE(&e_new->env->mail_followup_to);
634 /* decrypt pgp/mime encoded messages */
636 if (((WithCrypto & APPLICATION_PGP) != 0) &&
637 (sec_type = mutt_is_multipart_encrypted(e_new->content)))
639 e_new->security |= sec_type;
640 if (!crypt_valid_passphrase(sec_type))
643 mutt_message(_("Decrypting message..."));
644 if ((crypt_pgp_decrypt_mime(fp, &fp_body, e_new->content, &b) == -1) || !b)
649 mutt_body_free(&e_new->content);
654 protected_headers = b->mime_headers;
655 b->mime_headers = NULL;
661 /* remove a potential multipart/signed layer - useful when
662 * resending messages */
663 if ((WithCrypto != 0) && mutt_is_multipart_signed(e_new->content))
665 e_new->security |= SEC_SIGN;
666 if (((WithCrypto & APPLICATION_PGP) != 0) &&
667 (mutt_str_strcasecmp(
668 mutt_param_get(&e_new->content->parameter, "protocol"),
669 "application/pgp-signature") == 0))
671 e_new->security |= APPLICATION_PGP;
673 else if (WithCrypto & APPLICATION_SMIME)
674 e_new->security |= APPLICATION_SMIME;
676 /* destroy the signature */
677 mutt_body_free(&e_new->content->parts->next);
678 e_new->content = mutt_remove_multipart(e_new->content);
680 if (e_new->content->mime_headers)
682 mutt_env_free(&protected_headers);
683 protected_headers = e_new->content->mime_headers;
684 e_new->content->mime_headers = NULL;
688 /* We don't need no primary multipart.
689 * Note: We _do_ preserve messages!
691 * XXX - we don't handle multipart/alternative in any
692 * smart way when sending messages. However, one may
693 * consider this a feature. */
694 if (e_new->content->type == TYPE_MULTIPART)
695 e_new->content = mutt_remove_multipart(e_new->content);
699 struct Buffer *file = mutt_buffer_pool_get();
701 /* create temporary files for all attachments */
702 for (b = e_new->content; b; b = b->next)
704 /* what follows is roughly a receive-mode variant of
705 * mutt_get_tmp_attachment () from muttlib.c */
707 mutt_buffer_reset(file);
710 mutt_buffer_strcpy(file, b->filename);
711 b->d_filename = mutt_str_strdup(b->filename);
715 /* avoid Content-Disposition: header with temporary filename */
719 /* set up state flags */
723 if (b->type == TYPE_TEXT)
725 if (mutt_str_strcasecmp("yes",
726 mutt_param_get(&b->parameter, "x-mutt-noconv")) == 0)
732 s.flags |= MUTT_CHARCONV;
736 mutt_param_delete(&b->parameter, "x-mutt-noconv");
739 mutt_adv_mktemp(file);
740 s.fp_out = mutt_file_fopen(mutt_b2s(file), "w");
744 if (((WithCrypto & APPLICATION_PGP) != 0) &&
745 ((sec_type = mutt_is_application_pgp(b)) & (SEC_ENCRYPT | SEC_SIGN)))
747 if (sec_type & SEC_ENCRYPT)
749 if (!crypt_valid_passphrase(APPLICATION_PGP))
751 mutt_message(_("Decrypting message..."));
754 if (mutt_body_handler(b, &s) < 0)
756 mutt_error(_("Decryption failed"));
760 if ((b == e_new->content) && !protected_headers)
762 protected_headers = b->mime_headers;
763 b->mime_headers = NULL;
766 e_new->security |= sec_type;
768 mutt_str_replace(&b->subtype, "plain");
769 mutt_param_delete(&b->parameter, "x-action");
771 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
772 ((sec_type = mutt_is_application_smime(b)) & (SEC_ENCRYPT | SEC_SIGN)))
774 if (sec_type & SEC_ENCRYPT)
776 if (!crypt_valid_passphrase(APPLICATION_SMIME))
778 crypt_smime_getkeys(e_new->env);
779 mutt_message(_("Decrypting message..."));
782 if (mutt_body_handler(b, &s) < 0)
784 mutt_error(_("Decryption failed"));
788 e_new->security |= sec_type;
790 mutt_str_replace(&b->subtype, "plain");
793 mutt_decode_attachment(b, &s);
795 if (mutt_file_fclose(&s.fp_out) != 0)
798 mutt_str_replace(&b->filename, mutt_b2s(file));
801 mutt_stamp_attachment(b);
803 mutt_body_free(&b->parts);
805 b->email->content = NULL; /* avoid dangling pointer */
808 if (C_CryptProtectedHeadersRead && protected_headers && protected_headers->subject &&
809 (mutt_str_strcmp(e_new->env->subject, protected_headers->subject) != 0))
811 mutt_str_replace(&e_new->env->subject, protected_headers->subject);
813 mutt_env_free(&protected_headers);
815 /* Fix encryption flags. */
817 /* No inline if multipart. */
818 if ((WithCrypto != 0) && (e_new->security & SEC_INLINE) && e_new->content->next)
819 e_new->security &= ~SEC_INLINE;
821 /* Do we even support multiple mechanisms? */
822 e_new->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
824 /* Theoretically, both could be set. Take the one the user wants to set by default. */
825 if ((e_new->security & APPLICATION_PGP) && (e_new->security & APPLICATION_SMIME))
827 if (C_SmimeIsDefault)
828 e_new->security &= ~APPLICATION_PGP;
830 e_new->security &= ~APPLICATION_SMIME;
833 mutt_rfc3676_space_unstuff(e_new);
840 mutt_buffer_pool_release(&file);
842 mutt_file_fclose(&fp_body);
844 mx_msg_close(m, &msg);
848 mutt_env_free(&e_new->env);
849 mutt_body_free(&e_new->content);