/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "istream.h" #include "istream-crlf.h" #include "ostream.h" #include "imap-date.h" #include "imap-util.h" #include "imap-seqset.h" #include "imap-quote.h" #include "index-mail.h" #include "mail-copy.h" #include "mailbox-list-private.h" #include "imapc-msgmap.h" #include "imapc-storage.h" #include "imapc-sync.h" #include "imapc-mail.h" #include "seq-set-builder.h" struct imapc_save_context { struct mail_save_context ctx; struct imapc_mailbox *mbox; struct imapc_mailbox *src_mbox; struct mail_index_transaction *trans; int fd; char *temp_path; struct istream *input; uint32_t dest_uid_validity; ARRAY_TYPE(seq_range) dest_saved_uids; unsigned int save_count; bool failed:1; bool finished:1; }; struct imapc_save_cmd_context { struct imapc_save_context *ctx; int ret; }; #define IMAPC_SAVECTX(s) container_of(s, struct imapc_save_context, ctx) #define IMAPC_SERVER_CMDLINE_MAX_LEN 8000 void imapc_transaction_save_rollback(struct mail_save_context *_ctx); static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox); struct mail_save_context * imapc_save_alloc(struct mailbox_transaction_context *t) { struct imapc_mailbox *mbox = IMAPC_MAILBOX(t->box); struct imapc_save_context *ctx; i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0); if (t->save_ctx == NULL) { ctx = i_new(struct imapc_save_context, 1); ctx->ctx.transaction = t; ctx->mbox = mbox; ctx->src_mbox = NULL; ctx->trans = t->itrans; ctx->fd = -1; t->save_ctx = &ctx->ctx; } return t->save_ctx; } int imapc_save_begin(struct mail_save_context *_ctx, struct istream *input) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); const char *path; i_assert(ctx->fd == -1); if (imapc_storage_client_handle_auth_failure(ctx->mbox->storage->client)) return -1; ctx->fd = imapc_client_create_temp_fd(ctx->mbox->storage->client->client, &path); if (ctx->fd == -1) { mail_set_critical(_ctx->dest_mail, "Couldn't create temp file %s", path); ctx->failed = TRUE; return -1; } /* we may not know the size of the input, or be sure that it contains only CRLFs. so we'll always first write the mail to a temp file and upload it from there to remote server. */ ctx->finished = FALSE; ctx->temp_path = i_strdup(path); ctx->input = i_stream_create_crlf(input); _ctx->data.output = o_stream_create_fd_file(ctx->fd, 0, FALSE); o_stream_cork(_ctx->data.output); return 0; } int imapc_save_continue(struct mail_save_context *_ctx) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); if (ctx->failed) return -1; if (index_storage_save_continue(_ctx, ctx->input, NULL) < 0) { ctx->failed = TRUE; return -1; } return 0; } static void imapc_save_appenduid(struct imapc_save_context *ctx, const struct imapc_command_reply *reply, uint32_t *uid_r) { const char *const *args; uint32_t uid_validity, dest_uid; *uid_r = 0; /* */ args = t_strsplit(reply->resp_text_value, " "); if (str_array_length(args) != 2) return; if (str_to_uint32(args[0], &uid_validity) < 0) return; if (ctx->dest_uid_validity == 0) ctx->dest_uid_validity = uid_validity; else if (ctx->dest_uid_validity != uid_validity) return; if (str_to_uint32(args[1], &dest_uid) == 0) { seq_range_array_add_with_init(&ctx->dest_saved_uids, 32, dest_uid); *uid_r = dest_uid; } } static void imapc_save_add_to_index(struct imapc_save_context *ctx, uint32_t uid) { struct mail *_mail = ctx->ctx.dest_mail; struct index_mail *imail = INDEX_MAIL(_mail); uint32_t seq; /* we'll temporarily append messages and at commit time expunge them all, since we can't guarantee that no one else has saved messages to remote server during our transaction */ mail_index_append(ctx->trans, uid, &seq); mail_set_seq_saving(_mail, seq); imail->data.no_caching = TRUE; imail->data.forced_no_caching = TRUE; if (ctx->fd != -1) { struct imapc_mail *imapc_mail = IMAPC_MAIL(_mail); imail->data.stream = i_stream_create_fd_autoclose(&ctx->fd, 0); imapc_mail->header_fetched = TRUE; imapc_mail->body_fetched = TRUE; /* The saved stream wasn't actually read, but it needs to be set accessed to avoid assert-crash. */ _mail->mail_stream_accessed = TRUE; imapc_mail_init_stream(imapc_mail); } ctx->save_count++; } static void imapc_save_callback(const struct imapc_command_reply *reply, void *context) { struct imapc_save_cmd_context *ctx = context; uint32_t uid = 0; if (reply->state == IMAPC_COMMAND_STATE_OK) { if (reply->resp_text_key != NULL && strcasecmp(reply->resp_text_key, "APPENDUID") == 0) imapc_save_appenduid(ctx->ctx, reply, &uid); imapc_save_add_to_index(ctx->ctx, uid); ctx->ret = 0; } else if (imapc_storage_client_handle_auth_failure(ctx->ctx->mbox->storage->client)) { ctx->ret = -1; } else if (reply->state == IMAPC_COMMAND_STATE_NO) { imapc_copy_error_from_reply(ctx->ctx->mbox->storage, MAIL_ERROR_PARAMS, reply); ctx->ret = -1; } else { mailbox_set_critical(&ctx->ctx->mbox->box, "imapc: APPEND failed: %s", reply->text_full); ctx->ret = -1; } imapc_client_stop(ctx->ctx->mbox->storage->client->client); } static void imapc_save_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context) { struct imapc_save_cmd_context *ctx = context; /* we don't really care about the reply */ ctx->ret = 0; imapc_client_stop(ctx->ctx->mbox->storage->client->client); } static void imapc_copy_rollback_store_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context) { struct imapc_save_context *ctx = context; /* Can't do much about a non successful STORE here */ if (reply->state != IMAPC_COMMAND_STATE_OK) { e_error(ctx->src_mbox->box.event, "imapc: Failed to set \\Deleted flag for rolling back " "failed copy: %s", reply->text_full); ctx->src_mbox->rollback_pending = FALSE; ctx->finished = TRUE; ctx->failed = TRUE; } else { i_assert(ctx->src_mbox->rollback_pending); } /* No need stop the imapc client here there is always an additional expunge callback after this. */ } static void imapc_copy_rollback_expunge_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context) { struct imapc_save_context *ctx = context; /* Can't do much about a non successful EXPUNGE here */ if (reply->state != IMAPC_COMMAND_STATE_OK) { e_error(ctx->src_mbox->box.event, "imapc: Failed to expunge messages for rolling back " "failed copy: %s", reply->text_full); ctx->src_mbox->rollback_pending = FALSE; ctx->finished = TRUE; ctx->failed = TRUE; } else { ctx->finished = TRUE; ctx->src_mbox->rollback_pending = FALSE; } imapc_client_stop(ctx->src_mbox->storage->client->client); } static void imapc_append_keywords(string_t *str, struct mail_keywords *kw) { const ARRAY_TYPE(keywords) *kw_arr; const char *kw_str; unsigned int i; kw_arr = mail_index_get_keywords(kw->index); for (i = 0; i < kw->count; i++) { kw_str = array_idx_elem(kw_arr, kw->idx[i]); if (str_len(str) > 1) str_append_c(str, ' '); str_append(str, kw_str); } } static int imapc_save_append(struct imapc_save_context *ctx) { struct mail_save_context *_ctx = &ctx->ctx; struct mail_save_data *mdata = &_ctx->data; struct imapc_command *cmd; struct imapc_save_cmd_context sctx; struct istream *input; const char *flags = "", *internaldate = ""; if (mdata->flags != 0 || mdata->keywords != NULL) { string_t *str = t_str_new(64); str_append(str, " ("); imap_write_flags(str, mdata->flags & ENUM_NEGATE(MAIL_RECENT), NULL); if (mdata->keywords != NULL) imapc_append_keywords(str, mdata->keywords); str_append_c(str, ')'); flags = str_c(str); } if (mdata->received_date != (time_t)-1) { internaldate = t_strdup_printf(" \"%s\"", imap_to_datetime(mdata->received_date)); } ctx->mbox->exists_received = FALSE; input = i_stream_create_fd(ctx->fd, IO_BLOCK_SIZE); sctx.ctx = ctx; sctx.ret = -2; cmd = imapc_client_cmd(ctx->mbox->storage->client->client, imapc_save_callback, &sctx); imapc_command_sendf(cmd, "APPEND %s%1s%1s %p", imapc_mailbox_get_remote_name(ctx->mbox), flags, internaldate, input); i_stream_unref(&input); while (sctx.ret == -2) imapc_mailbox_run(ctx->mbox); if (sctx.ret == 0 && ctx->mbox->selected && !ctx->mbox->exists_received) { /* e.g. Courier doesn't send EXISTS reply before the tagged APPEND reply. That isn't exactly required by the IMAP RFC, but it makes the behavior better. See if NOOP finds the mail. */ sctx.ret = -2; cmd = imapc_client_cmd(ctx->mbox->storage->client->client, imapc_save_noop_callback, &sctx); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); imapc_command_send(cmd, "NOOP"); while (sctx.ret == -2) imapc_mailbox_run(ctx->mbox); } return sctx.ret; } int imapc_save_finish(struct mail_save_context *_ctx) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); struct mail_storage *storage = _ctx->transaction->box->storage; ctx->finished = TRUE; if (!ctx->failed) { if (o_stream_finish(_ctx->data.output) < 0) { if (!mail_storage_set_error_from_errno(storage)) { mail_set_critical(_ctx->dest_mail, "write(%s) failed: %s", ctx->temp_path, o_stream_get_error(_ctx->data.output)); } ctx->failed = TRUE; } } if (!ctx->failed) { if (imapc_save_append(ctx) < 0) ctx->failed = TRUE; } o_stream_unref(&_ctx->data.output); i_stream_unref(&ctx->input); i_close_fd_path(&ctx->fd, ctx->temp_path); i_free(ctx->temp_path); index_save_context_free(_ctx); return ctx->failed ? -1 : 0; } void imapc_save_cancel(struct mail_save_context *_ctx) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); ctx->failed = TRUE; (void)imapc_transaction_save_commit_pre(_ctx); (void)imapc_save_finish(_ctx); } static void imapc_copy_bulk_finish(struct imapc_save_context *ctx) { while (ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL) imapc_mailbox_run_nofetch(ctx->src_mbox); } int imapc_transaction_save_commit_pre(struct mail_save_context *_ctx) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); struct mail_transaction_commit_changes *changes = _ctx->transaction->changes; uint32_t i, last_seq; i_assert(ctx->finished || ctx->failed); /* expunge all added messages from index before commit */ last_seq = mail_index_view_get_messages_count(_ctx->transaction->view); if (last_seq == 0) return -1; for (i = 0; i < ctx->save_count; i++) mail_index_expunge(ctx->trans, last_seq - i); if (!ctx->failed && array_is_created(&ctx->dest_saved_uids)) { changes->uid_validity = ctx->dest_uid_validity; array_append_array(&changes->saved_uids, &ctx->dest_saved_uids); } return 0; } int imapc_transaction_save_commit(struct mailbox_transaction_context *t) { struct imapc_save_context *ctx = NULL; struct imapc_mailbox *src_mbox = NULL; if (t->save_ctx != NULL) { ctx = IMAPC_SAVECTX(t->save_ctx); src_mbox = ctx->src_mbox; } if (src_mbox != NULL && src_mbox->pending_copy_request != NULL) { /* If there is still a copy command to send flush it now */ imapc_mail_copy_bulk_flush(src_mbox); imapc_copy_bulk_finish(ctx); } if (ctx != NULL) return ctx->failed ? -1 : 0; return 0; } void imapc_transaction_save_commit_post(struct mail_save_context *_ctx, struct mail_index_transaction_commit_result *result ATTR_UNUSED) { imapc_transaction_save_rollback(_ctx); } static void imapc_expunge_construct_cmd_str(string_t *store_cmd, string_t *expunge_cmd, string_t *uids) { str_append(store_cmd, "UID STORE "); str_append_str(store_cmd, uids); str_append(store_cmd, " +FLAGS (\\Deleted)"); str_append(expunge_cmd, "UID EXPUNGE "); str_append_str(expunge_cmd, uids); /* Clear already appened uids */ str_truncate(uids, 0); } static void imapc_expunge_send_cmd_str(struct imapc_save_context *ctx, string_t *uids) { struct imapc_command *store_cmd, *expunge_cmd; string_t *store_cmd_str, *expunge_cmd_str; store_cmd_str = t_str_new(128); expunge_cmd_str = t_str_new(128); imapc_expunge_construct_cmd_str(store_cmd_str, expunge_cmd_str, uids); /* Make sure line length is less than 8k */ i_assert(str_len(store_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN); i_assert(str_len(expunge_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN); store_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box, imapc_copy_rollback_store_callback, ctx); expunge_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box, imapc_copy_rollback_expunge_callback, ctx); ctx->src_mbox->rollback_pending = TRUE; imapc_command_send(store_cmd, str_c(store_cmd_str)); imapc_command_send(expunge_cmd, str_c(expunge_cmd_str)); } static void imapc_rollback_send_expunge(struct imapc_save_context *ctx) { string_t *uids_str; struct seqset_builder *seqset_builder; struct seq_range_iter iter; unsigned int i = 0; uint32_t uid; if (!array_not_empty(&ctx->src_mbox->copy_rollback_expunge_uids)) return; uids_str = t_str_new(128); seqset_builder = seqset_builder_init(uids_str); seq_range_array_iter_init(&iter, &ctx->src_mbox->copy_rollback_expunge_uids); /* Iterate over all uids that must be rolled back */ while (seq_range_array_iter_nth(&iter, i++, &uid)) { /* Try to add the to the seqset builder while respecting the maximum length of IMAPC_SERVER_CMDLINE_MAX_LEN. */ if (!seqset_builder_try_add(seqset_builder, IMAPC_SERVER_CMDLINE_MAX_LEN - strlen("UID STORE +FLAGS (\\Deleted)"), uid)) { /* Maximum length is reached send the rollback and wait for it to be finished. */ imapc_expunge_send_cmd_str(ctx, uids_str); while (ctx->src_mbox->rollback_pending) imapc_mailbox_run_nofetch(ctx->src_mbox); /* Truncate the uids_str and create a new seqset_builder for the next command */ seqset_builder_deinit(&seqset_builder); str_truncate(uids_str, 0); seqset_builder = seqset_builder_init(uids_str); /* Make sure the current uid which is part of the next uid_str */ seqset_builder_add(seqset_builder, uid); } } if (str_len(uids_str) > 0) imapc_expunge_send_cmd_str(ctx, uids_str); while (ctx->src_mbox->rollback_pending) imapc_mailbox_run_nofetch(ctx->src_mbox); } static void imapc_copy_bulk_ctx_deinit(struct imapc_save_context *ctx) { /* Clean up the pending copy and the context attached to it */ str_truncate(ctx->src_mbox->pending_copy_cmd, 0); i_free(ctx->src_mbox->copy_dest_box); } void imapc_transaction_save_rollback(struct mail_save_context *_ctx) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); if ((ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL) || !ctx->finished) { /* There is still a pending copy which should not be send as rollback() is called or the transaction has not yet finished and rollback is called */ ctx->failed = TRUE; (void)imapc_transaction_save_commit_pre(_ctx); i_assert(ctx->finished || ctx->src_mbox != NULL); /* Clean up the pending copy and the context attached to it */ if (ctx->src_mbox != NULL) { if (ctx->src_mbox->pending_copy_request != NULL) { seqset_builder_deinit(&ctx->src_mbox->pending_copy_request->uidset_builder); i_free(ctx->src_mbox->pending_copy_request); } imapc_copy_bulk_ctx_deinit(ctx); imapc_client_stop(ctx->src_mbox->storage->client->client); } } /* Expunge all added messages from index */ if (ctx->failed && array_is_created(&ctx->dest_saved_uids)) { i_assert(ctx->src_mbox != NULL); seq_range_array_merge(&ctx->src_mbox->copy_rollback_expunge_uids, &ctx->dest_saved_uids); /* Make sure context is not finished already */ ctx->finished = FALSE; imapc_rollback_send_expunge(ctx); array_free(&ctx->dest_saved_uids); } if (ctx->finished || ctx->failed) { array_free(&ctx->dest_saved_uids); i_free(ctx); } } static bool imapc_save_copyuid(struct imapc_save_context *ctx, const struct imapc_command_reply *reply, uint32_t *uid_r) { ARRAY_TYPE(seq_range) dest_uidset, source_uidset; struct seq_range_iter iter; const char *const *args; uint32_t uid_validity; *uid_r = 0; /* */ args = t_strsplit(reply->resp_text_value, " "); if (str_array_length(args) != 3) return FALSE; if (str_to_uint32(args[0], &uid_validity) < 0) return FALSE; if (ctx->dest_uid_validity == 0) ctx->dest_uid_validity = uid_validity; else if (ctx->dest_uid_validity != uid_validity) return FALSE; t_array_init(&source_uidset, 8); t_array_init(&dest_uidset, 8); if (imap_seq_set_nostar_parse(args[1], &source_uidset) < 0) return FALSE; if (imap_seq_set_nostar_parse(args[2], &dest_uidset) < 0) return FALSE; if (!array_is_created(&ctx->dest_saved_uids)) i_array_init(&ctx->dest_saved_uids, 8); seq_range_array_merge(&ctx->dest_saved_uids, &dest_uidset); seq_range_array_iter_init(&iter, &dest_uidset); (void)seq_range_array_iter_nth(&iter, 0, uid_r); return TRUE; } static void imapc_copy_set_error(struct imapc_save_context *sctx, const struct imapc_command_reply *reply) { sctx->failed = TRUE; if (reply->state != IMAPC_COMMAND_STATE_BAD) imapc_copy_error_from_reply(sctx->mbox->storage, MAIL_ERROR_PARAMS, reply); else mailbox_set_critical(&sctx->mbox->box, "imapc: COPY failed: %s", reply->text_full); } static void imapc_copy_simple_callback(const struct imapc_command_reply *reply, void *context) { struct imapc_save_cmd_context *ctx = context; uint32_t uid = 0; if (reply->state == IMAPC_COMMAND_STATE_OK) { if (reply->resp_text_key != NULL && strcasecmp(reply->resp_text_key, "COPYUID") == 0) imapc_save_copyuid(ctx->ctx, reply, &uid); imapc_save_add_to_index(ctx->ctx, uid); ctx->ret = 0; } else if (reply->state == IMAPC_COMMAND_STATE_NO) { imapc_copy_error_from_reply(ctx->ctx->mbox->storage, MAIL_ERROR_PARAMS, reply); ctx->ret = -1; } else { mailbox_set_critical(&ctx->ctx->mbox->box, "imapc: COPY failed: %s", reply->text_full); ctx->ret = -1; } imapc_client_stop(ctx->ctx->mbox->storage->client->client); } static int imapc_copy_simple(struct mail_save_context *_ctx, struct mail *mail) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); struct mailbox_transaction_context *_t = _ctx->transaction; struct imapc_save_cmd_context sctx; struct imapc_command *cmd; sctx.ret = -2; sctx.ctx = ctx; cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box, imapc_copy_simple_callback, &sctx); imapc_command_sendf(cmd, "UID COPY %u %s", mail->uid, _t->box->name); while (sctx.ret == -2) imapc_mailbox_run(ctx->src_mbox); ctx->finished = TRUE; return sctx.ret; } static void imapc_copy_bulk_callback(const struct imapc_command_reply *reply, void *context) { struct imapc_copy_request *request = context; struct imapc_save_context *ctx = request->sctx; struct imapc_mailbox *mbox = ctx->src_mbox; unsigned int uid; i_assert(mbox != NULL); i_assert(request == mbox->pending_copy_request); /* Check the reply state and add uid's to index and dest_saved_uids. */ if (ctx->failed) { /* If the saving already failed try to find UIDs already copied from the reply so that rollback can expunge them */ if (null_strcasecmp(reply->resp_text_key, "COPYUID") == 0) { (void)imapc_save_copyuid(ctx, reply, &uid); imapc_transaction_save_rollback(&ctx->ctx); } } else if (reply->state == IMAPC_COMMAND_STATE_OK) { if (reply->resp_text_key != NULL && strcasecmp(reply->resp_text_key, "COPYUID") == 0 && imapc_save_copyuid(ctx, reply, &uid)) { ctx->finished = TRUE; } } else { imapc_copy_set_error(ctx, reply); } ctx->src_mbox->pending_copy_request = NULL; i_free(request); imapc_client_stop(mbox->storage->client->client); } static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox) { struct imapc_command *cmd; i_assert(mbox != NULL); i_assert(mbox->pending_copy_request != NULL); i_assert(mbox->client_box != NULL); cmd = imapc_client_mailbox_cmd(mbox->client_box, imapc_copy_bulk_callback, mbox->pending_copy_request); seqset_builder_deinit(&mbox->pending_copy_request->uidset_builder); str_append(mbox->pending_copy_cmd, " "); imap_append_astring(mbox->pending_copy_cmd, mbox->copy_dest_box); imapc_command_send(cmd, str_c(mbox->pending_copy_cmd)); imapc_copy_bulk_ctx_deinit(mbox->pending_copy_request->sctx); } static bool imapc_mail_copy_bulk_try_merge(struct imapc_mailbox *mbox, uint32_t uid, const char *box) { i_assert(str_begins(str_c(mbox->pending_copy_cmd), "UID COPY ")); if (strcmp(box, mbox->copy_dest_box) != 0) { /* Not the same mailbox merging not possible */ return FALSE; } return seqset_builder_try_add(mbox->pending_copy_request->uidset_builder, IMAPC_SERVER_CMDLINE_MAX_LEN, uid); } static void imapc_mail_copy_bulk_delayed_send_or_merge(struct imapc_save_context *ctx, uint32_t uid, const char *box) { struct imapc_mailbox *mbox = ctx->src_mbox; if (mbox->pending_copy_request != NULL && !imapc_mail_copy_bulk_try_merge(mbox, uid, box)) { /* send the previous COPY and create new one after waiting for this one to be finished. */ imapc_mail_copy_bulk_flush(mbox); imapc_copy_bulk_finish(mbox->pending_copy_request->sctx); } if (mbox->pending_copy_request == NULL) { mbox->pending_copy_request = i_new(struct imapc_copy_request, 1); str_printfa(mbox->pending_copy_cmd, "UID COPY "); mbox->pending_copy_request->uidset_builder = seqset_builder_init(mbox->pending_copy_cmd); seqset_builder_add(mbox->pending_copy_request->uidset_builder, uid); mbox->copy_dest_box = i_strdup(box); } else { i_assert(mbox->pending_copy_request->sctx == ctx); } mbox->pending_copy_request->sctx = ctx; } static int imapc_copy_bulk(struct imapc_save_context *ctx, struct mail *mail) { struct imapc_mailbox *mbox = IMAPC_MAILBOX(ctx->ctx.transaction->box); imapc_mail_copy_bulk_delayed_send_or_merge(ctx, mail->uid, imapc_mailbox_get_remote_name(mbox)); imapc_save_add_to_index(ctx, 0); return ctx->failed ? -1 : 0; } static bool imapc_is_mail_expunged(struct imapc_mailbox *mbox, uint32_t uid) { if (array_is_created(&mbox->delayed_expunged_uids) && seq_range_exists(&mbox->delayed_expunged_uids, uid)) return TRUE; if (mbox->delayed_sync_trans == NULL) return FALSE; struct mail_index_view *view = mail_index_transaction_get_view(mbox->delayed_sync_trans); uint32_t seq; return mail_index_lookup_seq(view, uid, &seq) && mail_index_transaction_is_expunged(mbox->delayed_sync_trans, seq); } int imapc_copy(struct mail_save_context *_ctx, struct mail *mail) { struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); struct mailbox_transaction_context *_t = _ctx->transaction; struct imapc_msgmap *src_msgmap; uint32_t rseq; int ret; i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0); if (_t->box->storage == mail->box->storage) { /* Currently we don't support copying mails from multiple different source mailboxes within the same transaction. */ i_assert(ctx->src_mbox == NULL || &ctx->src_mbox->box == mail->box); ctx->src_mbox = IMAPC_MAILBOX(mail->box); if (!mail->expunged && imapc_is_mail_expunged(ctx->mbox, mail->uid)) mail_set_expunged(mail); /* same server, we can use COPY for the mail */ src_msgmap = imapc_client_mailbox_get_msgmap(ctx->src_mbox->client_box); if (mail->expunged || !imapc_msgmap_uid_to_rseq(src_msgmap, mail->uid, &rseq)) { mail_storage_set_error(mail->box->storage, MAIL_ERROR_EXPUNGED, "Some of the requested messages no longer exist."); ctx->finished = TRUE; index_save_context_free(_ctx); return -1; } /* Mail has not been expunged and can be copied. */ if (ctx->mbox->capabilities == 0) { /* The destination mailbox has not yet been selected so the capabilities are unknown */ if (imapc_client_get_capabilities(ctx->mbox->storage->client->client, &ctx->mbox->capabilities) < 0) { mail_storage_set_error(mail->box->storage, MAIL_ERROR_UNAVAILABLE, "Failed to determine capabilities for mailbox."); ctx->finished = TRUE; index_save_context_free(_ctx); return -1; } } if ((ctx->mbox->capabilities & IMAPC_CAPABILITY_UIDPLUS) != 0) ret = imapc_copy_bulk(ctx, mail); else ret = imapc_copy_simple(_ctx, mail); index_save_context_free(_ctx); return ret; } return mail_storage_copy(_ctx, mail); }