diff options
Diffstat (limited to 'src/lib-storage/index/imapc/imapc-mailbox.c')
-rw-r--r-- | src/lib-storage/index/imapc/imapc-mailbox.c | 994 |
1 files changed, 994 insertions, 0 deletions
diff --git a/src/lib-storage/index/imapc/imapc-mailbox.c b/src/lib-storage/index/imapc/imapc-mailbox.c new file mode 100644 index 0000000..ff915d9 --- /dev/null +++ b/src/lib-storage/index/imapc/imapc-mailbox.c @@ -0,0 +1,994 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "str.h" +#include "mail-index-modseq.h" +#include "imap-arg.h" +#include "imap-seqset.h" +#include "imap-util.h" +#include "imapc-mail.h" +#include "imapc-msgmap.h" +#include "imapc-list.h" +#include "imapc-search.h" +#include "imapc-sync.h" +#include "imapc-storage.h" + +#define NOTIFY_DELAY_MSECS 500 + +void imapc_mailbox_set_corrupted(struct imapc_mailbox *mbox, + const char *reason, ...) +{ + const char *errmsg; + va_list va; + + va_start(va, reason); + errmsg = t_strdup_printf("Mailbox '%s' state corrupted: %s", + mbox->box.name, t_strdup_vprintf(reason, va)); + va_end(va); + + mail_storage_set_internal_error(&mbox->storage->storage); + + if (!mbox->initial_sync_done) { + /* we failed during initial sync. need to rebuild indexes if + we want to get this fixed */ + mail_index_mark_corrupted(mbox->box.index); + } else { + /* maybe the remote server is buggy and has become confused. + try reconnecting. */ + } + imapc_client_mailbox_reconnect(mbox->client_box, errmsg); +} + +struct mail_index_view * +imapc_mailbox_get_sync_view(struct imapc_mailbox *mbox) +{ + if (mbox->sync_view == NULL) + mbox->sync_view = mail_index_view_open(mbox->box.index); + return mbox->sync_view; +} + +static void imapc_mailbox_init_delayed_trans(struct imapc_mailbox *mbox) +{ + if (mbox->delayed_sync_trans != NULL) + return; + + i_assert(mbox->delayed_sync_cache_view == NULL); + i_assert(mbox->delayed_sync_cache_trans == NULL); + + mbox->delayed_sync_trans = + mail_index_transaction_begin(imapc_mailbox_get_sync_view(mbox), + MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); + mbox->delayed_sync_view = + mail_index_transaction_open_updated_view(mbox->delayed_sync_trans); + + mbox->delayed_sync_cache_view = + mail_cache_view_open(mbox->box.cache, mbox->delayed_sync_view); + mbox->delayed_sync_cache_trans = + mail_cache_get_transaction(mbox->delayed_sync_cache_view, + mbox->delayed_sync_trans); +} + +static int imapc_mailbox_commit_delayed_expunges(struct imapc_mailbox *mbox) +{ + struct mail_index_view *view = imapc_mailbox_get_sync_view(mbox); + struct mail_index_transaction *trans; + struct seq_range_iter iter; + unsigned int n; + uint32_t lseq, uid; + int ret; + + trans = mail_index_transaction_begin(view, + MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); + + seq_range_array_iter_init(&iter, &mbox->delayed_expunged_uids); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &uid)) { + if (mail_index_lookup_seq(view, uid, &lseq)) + mail_index_expunge(trans, lseq); + } + array_clear(&mbox->delayed_expunged_uids); + ret = mail_index_transaction_commit(&trans); + if (ret < 0) + mailbox_set_index_error(&mbox->box); + return ret; +} + +int imapc_mailbox_commit_delayed_trans(struct imapc_mailbox *mbox, + bool force, bool *changes_r) +{ + int ret = 0; + + *changes_r = FALSE; + + if (mbox->delayed_sync_view != NULL) + mail_index_view_close(&mbox->delayed_sync_view); + if (mbox->delayed_sync_trans == NULL) + ; + else if (!mbox->selected && !force) { + /* ignore any changes done during SELECT */ + mail_index_transaction_rollback(&mbox->delayed_sync_trans); + } else { + if (mail_index_transaction_commit(&mbox->delayed_sync_trans) < 0) { + mailbox_set_index_error(&mbox->box); + ret = -1; + } + *changes_r = TRUE; + } + mbox->delayed_sync_cache_trans = NULL; + if (mbox->delayed_sync_cache_view != NULL) + mail_cache_view_close(&mbox->delayed_sync_cache_view); + + if (array_count(&mbox->delayed_expunged_uids) > 0) { + /* delayed expunges - commit them now in a separate + transaction. Reopen mbox->sync_view to see changes + committed in delayed_sync_trans. */ + if (mbox->sync_view != NULL) + mail_index_view_close(&mbox->sync_view); + if (imapc_mailbox_commit_delayed_expunges(mbox) < 0) + ret = -1; + } + + if (mbox->sync_view != NULL) + mail_index_view_close(&mbox->sync_view); + i_assert(mbox->delayed_sync_trans == NULL); + i_assert(mbox->delayed_sync_view == NULL); + i_assert(mbox->delayed_sync_cache_trans == NULL); + return ret; +} + +static void imapc_mailbox_idle_timeout(struct imapc_mailbox *mbox) +{ + timeout_remove(&mbox->to_idle_delay); + if (mbox->box.notify_callback != NULL) + mbox->box.notify_callback(&mbox->box, mbox->box.notify_context); +} + +static void imapc_mailbox_idle_notify(struct imapc_mailbox *mbox) +{ + struct ioloop *old_ioloop = current_ioloop; + + if (mbox->box.notify_callback != NULL && + mbox->to_idle_delay == NULL) { + io_loop_set_current(mbox->storage->root_ioloop); + mbox->to_idle_delay = + timeout_add_short(NOTIFY_DELAY_MSECS, + imapc_mailbox_idle_timeout, mbox); + io_loop_set_current(old_ioloop); + } +} + +static void +imapc_mailbox_index_expunge(struct imapc_mailbox *mbox, uint32_t uid) +{ + uint32_t lseq; + + if (mail_index_lookup_seq(mbox->sync_view, uid, &lseq)) + mail_index_expunge(mbox->delayed_sync_trans, lseq); + else if (mail_index_lookup_seq(mbox->delayed_sync_view, uid, &lseq)) { + /* this message exists only in this transaction. lib-index + can't currently handle expunging anything except the last + appended message in a transaction, and fixing it would be + quite a lot of trouble. so instead we'll just delay doing + this expunge until after the current transaction has been + committed. */ + seq_range_array_add(&mbox->delayed_expunged_uids, uid); + } else { + /* already expunged by another session */ + } +} + +static void +imapc_mailbox_fetch_state_finish(struct imapc_mailbox *mbox) +{ + uint32_t lseq, uid, msg_count; + + if (mbox->sync_next_lseq == 0) { + /* FETCH n:*, not 1:* */ + i_assert(mbox->state_fetched_success || + (mbox->box.flags & MAILBOX_FLAG_SAVEONLY) != 0); + return; + } + + /* if we haven't seen FETCH reply for some messages at the end of + mailbox they've been externally expunged. */ + msg_count = mail_index_view_get_messages_count(mbox->delayed_sync_view); + for (lseq = mbox->sync_next_lseq; lseq <= msg_count; lseq++) { + mail_index_lookup_uid(mbox->delayed_sync_view, lseq, &uid); + if (uid >= mbox->sync_uid_next) { + /* another process already added new messages to index + that our IMAP connection hasn't seen yet */ + break; + } + imapc_mailbox_index_expunge(mbox, uid); + } + + mbox->sync_next_lseq = 0; + mbox->sync_next_rseq = 0; + mbox->state_fetched_success = TRUE; +} + +static void +imapc_mailbox_fetch_state_callback(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_mailbox *mbox = context; + + mbox->state_fetching_uid1 = FALSE; + mbox->delayed_untagged_exists = FALSE; + imapc_client_stop(mbox->storage->client->client); + + switch (reply->state) { + case IMAPC_COMMAND_STATE_OK: + imapc_mailbox_fetch_state_finish(mbox); + break; + case IMAPC_COMMAND_STATE_NO: + imapc_copy_error_from_reply(mbox->storage, MAIL_ERROR_PARAMS, reply); + break; + case IMAPC_COMMAND_STATE_DISCONNECTED: + mail_storage_set_internal_error(mbox->box.storage); + + break; + default: + mail_storage_set_critical(mbox->box.storage, + "imapc: state FETCH failed: %s", reply->text_full); + break; + } +} + +void imap_mailbox_select_finish(struct imapc_mailbox *mbox) +{ + if (mbox->exists_count == 0) { + /* no mails. expunge everything. */ + mbox->sync_next_lseq = 1; + imapc_mailbox_init_delayed_trans(mbox); + imapc_mailbox_fetch_state_finish(mbox); + } + mbox->selected = TRUE; +} + +bool +imapc_mailbox_fetch_state(struct imapc_mailbox *mbox, uint32_t first_uid) +{ + struct imapc_command *cmd; + + if (mbox->exists_count == 0) { + /* empty mailbox - no point in fetching anything. + just make sure everything is expunged in local index. + Delay calling imapc_mailbox_fetch_state_finish() until + SELECT finishes, so we see the updated UIDNEXT. */ + return FALSE; + } + if (mbox->state_fetching_uid1) { + /* retrying after reconnection - don't send duplicate */ + return FALSE; + } + + string_t *str = t_str_new(64); + str_printfa(str, "UID FETCH %u:* (FLAGS", first_uid); + if (imapc_mailbox_has_modseqs(mbox)) { + str_append(str, " MODSEQ"); + mail_index_modseq_enable(mbox->box.index); + } + if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GMAIL_MIGRATION)) { + enum mailbox_info_flags flags; + + if (!mail_index_is_in_memory(mbox->box.index)) { + /* these can be efficiently fetched among flags and + stored into cache */ + str_append(str, " X-GM-MSGID"); + } + /* do this only for the \All mailbox */ + if (imapc_list_get_mailbox_flags(mbox->box.list, + mbox->box.name, &flags) == 0 && + (flags & MAILBOX_SPECIALUSE_ALL) != 0) + str_append(str, " X-GM-LABELS"); + + } + str_append_c(str, ')'); + + cmd = imapc_client_mailbox_cmd(mbox->client_box, + imapc_mailbox_fetch_state_callback, mbox); + if (first_uid == 1) { + mbox->sync_next_lseq = 1; + mbox->sync_next_rseq = 1; + mbox->state_fetched_success = FALSE; + /* only the FETCH 1:* is retriable - others will be retried + by the 1:* after the reconnection */ + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); + } + mbox->state_fetching_uid1 = first_uid == 1; + imapc_command_send(cmd, str_c(str)); + return TRUE; +} + +static void +imapc_untagged_exists(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + struct mail_index_view *view; + uint32_t exists_count = reply->num; + + if (mbox == NULL) + return; + if (mbox->exists_received && + IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES)) { + /* ignore all except the first EXISTS reply (returned by + SELECT) */ + return; + } + + mbox->exists_count = exists_count; + mbox->exists_received = TRUE; + + view = mbox->delayed_sync_view; + if (view == NULL) + view = imapc_mailbox_get_sync_view(mbox); + + if (mbox->selecting) { + /* We don't know the latest flags, refresh them. */ + (void)imapc_mailbox_fetch_state(mbox, 1); + } else if (mbox->sync_fetch_first_uid != 1) { + const struct mail_index_header *hdr; + hdr = mail_index_get_header(view); + mbox->sync_fetch_first_uid = hdr->next_uid; + mbox->delayed_untagged_exists = TRUE; + } + imapc_mailbox_idle_notify(mbox); +} + +static bool keywords_are_equal(struct mail_keywords *kw, + const ARRAY_TYPE(keyword_indexes) *kw_arr) +{ + const unsigned int *kw_idx; + unsigned int i, j, count; + + kw_idx = array_get(kw_arr, &count); + if (count != kw->count) + return FALSE; + + /* there are normally only a few keywords, so O(n^2) is fine */ + for (i = 0; i < count; i++) { + for (j = 0; j < count; j++) { + if (kw->idx[i] == kw_idx[j]) + break; + } + if (j == count) + return FALSE; + } + return TRUE; +} + +static int +imapc_mailbox_msgmap_update(struct imapc_mailbox *mbox, + uint32_t rseq, uint32_t fetch_uid, + uint32_t *lseq_r, uint32_t *uid_r, + bool *new_message_r) +{ + struct imapc_msgmap *msgmap; + uint32_t uid, msg_count, rseq2; + + *lseq_r = 0; + *uid_r = uid = fetch_uid; + *new_message_r = FALSE; + + if (rseq > mbox->exists_count) { + /* Receiving a FETCH for a message that EXISTS hasn't + announced yet. MS Exchange has a bug where our UID FETCH + request sometimes sends replies where sequences are above + EXISTS value, but their UIDs are for existing messages. + We'll just ignore these replies. */ + return 0; + } + if (rseq < mbox->prev_skipped_rseq && + fetch_uid > mbox->prev_skipped_uid) { + /* This was the initial attempt at catching the above + MS Exchange bug, but the above one appears to catch all + these cases. But keep it here just in case. */ + imapc_mailbox_set_corrupted(mbox, + "FETCH sequence/UID order is mixed " + "(seq=%u,%u vs uid=%u,%u)", + mbox->prev_skipped_rseq, rseq, + mbox->prev_skipped_uid, fetch_uid); + return -1; + } + + msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box); + msg_count = imapc_msgmap_count(msgmap); + if (fetch_uid != 0 && mbox->state_fetched_success && + (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_MSN_WORKAROUNDS) || + IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES))) { + /* if we know the UID, use own own generated rseq instead of + the potentially broken rseq that the server sent. + Skip this during the initial FETCH 1:* (UID ..) handling, + or we can't detect duplicate UIDs and will instead + assert-crash later on. */ + uint32_t fixed_rseq; + + if (imapc_msgmap_uid_to_rseq(msgmap, fetch_uid, &fixed_rseq)) + rseq = fixed_rseq; + else if (fetch_uid >= imapc_msgmap_uidnext(msgmap) && + rseq <= msg_count) { + /* The current rseq is wrong. Lets hope that the + correct rseq is the next new one. This happens + especially with no-msn-updates when mails have been + expunged and new mails arrive in the same session. */ + rseq = msg_count+1; + } + } + + if (rseq <= msg_count) { + uid = imapc_msgmap_rseq_to_uid(msgmap, rseq); + if (uid != fetch_uid && fetch_uid != 0) { + imapc_mailbox_set_corrupted(mbox, + "FETCH UID mismatch (%u != %u)", + fetch_uid, uid); + return -1; + } + *uid_r = uid; + } else if (fetch_uid == 0 || rseq != msg_count+1) { + /* probably a flag update for a message we haven't yet + received our initial UID FETCH for. we should get + another one. */ + if (fetch_uid == 0) + return 0; + + if (imapc_msgmap_uid_to_rseq(msgmap, fetch_uid, &rseq2)) { + imapc_mailbox_set_corrupted(mbox, + "FETCH returned wrong sequence for UID %u " + "(%u != %u)", fetch_uid, rseq, rseq2); + return -1; + } + mbox->prev_skipped_rseq = rseq; + mbox->prev_skipped_uid = fetch_uid; + /* Check if this uid must be added later when syncing. */ + *new_message_r = TRUE; + } else if (fetch_uid < imapc_msgmap_uidnext(msgmap)) { + imapc_mailbox_set_corrupted(mbox, + "Expunged message reappeared in session " + "(uid=%u < next_uid=%u)", + fetch_uid, imapc_msgmap_uidnext(msgmap)); + return -1; + } else { + /* newly seen message */ + imapc_msgmap_append(msgmap, rseq, uid); + if (uid < mbox->min_append_uid || + uid < mail_index_get_header(mbox->delayed_sync_view)->next_uid) { + /* message is already added to index */ + } else if (mbox->state_fetching_uid1) { + /* Initial fetching, allow messages to be appened to + index directly */ + mail_index_append(mbox->delayed_sync_trans, + uid, lseq_r); + mbox->min_append_uid = uid + 1; + } else { + /* message is not yet added to index, in order to + prevent log synchronization errors add this + message later, when the mailbox is synced. */ + *new_message_r = TRUE; + } + } + return 0; +} + +bool imapc_mailbox_name_equals(struct imapc_mailbox *mbox, + const char *remote_name) +{ + const char *imapc_remote_name = + imapc_mailbox_get_remote_name(mbox); + + if (strcmp(imapc_remote_name, remote_name) == 0) { + /* match */ + return TRUE; + } else if (strcasecmp(mbox->box.name, "INBOX") == 0 && + strcasecmp(remote_name, "INBOX") == 0) { + /* case-insensitive INBOX */ + return TRUE; + } + return FALSE; +} + +static struct imapc_untagged_fetch_ctx * +imapc_untagged_fetch_ctx_create(void) +{ + pool_t pool = pool_alloconly_create("imapc untagged fetch ctx", 128); + struct imapc_untagged_fetch_ctx *ctx = + p_new(pool, struct imapc_untagged_fetch_ctx, 1); + ctx->pool = pool; + return ctx; +} + +void imapc_untagged_fetch_ctx_free(struct imapc_untagged_fetch_ctx **_ctx) +{ + struct imapc_untagged_fetch_ctx *ctx = *_ctx; + + *_ctx = NULL; + i_assert(ctx != NULL); + + pool_unref(&ctx->pool); +} + +void imapc_untagged_fetch_update_flags(struct imapc_mailbox *mbox, + struct imapc_untagged_fetch_ctx *ctx, + struct mail_index_view *view, + uint32_t lseq) +{ + ARRAY_TYPE(keyword_indexes) old_kws; + struct mail_keywords *kw; + const struct mail_index_record *rec = NULL; + const char *atom; + + if (!ctx->have_flags) + return; + + rec = mail_index_lookup(view, lseq); + if (rec->flags != ctx->flags) { + mail_index_update_flags(mbox->delayed_sync_trans, lseq, + MODIFY_REPLACE, ctx->flags); + } + + t_array_init(&old_kws, 8); + mail_index_lookup_keywords(view, lseq, &old_kws); + + if (ctx->have_gmail_labels) { + /* add keyword for mails that have GMail labels. + this can be used for "All Mail" mailbox migrations + with dsync */ + atom = "$GMailHaveLabels"; + array_push_back(&ctx->keywords, &atom); + } + + array_append_zero(&ctx->keywords); + kw = mail_index_keywords_create(mbox->box.index, + array_front(&ctx->keywords)); + if (!keywords_are_equal(kw, &old_kws)) { + mail_index_update_keywords(mbox->delayed_sync_trans, + lseq, MODIFY_REPLACE, kw); + } + mail_index_keywords_unref(&kw); +} + +static bool imapc_untagged_fetch_handle(struct imapc_mailbox *mbox, + struct imapc_untagged_fetch_ctx *ctx, + uint32_t rseq) +{ + uint32_t lseq; + bool new_message; + + imapc_mailbox_init_delayed_trans(mbox); + if (imapc_mailbox_msgmap_update(mbox, rseq, ctx->fetch_uid, + &lseq, &ctx->uid, + &new_message) < 0 || ctx->uid == 0) + return FALSE; + + if ((ctx->flags & MAIL_RECENT) == 0 && mbox->highest_nonrecent_uid < ctx->uid) { + /* remember for STATUS_FIRST_RECENT_UID */ + mbox->highest_nonrecent_uid = ctx->uid; + } + /* FIXME: we should ideally also pass these through so they show up + to clients. */ + ctx->flags &= ENUM_NEGATE(MAIL_RECENT); + + if (lseq == 0) { + if (!mail_index_lookup_seq(mbox->delayed_sync_view, + ctx->uid, &lseq)) { + /* already expunged by another session */ + if (rseq == mbox->sync_next_rseq) + mbox->sync_next_rseq++; + return new_message; + } + } + + if (rseq == mbox->sync_next_rseq) { + /* we're doing the initial full sync of mails. expunge any + mails that no longer exist. */ + while (mbox->sync_next_lseq < lseq) { + mail_index_lookup_uid(mbox->delayed_sync_view, + mbox->sync_next_lseq, &ctx->uid); + imapc_mailbox_index_expunge(mbox, ctx->uid); + mbox->sync_next_lseq++; + } + i_assert(lseq == mbox->sync_next_lseq); + mbox->sync_next_rseq++; + mbox->sync_next_lseq++; + } + + if (!new_message) { + /* Only update flags immediately for existing messages */ + imapc_untagged_fetch_update_flags(mbox, ctx, + mbox->delayed_sync_view, lseq); + } + + if (ctx->modseq != 0) { + if (mail_index_modseq_lookup(mbox->delayed_sync_view, lseq) < ctx->modseq) + mail_index_update_modseq(mbox->delayed_sync_trans, lseq, ctx->modseq); + array_idx_set(&mbox->rseq_modseqs, rseq-1, &ctx->modseq); + } + if (ctx->guid != NULL) { + struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(&mbox->box); + const enum index_cache_field guid_cache_idx = + ibox->cache_fields[MAIL_CACHE_GUID].idx; + + if (mail_cache_field_can_add(mbox->delayed_sync_cache_trans, + lseq, guid_cache_idx)) { + mail_cache_add(mbox->delayed_sync_cache_trans, lseq, + guid_cache_idx, ctx->guid, strlen(ctx->guid)); + } + } + return new_message; +} + +static bool imapc_untagged_fetch_parse(struct imapc_mailbox *mbox, + struct imapc_untagged_fetch_ctx *ctx, + const struct imap_arg *list) +{ + const struct imap_arg *flags_list, *modseq_list; + const char *atom, *patom; + unsigned int i, j; + + ctx->fetch_uid = 0; ctx->flags = 0; + for (i = 0; list[i].type != IMAP_ARG_EOL; i += 2) { + if (!imap_arg_get_atom(&list[i], &atom) || + list[i+1].type == IMAP_ARG_EOL) + return FALSE; + + if (strcasecmp(atom, "UID") == 0) { + if (!imap_arg_get_atom(&list[i+1], &atom) || + str_to_uint32(atom, &ctx->fetch_uid) < 0) + return FALSE; + } else if (strcasecmp(atom, "FLAGS") == 0) { + if (!imap_arg_get_list(&list[i+1], &flags_list)) + return FALSE; + + p_array_init(&ctx->keywords, ctx->pool, 8); + ctx->have_flags = TRUE; + for (j = 0; flags_list[j].type != IMAP_ARG_EOL; j++) { + if (!imap_arg_get_atom(&flags_list[j], &atom)) + return FALSE; + if (atom[0] == '\\') + ctx->flags |= imap_parse_system_flag(atom); + else { + patom = p_strdup(ctx->pool, atom); + /* keyword */ + array_push_back(&ctx->keywords, &patom); + } + } + } else if (strcasecmp(atom, "MODSEQ") == 0 && + imapc_mailbox_has_modseqs(mbox)) { + /* (modseq-number) */ + if (!imap_arg_get_list(&list[i+1], &modseq_list)) + return FALSE; + if (!imap_arg_get_atom(&modseq_list[0], &atom) || + str_to_uint64(atom, &ctx->modseq) < 0 || + modseq_list[1].type != IMAP_ARG_EOL) + return FALSE; + } else if (strcasecmp(atom, "X-GM-MSGID") == 0 && + !mbox->initial_sync_done) { + if (imap_arg_get_atom(&list[i+1], &atom)) + ctx->guid = atom; + } else if (strcasecmp(atom, "X-GM-LABELS") == 0 && + IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GMAIL_MIGRATION)) { + if (!imap_arg_get_list(&list[i+1], &flags_list)) + return FALSE; + for (j = 0; flags_list[j].type != IMAP_ARG_EOL; j++) { + if (!imap_arg_get_astring(&flags_list[j], &atom)) + return FALSE; + if (strcasecmp(atom, "\\Muted") != 0) + ctx->have_gmail_labels = TRUE; + } + } + } + if (ctx->fetch_uid == 0 && + IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES)) { + /* UID missing and we're not tracking MSNs */ + return FALSE; + } + + return TRUE; +} + +static void imapc_untagged_fetch(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + const struct imap_arg *list; + struct imapc_fetch_request *fetch_request; + struct imapc_mail *mail; + bool new_message = FALSE; + + if (mbox == NULL || reply->num == 0 || !imap_arg_get_list(reply->args, &list)) + return; + + struct imapc_untagged_fetch_ctx *ctx = + imapc_untagged_fetch_ctx_create(); + if (!imapc_untagged_fetch_parse(mbox, ctx, list)) { + imapc_untagged_fetch_ctx_free(&ctx); + return; + } + + new_message = imapc_untagged_fetch_handle(mbox, ctx, reply->num); + + /* if this is a reply to some FETCH request, update the mail's fields */ + array_foreach_elem(&mbox->fetch_requests, fetch_request) { + array_foreach_elem(&fetch_request->mails, mail) { + if (mail->imail.mail.mail.uid == ctx->uid) + imapc_mail_fetch_update(mail, reply, list); + } + } + + if (!new_message) { + /* Handling this context is finished if the mail was not new + to the local index. It has not been added to + mbox->untagged_fetch_contexts so no need to delete it from + the array. The context itself can be freed here. */ + imapc_untagged_fetch_ctx_free(&ctx); + } else { + /* If this is a new message store this context to be handled + when syncing */ + array_push_back(&mbox->untagged_fetch_contexts, &ctx); + } + imapc_mailbox_idle_notify(mbox); +} + +static void imapc_untagged_expunge(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + struct imapc_msgmap *msgmap; + uint32_t uid, rseq = reply->num; + + if (mbox == NULL || rseq == 0 || + IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES)) + return; + + mbox->prev_skipped_rseq = 0; + mbox->prev_skipped_uid = 0; + + if (mbox->exists_count == 0) { + imapc_mailbox_set_corrupted(mbox, + "EXPUNGE received for empty mailbox"); + return; + } + mbox->exists_count--; + + msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box); + if (rseq > imapc_msgmap_count(msgmap)) { + /* we haven't even seen this message yet */ + return; + } + uid = imapc_msgmap_rseq_to_uid(msgmap, rseq); + imapc_msgmap_expunge(msgmap, rseq); + if (array_is_created(&mbox->rseq_modseqs)) + array_delete(&mbox->rseq_modseqs, rseq-1, 1); + + imapc_mailbox_init_delayed_trans(mbox); + imapc_mailbox_index_expunge(mbox, uid); + imapc_mailbox_idle_notify(mbox); +} + +static void +imapc_untagged_esearch_gmail_pop3(const struct imap_arg *args, + struct imapc_mailbox *mbox) +{ + struct imapc_msgmap *msgmap; + const char *atom; + struct seq_range_iter iter; + ARRAY_TYPE(seq_range) rseqs; + unsigned int n; + uint32_t rseq, lseq, uid; + ARRAY_TYPE(keyword_indexes) keywords; + struct mail_keywords *kw; + unsigned int pop3_deleted_kw_idx; + + i_free_and_null(mbox->sync_gmail_pop3_search_tag); + + /* It should contain ALL <seqset> or nonexistent if nothing matched */ + if (args[0].type == IMAP_ARG_EOL) + return; + t_array_init(&rseqs, 64); + if (!imap_arg_atom_equals(&args[0], "ALL") || + !imap_arg_get_atom(&args[1], &atom) || + imap_seq_set_nostar_parse(atom, &rseqs) < 0) { + i_error("Invalid gmail-pop3 ESEARCH reply"); + return; + } + + mail_index_keyword_lookup_or_create(mbox->box.index, + mbox->storage->set->pop3_deleted_flag, &pop3_deleted_kw_idx); + + t_array_init(&keywords, 1); + array_push_back(&keywords, &pop3_deleted_kw_idx); + kw = mail_index_keywords_create_from_indexes(mbox->box.index, &keywords); + + msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box); + seq_range_array_iter_init(&iter, &rseqs); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &rseq)) { + if (rseq > imapc_msgmap_count(msgmap)) { + /* we haven't even seen this message yet */ + break; + } + uid = imapc_msgmap_rseq_to_uid(msgmap, rseq); + if (!mail_index_lookup_seq(mbox->delayed_sync_view, + uid, &lseq)) + continue; + + /* add the pop3_deleted_flag */ + mail_index_update_keywords(mbox->delayed_sync_trans, + lseq, MODIFY_ADD, kw); + } + mail_index_keywords_unref(&kw); +} + +static void imapc_untagged_search(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + if (mbox != NULL) + imapc_search_reply_search(reply->args, mbox); +} + +static void imapc_untagged_esearch(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + const struct imap_arg *tag_list; + const char *str; + + if (mbox == NULL || !imap_arg_get_list(reply->args, &tag_list)) + return; + + /* ESEARCH begins with (TAG <tag>) */ + if (!imap_arg_atom_equals(&tag_list[0], "TAG") || + !imap_arg_get_string(&tag_list[1], &str) || + tag_list[2].type != IMAP_ARG_EOL) + return; + + /* for now the only ESEARCH reply that we have is for getting GMail's + list of hidden POP3 messages. */ + if (mbox->sync_gmail_pop3_search_tag != NULL && + strcmp(mbox->sync_gmail_pop3_search_tag, str) == 0) + imapc_untagged_esearch_gmail_pop3(reply->args+1, mbox); + else + imapc_search_reply_esearch(reply->args+1, mbox); +} + +static void imapc_sync_uid_validity(struct imapc_mailbox *mbox) +{ + const struct mail_index_header *hdr; + + imapc_mailbox_init_delayed_trans(mbox); + hdr = mail_index_get_header(mbox->delayed_sync_view); + if (hdr->uid_validity != mbox->sync_uid_validity && + mbox->sync_uid_validity != 0) { + if (hdr->uid_validity != 0) { + /* uidvalidity changed, reset the entire mailbox */ + mail_index_reset(mbox->delayed_sync_trans); + mbox->sync_fetch_first_uid = 1; + /* The reset needs to be committed before FETCH 1:* + results are received. */ + bool changes; + if (imapc_mailbox_commit_delayed_trans(mbox, TRUE, &changes) < 0) + mail_index_mark_corrupted(mbox->box.index); + imapc_mailbox_init_delayed_trans(mbox); + } + mail_index_update_header(mbox->delayed_sync_trans, + offsetof(struct mail_index_header, uid_validity), + &mbox->sync_uid_validity, + sizeof(mbox->sync_uid_validity), TRUE); + } +} + +static void +imapc_resp_text_uidvalidity(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + uint32_t uid_validity; + + if (mbox == NULL || + str_to_uint32(reply->resp_text_value, &uid_validity) < 0 || + uid_validity == 0) + return; + + if (mbox->sync_uid_validity != uid_validity) { + mbox->sync_uid_validity = uid_validity; + imapc_mail_cache_free(&mbox->prev_mail_cache); + imapc_sync_uid_validity(mbox); + } +} + +static void +imapc_resp_text_uidnext(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + uint32_t uid_next; + + if (mbox == NULL || + str_to_uint32(reply->resp_text_value, &uid_next) < 0) + return; + + mbox->sync_uid_next = uid_next; +} + +static void +imapc_resp_text_highestmodseq(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + uint64_t highestmodseq; + + if (mbox == NULL || + str_to_uint64(reply->resp_text_value, &highestmodseq) < 0) + return; + + mbox->sync_highestmodseq = highestmodseq; +} + +static void +imapc_resp_text_permanentflags(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + const struct imap_arg *flags_args, *arg; + const char *flag; + unsigned int idx; + + i_assert(reply->args[0].type == IMAP_ARG_ATOM); + + if (mbox == NULL || !imap_arg_get_list(&reply->args[1], &flags_args)) + return; + + mbox->permanent_flags = 0; + mbox->box.disallow_new_keywords = TRUE; + + for (arg = flags_args; arg->type != IMAP_ARG_EOL; arg++) { + if (!imap_arg_get_atom(arg, &flag)) + continue; + + if (strcmp(flag, "\\*") == 0) + mbox->box.disallow_new_keywords = FALSE; + else if (*flag == '\\') + mbox->permanent_flags |= imap_parse_system_flag(flag); + else { + /* we'll simply make sure that it exists in the index */ + mail_index_keyword_lookup_or_create(mbox->box.index, + flag, &idx); + } + } +} + +void imapc_mailbox_register_untagged(struct imapc_mailbox *mbox, + const char *key, + imapc_mailbox_callback_t *callback) +{ + struct imapc_mailbox_event_callback *cb; + + cb = array_append_space(&mbox->untagged_callbacks); + cb->name = p_strdup(mbox->box.pool, key); + cb->callback = callback; +} + +void imapc_mailbox_register_resp_text(struct imapc_mailbox *mbox, + const char *key, + imapc_mailbox_callback_t *callback) +{ + struct imapc_mailbox_event_callback *cb; + + cb = array_append_space(&mbox->resp_text_callbacks); + cb->name = p_strdup(mbox->box.pool, key); + cb->callback = callback; +} + +void imapc_mailbox_register_callbacks(struct imapc_mailbox *mbox) +{ + imapc_mailbox_register_untagged(mbox, "EXISTS", + imapc_untagged_exists); + imapc_mailbox_register_untagged(mbox, "FETCH", + imapc_untagged_fetch); + imapc_mailbox_register_untagged(mbox, "EXPUNGE", + imapc_untagged_expunge); + imapc_mailbox_register_untagged(mbox, "SEARCH", + imapc_untagged_search); + imapc_mailbox_register_untagged(mbox, "ESEARCH", + imapc_untagged_esearch); + imapc_mailbox_register_resp_text(mbox, "UIDVALIDITY", + imapc_resp_text_uidvalidity); + imapc_mailbox_register_resp_text(mbox, "UIDNEXT", + imapc_resp_text_uidnext); + imapc_mailbox_register_resp_text(mbox, "HIGHESTMODSEQ", + imapc_resp_text_highestmodseq); + imapc_mailbox_register_resp_text(mbox, "PERMANENTFLAGS", + imapc_resp_text_permanentflags); +} |