From 6fc0ef05587797999fff99010e9b747580913043 Mon Sep 17 00:00:00 2001 From: Richard Russon Date: Tue, 11 Sep 2018 17:49:24 +0100 Subject: [PATCH] reorg imap functions --- imap/imap.c | 448 +++++++++++++++++++++---------------------- imap/message.c | 507 ++++++++++++++++++++++++------------------------- 2 files changed, 476 insertions(+), 479 deletions(-) diff --git a/imap/imap.c b/imap/imap.c index 5269c8c50..755da095e 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -1993,6 +1993,211 @@ out: return (rc < 0) ? -1 : rc; } +/** + * imap_sync_mailbox - Sync all the changes to the server + * @param ctx Mailbox + * @param expunge if true do expunge + * @retval 0 Success + * @retval -1 Error + */ +int imap_sync_mailbox(struct Context *ctx, bool expunge) +{ + struct Context *appendctx = NULL; + struct Header *h = NULL; + struct Header **hdrs = NULL; + int oldsort; + int rc; + + struct ImapData *idata = ctx->mailbox->data; + + if (idata->state < IMAP_SELECTED) + { + mutt_debug(2, "no mailbox selected\n"); + return -1; + } + + /* This function is only called when the calling code expects the context + * to be changed. */ + imap_allow_reopen(ctx); + + rc = imap_check(idata, false); + if (rc != 0) + return rc; + + /* if we are expunging anyway, we can do deleted messages very quickly... */ + if (expunge && mutt_bit_isset(ctx->mailbox->rights, MUTT_ACL_DELETE)) + { + rc = imap_exec_msgset(idata, "UID STORE", "+FLAGS.SILENT (\\Deleted)", + MUTT_DELETED, true, false); + if (rc < 0) + { + mutt_error(_("Expunge failed")); + goto out; + } + + if (rc > 0) + { + /* mark these messages as unchanged so second pass ignores them. Done + * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */ + for (int i = 0; i < ctx->mailbox->msg_count; i++) + if (ctx->mailbox->hdrs[i]->deleted && ctx->mailbox->hdrs[i]->changed) + ctx->mailbox->hdrs[i]->active = false; + mutt_message(ngettext("Marking %d message deleted...", + "Marking %d messages deleted...", rc), + rc); + } + } + +#ifdef USE_HCACHE + idata->hcache = imap_hcache_open(idata, NULL); +#endif + + /* save messages with real (non-flag) changes */ + for (int i = 0; i < ctx->mailbox->msg_count; i++) + { + h = ctx->mailbox->hdrs[i]; + + if (h->deleted) + { + imap_cache_del(idata, h); +#ifdef USE_HCACHE + imap_hcache_del(idata, HEADER_DATA(h)->uid); +#endif + } + + if (h->active && h->changed) + { +#ifdef USE_HCACHE + imap_hcache_put(idata, h); +#endif + /* if the message has been rethreaded or attachments have been deleted + * we delete the message and reupload it. + * This works better if we're expunging, of course. */ + if ((h->env && (h->env->refs_changed || h->env->irt_changed)) || + h->attach_del || h->xlabel_changed) + { + /* L10N: The plural is choosen by the last %d, i.e. the total number */ + mutt_message(ngettext("Saving changed message... [%d/%d]", + "Saving changed messages... [%d/%d]", ctx->mailbox->msg_count), + i + 1, ctx->mailbox->msg_count); + if (!appendctx) + appendctx = mx_mbox_open(ctx->mailbox->path, MUTT_APPEND | MUTT_QUIET); + if (!appendctx) + mutt_debug(1, "Error opening mailbox in append mode\n"); + else + mutt_save_message_ctx(h, true, false, false, appendctx); + h->xlabel_changed = false; + } + } + } + +#ifdef USE_HCACHE + imap_hcache_close(idata); +#endif + + /* presort here to avoid doing 10 resorts in imap_exec_msgset */ + oldsort = Sort; + if (Sort != SORT_ORDER) + { + hdrs = ctx->mailbox->hdrs; + ctx->mailbox->hdrs = + mutt_mem_malloc(ctx->mailbox->msg_count * sizeof(struct Header *)); + memcpy(ctx->mailbox->hdrs, hdrs, ctx->mailbox->msg_count * sizeof(struct Header *)); + + Sort = SORT_ORDER; + qsort(ctx->mailbox->hdrs, ctx->mailbox->msg_count, sizeof(struct Header *), + mutt_get_sort_func(SORT_ORDER)); + } + + rc = sync_helper(idata, MUTT_ACL_DELETE, MUTT_DELETED, "\\Deleted"); + if (rc >= 0) + rc |= sync_helper(idata, MUTT_ACL_WRITE, MUTT_FLAG, "\\Flagged"); + if (rc >= 0) + rc |= sync_helper(idata, MUTT_ACL_WRITE, MUTT_OLD, "Old"); + if (rc >= 0) + rc |= sync_helper(idata, MUTT_ACL_SEEN, MUTT_READ, "\\Seen"); + if (rc >= 0) + rc |= sync_helper(idata, MUTT_ACL_WRITE, MUTT_REPLIED, "\\Answered"); + + if (oldsort != Sort) + { + Sort = oldsort; + FREE(&ctx->mailbox->hdrs); + ctx->mailbox->hdrs = hdrs; + } + + /* Flush the queued flags if any were changed in sync_helper. */ + if (rc > 0) + if (imap_exec(idata, NULL, 0) != IMAP_CMD_OK) + rc = -1; + + if (rc < 0) + { + if (ctx->mailbox->closing) + { + if (mutt_yesorno(_("Error saving flags. Close anyway?"), 0) == MUTT_YES) + { + rc = 0; + idata->state = IMAP_AUTHENTICATED; + goto out; + } + } + else + mutt_error(_("Error saving flags")); + rc = -1; + goto out; + } + + /* Update local record of server state to reflect the synchronization just + * completed. imap_read_headers always overwrites hcache-origin flags, so + * there is no need to mutate the hcache after flag-only changes. */ + for (int i = 0; i < ctx->mailbox->msg_count; i++) + { + HEADER_DATA(ctx->mailbox->hdrs[i])->deleted = ctx->mailbox->hdrs[i]->deleted; + HEADER_DATA(ctx->mailbox->hdrs[i])->flagged = ctx->mailbox->hdrs[i]->flagged; + HEADER_DATA(ctx->mailbox->hdrs[i])->old = ctx->mailbox->hdrs[i]->old; + HEADER_DATA(ctx->mailbox->hdrs[i])->read = ctx->mailbox->hdrs[i]->read; + HEADER_DATA(ctx->mailbox->hdrs[i])->replied = ctx->mailbox->hdrs[i]->replied; + ctx->mailbox->hdrs[i]->changed = false; + } + ctx->mailbox->changed = false; + + /* We must send an EXPUNGE command if we're not closing. */ + if (expunge && !(ctx->mailbox->closing) && mutt_bit_isset(ctx->mailbox->rights, MUTT_ACL_DELETE)) + { + mutt_message(_("Expunging messages from server...")); + /* Set expunge bit so we don't get spurious reopened messages */ + idata->reopen |= IMAP_EXPUNGE_EXPECTED; + if (imap_exec(idata, "EXPUNGE", 0) != 0) + { + idata->reopen &= ~IMAP_EXPUNGE_EXPECTED; + imap_error(_("imap_sync_mailbox: EXPUNGE failed"), idata->buf); + rc = -1; + goto out; + } + idata->reopen &= ~IMAP_EXPUNGE_EXPECTED; + } + + if (expunge && ctx->mailbox->closing) + { + imap_exec(idata, "CLOSE", IMAP_CMD_QUEUE); + idata->state = IMAP_AUTHENTICATED; + } + + if (MessageCacheClean) + imap_cache_clean(idata); + + rc = 0; + +out: + if (appendctx) + { + mx_fastclose_mailbox(appendctx); + FREE(&appendctx); + } + return rc; +} + /** * imap_mbox_open - Implements MxOps::mbox_open() */ @@ -2289,6 +2494,25 @@ static int imap_mbox_open_append(struct Context *ctx, int flags) return 0; } +/** + * imap_mbox_check - Implements MxOps::mbox_check() + * @param ctx Mailbox + * @param index_hint Remember our place in the index + * @retval >0 Success, e.g. #MUTT_REOPENED + * @retval -1 Failure + */ +static int imap_mbox_check(struct Context *ctx, int *index_hint) +{ + int rc; + (void) index_hint; + + imap_allow_reopen(ctx); + rc = imap_check(ctx->mailbox->data, false); + imap_disallow_reopen(ctx); + + return rc; +} + /** * imap_mbox_close - Implements MxOps::mbox_close() * @retval 0 Always @@ -2370,230 +2594,6 @@ static int imap_msg_open_new(struct Context *ctx, struct Message *msg, struct He return 0; } -/** - * imap_mbox_check - Implements MxOps::mbox_check() - * @param ctx Mailbox - * @param index_hint Remember our place in the index - * @retval >0 Success, e.g. #MUTT_REOPENED - * @retval -1 Failure - */ -static int imap_mbox_check(struct Context *ctx, int *index_hint) -{ - int rc; - (void) index_hint; - - imap_allow_reopen(ctx); - rc = imap_check(ctx->mailbox->data, false); - imap_disallow_reopen(ctx); - - return rc; -} - -/** - * imap_sync_mailbox - Sync all the changes to the server - * @param ctx Mailbox - * @param expunge if true do expunge - * @retval 0 Success - * @retval -1 Error - */ -int imap_sync_mailbox(struct Context *ctx, bool expunge) -{ - struct Context *appendctx = NULL; - struct Header *h = NULL; - struct Header **hdrs = NULL; - int oldsort; - int rc; - - struct ImapData *idata = ctx->mailbox->data; - - if (idata->state < IMAP_SELECTED) - { - mutt_debug(2, "no mailbox selected\n"); - return -1; - } - - /* This function is only called when the calling code expects the context - * to be changed. */ - imap_allow_reopen(ctx); - - rc = imap_check(idata, false); - if (rc != 0) - return rc; - - /* if we are expunging anyway, we can do deleted messages very quickly... */ - if (expunge && mutt_bit_isset(ctx->mailbox->rights, MUTT_ACL_DELETE)) - { - rc = imap_exec_msgset(idata, "UID STORE", "+FLAGS.SILENT (\\Deleted)", - MUTT_DELETED, true, false); - if (rc < 0) - { - mutt_error(_("Expunge failed")); - goto out; - } - - if (rc > 0) - { - /* mark these messages as unchanged so second pass ignores them. Done - * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */ - for (int i = 0; i < ctx->mailbox->msg_count; i++) - if (ctx->mailbox->hdrs[i]->deleted && ctx->mailbox->hdrs[i]->changed) - ctx->mailbox->hdrs[i]->active = false; - mutt_message(ngettext("Marking %d message deleted...", - "Marking %d messages deleted...", rc), - rc); - } - } - -#ifdef USE_HCACHE - idata->hcache = imap_hcache_open(idata, NULL); -#endif - - /* save messages with real (non-flag) changes */ - for (int i = 0; i < ctx->mailbox->msg_count; i++) - { - h = ctx->mailbox->hdrs[i]; - - if (h->deleted) - { - imap_cache_del(idata, h); -#ifdef USE_HCACHE - imap_hcache_del(idata, HEADER_DATA(h)->uid); -#endif - } - - if (h->active && h->changed) - { -#ifdef USE_HCACHE - imap_hcache_put(idata, h); -#endif - /* if the message has been rethreaded or attachments have been deleted - * we delete the message and reupload it. - * This works better if we're expunging, of course. */ - if ((h->env && (h->env->refs_changed || h->env->irt_changed)) || - h->attach_del || h->xlabel_changed) - { - /* L10N: The plural is choosen by the last %d, i.e. the total number */ - mutt_message(ngettext("Saving changed message... [%d/%d]", - "Saving changed messages... [%d/%d]", ctx->mailbox->msg_count), - i + 1, ctx->mailbox->msg_count); - if (!appendctx) - appendctx = mx_mbox_open(ctx->mailbox->path, MUTT_APPEND | MUTT_QUIET); - if (!appendctx) - mutt_debug(1, "Error opening mailbox in append mode\n"); - else - mutt_save_message_ctx(h, true, false, false, appendctx); - h->xlabel_changed = false; - } - } - } - -#ifdef USE_HCACHE - imap_hcache_close(idata); -#endif - - /* presort here to avoid doing 10 resorts in imap_exec_msgset */ - oldsort = Sort; - if (Sort != SORT_ORDER) - { - hdrs = ctx->mailbox->hdrs; - ctx->mailbox->hdrs = - mutt_mem_malloc(ctx->mailbox->msg_count * sizeof(struct Header *)); - memcpy(ctx->mailbox->hdrs, hdrs, ctx->mailbox->msg_count * sizeof(struct Header *)); - - Sort = SORT_ORDER; - qsort(ctx->mailbox->hdrs, ctx->mailbox->msg_count, sizeof(struct Header *), - mutt_get_sort_func(SORT_ORDER)); - } - - rc = sync_helper(idata, MUTT_ACL_DELETE, MUTT_DELETED, "\\Deleted"); - if (rc >= 0) - rc |= sync_helper(idata, MUTT_ACL_WRITE, MUTT_FLAG, "\\Flagged"); - if (rc >= 0) - rc |= sync_helper(idata, MUTT_ACL_WRITE, MUTT_OLD, "Old"); - if (rc >= 0) - rc |= sync_helper(idata, MUTT_ACL_SEEN, MUTT_READ, "\\Seen"); - if (rc >= 0) - rc |= sync_helper(idata, MUTT_ACL_WRITE, MUTT_REPLIED, "\\Answered"); - - if (oldsort != Sort) - { - Sort = oldsort; - FREE(&ctx->mailbox->hdrs); - ctx->mailbox->hdrs = hdrs; - } - - /* Flush the queued flags if any were changed in sync_helper. */ - if (rc > 0) - if (imap_exec(idata, NULL, 0) != IMAP_CMD_OK) - rc = -1; - - if (rc < 0) - { - if (ctx->mailbox->closing) - { - if (mutt_yesorno(_("Error saving flags. Close anyway?"), 0) == MUTT_YES) - { - rc = 0; - idata->state = IMAP_AUTHENTICATED; - goto out; - } - } - else - mutt_error(_("Error saving flags")); - rc = -1; - goto out; - } - - /* Update local record of server state to reflect the synchronization just - * completed. imap_read_headers always overwrites hcache-origin flags, so - * there is no need to mutate the hcache after flag-only changes. */ - for (int i = 0; i < ctx->mailbox->msg_count; i++) - { - HEADER_DATA(ctx->mailbox->hdrs[i])->deleted = ctx->mailbox->hdrs[i]->deleted; - HEADER_DATA(ctx->mailbox->hdrs[i])->flagged = ctx->mailbox->hdrs[i]->flagged; - HEADER_DATA(ctx->mailbox->hdrs[i])->old = ctx->mailbox->hdrs[i]->old; - HEADER_DATA(ctx->mailbox->hdrs[i])->read = ctx->mailbox->hdrs[i]->read; - HEADER_DATA(ctx->mailbox->hdrs[i])->replied = ctx->mailbox->hdrs[i]->replied; - ctx->mailbox->hdrs[i]->changed = false; - } - ctx->mailbox->changed = false; - - /* We must send an EXPUNGE command if we're not closing. */ - if (expunge && !(ctx->mailbox->closing) && mutt_bit_isset(ctx->mailbox->rights, MUTT_ACL_DELETE)) - { - mutt_message(_("Expunging messages from server...")); - /* Set expunge bit so we don't get spurious reopened messages */ - idata->reopen |= IMAP_EXPUNGE_EXPECTED; - if (imap_exec(idata, "EXPUNGE", 0) != 0) - { - idata->reopen &= ~IMAP_EXPUNGE_EXPECTED; - imap_error(_("imap_sync_mailbox: EXPUNGE failed"), idata->buf); - rc = -1; - goto out; - } - idata->reopen &= ~IMAP_EXPUNGE_EXPECTED; - } - - if (expunge && ctx->mailbox->closing) - { - imap_exec(idata, "CLOSE", IMAP_CMD_QUEUE); - idata->state = IMAP_AUTHENTICATED; - } - - if (MessageCacheClean) - imap_cache_clean(idata); - - rc = 0; - -out: - if (appendctx) - { - mx_fastclose_mailbox(appendctx); - FREE(&appendctx); - } - return rc; -} - /** * imap_tags_edit - Implements MxOps::tags_edit() */ diff --git a/imap/message.c b/imap/message.c index c609afc6d..050c19193 100644 --- a/imap/message.c +++ b/imap/message.c @@ -1330,261 +1330,6 @@ bail: return retval; } -/** - * imap_msg_open - Implements MxOps::msg_open() - */ -int imap_msg_open(struct Context *ctx, struct Message *msg, int msgno) -{ - struct Envelope *newenv = NULL; - char buf[LONG_STRING]; - char path[PATH_MAX]; - char *pc = NULL; - unsigned int bytes; - struct Progress progressbar; - unsigned int uid; - int cacheno; - struct ImapCache *cache = NULL; - bool retried = false; - bool read; - int rc; - - /* Sam's weird courier server returns an OK response even when FETCH - * fails. Thanks Sam. */ - bool fetched = false; - int output_progress; - - struct ImapData *idata = ctx->mailbox->data; - struct Header *h = ctx->mailbox->hdrs[msgno]; - - msg->fp = msg_cache_get(idata, h); - if (msg->fp) - { - if (HEADER_DATA(h)->parsed) - return 0; - else - goto parsemsg; - } - - /* we still do some caching even if imap_cachedir is unset */ - /* see if we already have the message in our cache */ - cacheno = HEADER_DATA(h)->uid % IMAP_CACHE_LEN; - cache = &idata->cache[cacheno]; - - if (cache->path) - { - /* don't treat cache errors as fatal, just fall back. */ - if (cache->uid == HEADER_DATA(h)->uid && (msg->fp = fopen(cache->path, "r"))) - return 0; - else - { - unlink(cache->path); - FREE(&cache->path); - } - } - - /* This function is called in a few places after endwin() - * e.g. mutt_pipe_message(). */ - output_progress = !isendwin(); - if (output_progress) - mutt_message(_("Fetching message...")); - - msg->fp = msg_cache_put(idata, h); - if (!msg->fp) - { - cache->uid = HEADER_DATA(h)->uid; - mutt_mktemp(path, sizeof(path)); - cache->path = mutt_str_strdup(path); - msg->fp = mutt_file_fopen(path, "w+"); - if (!msg->fp) - { - FREE(&cache->path); - return -1; - } - } - - /* mark this header as currently inactive so the command handler won't - * also try to update it. HACK until all this code can be moved into the - * command handler */ - h->active = false; - - snprintf(buf, sizeof(buf), "UID FETCH %u %s", HEADER_DATA(h)->uid, - (mutt_bit_isset(idata->capabilities, IMAP4REV1) ? - (ImapPeek ? "BODY.PEEK[]" : "BODY[]") : - "RFC822")); - - imap_cmd_start(idata, buf); - do - { - rc = imap_cmd_step(idata); - if (rc != IMAP_CMD_CONTINUE) - break; - - pc = idata->buf; - pc = imap_next_word(pc); - pc = imap_next_word(pc); - - if (mutt_str_strncasecmp("FETCH", pc, 5) == 0) - { - while (*pc) - { - pc = imap_next_word(pc); - if (pc[0] == '(') - pc++; - if (mutt_str_strncasecmp("UID", pc, 3) == 0) - { - pc = imap_next_word(pc); - if (mutt_str_atoui(pc, &uid) < 0) - goto bail; - if (uid != HEADER_DATA(h)->uid) - { - mutt_error(_( - "The message index is incorrect. Try reopening the mailbox.")); - } - } - else if ((mutt_str_strncasecmp("RFC822", pc, 6) == 0) || - (mutt_str_strncasecmp("BODY[]", pc, 6) == 0)) - { - pc = imap_next_word(pc); - if (imap_get_literal_count(pc, &bytes) < 0) - { - imap_error("imap_msg_open()", buf); - goto bail; - } - if (output_progress) - { - mutt_progress_init(&progressbar, _("Fetching message..."), - MUTT_PROGRESS_SIZE, NetInc, bytes); - } - if (imap_read_literal(msg->fp, idata, bytes, - output_progress ? &progressbar : NULL) < 0) - { - goto bail; - } - /* pick up trailing line */ - rc = imap_cmd_step(idata); - if (rc != IMAP_CMD_CONTINUE) - goto bail; - pc = idata->buf; - - fetched = true; - } - /* UW-IMAP will provide a FLAGS update here if the FETCH causes a - * change (eg from \Unseen to \Seen). - * Uncommitted changes in neomutt take precedence. If we decide to - * incrementally update flags later, this won't stop us syncing */ - else if ((mutt_str_strncasecmp("FLAGS", pc, 5) == 0) && !h->changed) - { - pc = imap_set_flags(idata, h, pc, NULL); - if (!pc) - goto bail; - } - } - } - } while (rc == IMAP_CMD_CONTINUE); - - /* see comment before command start. */ - h->active = true; - - fflush(msg->fp); - if (ferror(msg->fp)) - { - mutt_perror(cache->path); - goto bail; - } - - if (rc != IMAP_CMD_OK) - goto bail; - - if (!fetched || !imap_code(idata->buf)) - goto bail; - - msg_cache_commit(idata, h); - -parsemsg: - /* Update the header information. Previously, we only downloaded a - * portion of the headers, those required for the main display. - */ - rewind(msg->fp); - /* It may be that the Status header indicates a message is read, but the - * IMAP server doesn't know the message has been \Seen. So we capture - * the server's notion of 'read' and if it differs from the message info - * picked up in mutt_rfc822_read_header, we mark the message (and context - * changed). Another possibility: ignore Status on IMAP? */ - read = h->read; - newenv = mutt_rfc822_read_header(msg->fp, h, false, false); - mutt_env_merge(h->env, &newenv); - - /* see above. We want the new status in h->read, so we unset it manually - * and let mutt_set_flag set it correctly, updating context. */ - if (read != h->read) - { - h->read = read; - mutt_set_flag(ctx, h, MUTT_NEW, read); - } - - h->lines = 0; - fgets(buf, sizeof(buf), msg->fp); - while (!feof(msg->fp)) - { - h->lines++; - fgets(buf, sizeof(buf), msg->fp); - } - - h->content->length = ftell(msg->fp) - h->content->offset; - - mutt_clear_error(); - rewind(msg->fp); - HEADER_DATA(h)->parsed = true; - - /* retry message parse if cached message is empty */ - if (!retried && ((h->lines == 0) || (h->content->length == 0))) - { - imap_cache_del(idata, h); - retried = true; - goto parsemsg; - } - - return 0; - -bail: - mutt_file_fclose(&msg->fp); - imap_cache_del(idata, h); - if (cache->path) - { - unlink(cache->path); - FREE(&cache->path); - } - - return -1; -} - -/** - * imap_msg_close - Close an email - * @param ctx Mailbox - * @param msg Message to close - * @retval 0 Success - * - * @note May also return EOF Failure, see errno - */ -int imap_msg_close(struct Context *ctx, struct Message *msg) -{ - return mutt_file_fclose(&msg->fp); -} - -/** - * imap_msg_commit - Implements MxOps::msg_commit() - * - * @note May also return EOF Failure, see errno - */ -int imap_msg_commit(struct Context *ctx, struct Message *msg) -{ - int r = mutt_file_fclose(&msg->fp); - if (r != 0) - return r; - - return imap_append_message(ctx, msg); -} - /** * imap_append_message - Write an email back to the server * @param ctx Mailbox @@ -2027,3 +1772,255 @@ char *imap_set_flags(struct ImapData *idata, struct Header *h, char *s, int *ser return s; } + +/** + * imap_msg_open - Implements MxOps::msg_open() + */ +int imap_msg_open(struct Context *ctx, struct Message *msg, int msgno) +{ + struct Envelope *newenv = NULL; + char buf[LONG_STRING]; + char path[PATH_MAX]; + char *pc = NULL; + unsigned int bytes; + struct Progress progressbar; + unsigned int uid; + int cacheno; + struct ImapCache *cache = NULL; + bool retried = false; + bool read; + int rc; + + /* Sam's weird courier server returns an OK response even when FETCH + * fails. Thanks Sam. */ + bool fetched = false; + int output_progress; + + struct ImapData *idata = ctx->mailbox->data; + struct Header *h = ctx->mailbox->hdrs[msgno]; + + msg->fp = msg_cache_get(idata, h); + if (msg->fp) + { + if (HEADER_DATA(h)->parsed) + return 0; + else + goto parsemsg; + } + + /* we still do some caching even if imap_cachedir is unset */ + /* see if we already have the message in our cache */ + cacheno = HEADER_DATA(h)->uid % IMAP_CACHE_LEN; + cache = &idata->cache[cacheno]; + + if (cache->path) + { + /* don't treat cache errors as fatal, just fall back. */ + if (cache->uid == HEADER_DATA(h)->uid && (msg->fp = fopen(cache->path, "r"))) + return 0; + else + { + unlink(cache->path); + FREE(&cache->path); + } + } + + /* This function is called in a few places after endwin() + * e.g. mutt_pipe_message(). */ + output_progress = !isendwin(); + if (output_progress) + mutt_message(_("Fetching message...")); + + msg->fp = msg_cache_put(idata, h); + if (!msg->fp) + { + cache->uid = HEADER_DATA(h)->uid; + mutt_mktemp(path, sizeof(path)); + cache->path = mutt_str_strdup(path); + msg->fp = mutt_file_fopen(path, "w+"); + if (!msg->fp) + { + FREE(&cache->path); + return -1; + } + } + + /* mark this header as currently inactive so the command handler won't + * also try to update it. HACK until all this code can be moved into the + * command handler */ + h->active = false; + + snprintf(buf, sizeof(buf), "UID FETCH %u %s", HEADER_DATA(h)->uid, + (mutt_bit_isset(idata->capabilities, IMAP4REV1) ? + (ImapPeek ? "BODY.PEEK[]" : "BODY[]") : + "RFC822")); + + imap_cmd_start(idata, buf); + do + { + rc = imap_cmd_step(idata); + if (rc != IMAP_CMD_CONTINUE) + break; + + pc = idata->buf; + pc = imap_next_word(pc); + pc = imap_next_word(pc); + + if (mutt_str_strncasecmp("FETCH", pc, 5) == 0) + { + while (*pc) + { + pc = imap_next_word(pc); + if (pc[0] == '(') + pc++; + if (mutt_str_strncasecmp("UID", pc, 3) == 0) + { + pc = imap_next_word(pc); + if (mutt_str_atoui(pc, &uid) < 0) + goto bail; + if (uid != HEADER_DATA(h)->uid) + { + mutt_error(_( + "The message index is incorrect. Try reopening the mailbox.")); + } + } + else if ((mutt_str_strncasecmp("RFC822", pc, 6) == 0) || + (mutt_str_strncasecmp("BODY[]", pc, 6) == 0)) + { + pc = imap_next_word(pc); + if (imap_get_literal_count(pc, &bytes) < 0) + { + imap_error("imap_msg_open()", buf); + goto bail; + } + if (output_progress) + { + mutt_progress_init(&progressbar, _("Fetching message..."), + MUTT_PROGRESS_SIZE, NetInc, bytes); + } + if (imap_read_literal(msg->fp, idata, bytes, + output_progress ? &progressbar : NULL) < 0) + { + goto bail; + } + /* pick up trailing line */ + rc = imap_cmd_step(idata); + if (rc != IMAP_CMD_CONTINUE) + goto bail; + pc = idata->buf; + + fetched = true; + } + /* UW-IMAP will provide a FLAGS update here if the FETCH causes a + * change (eg from \Unseen to \Seen). + * Uncommitted changes in neomutt take precedence. If we decide to + * incrementally update flags later, this won't stop us syncing */ + else if ((mutt_str_strncasecmp("FLAGS", pc, 5) == 0) && !h->changed) + { + pc = imap_set_flags(idata, h, pc, NULL); + if (!pc) + goto bail; + } + } + } + } while (rc == IMAP_CMD_CONTINUE); + + /* see comment before command start. */ + h->active = true; + + fflush(msg->fp); + if (ferror(msg->fp)) + { + mutt_perror(cache->path); + goto bail; + } + + if (rc != IMAP_CMD_OK) + goto bail; + + if (!fetched || !imap_code(idata->buf)) + goto bail; + + msg_cache_commit(idata, h); + +parsemsg: + /* Update the header information. Previously, we only downloaded a + * portion of the headers, those required for the main display. + */ + rewind(msg->fp); + /* It may be that the Status header indicates a message is read, but the + * IMAP server doesn't know the message has been \Seen. So we capture + * the server's notion of 'read' and if it differs from the message info + * picked up in mutt_rfc822_read_header, we mark the message (and context + * changed). Another possibility: ignore Status on IMAP? */ + read = h->read; + newenv = mutt_rfc822_read_header(msg->fp, h, false, false); + mutt_env_merge(h->env, &newenv); + + /* see above. We want the new status in h->read, so we unset it manually + * and let mutt_set_flag set it correctly, updating context. */ + if (read != h->read) + { + h->read = read; + mutt_set_flag(ctx, h, MUTT_NEW, read); + } + + h->lines = 0; + fgets(buf, sizeof(buf), msg->fp); + while (!feof(msg->fp)) + { + h->lines++; + fgets(buf, sizeof(buf), msg->fp); + } + + h->content->length = ftell(msg->fp) - h->content->offset; + + mutt_clear_error(); + rewind(msg->fp); + HEADER_DATA(h)->parsed = true; + + /* retry message parse if cached message is empty */ + if (!retried && ((h->lines == 0) || (h->content->length == 0))) + { + imap_cache_del(idata, h); + retried = true; + goto parsemsg; + } + + return 0; + +bail: + mutt_file_fclose(&msg->fp); + imap_cache_del(idata, h); + if (cache->path) + { + unlink(cache->path); + FREE(&cache->path); + } + + return -1; +} + +/** + * imap_msg_commit - Implements MxOps::msg_commit() + * + * @note May also return EOF Failure, see errno + */ +int imap_msg_commit(struct Context *ctx, struct Message *msg) +{ + int r = mutt_file_fclose(&msg->fp); + if (r != 0) + return r; + + return imap_append_message(ctx, msg); +} + +/** + * imap_msg_close - Implements MxOps::msg_close() + * + * @note May also return EOF Failure, see errno + */ +int imap_msg_close(struct Context *ctx, struct Message *msg) +{ + return mutt_file_fclose(&msg->fp); +} -- 2.40.0