From: Stephan Bosch Date: Tue, 13 Nov 2018 21:25:07 +0000 (+0100) Subject: imap: Implement support for the REPLACE capability X-Git-Tag: 2.4.1~126 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5baeb0c44513da6489cced076237839109c0e17a;p=thirdparty%2Fdovecot%2Fcore.git imap: Implement support for the REPLACE capability --- diff --git a/src/imap/cmd-append.c b/src/imap/cmd-append.c index 30ac9a67b6..a72f16fa0d 100644 --- a/src/imap/cmd-append.c +++ b/src/imap/cmd-append.c @@ -6,6 +6,8 @@ #include "istream-chain.h" #include "ostream.h" #include "str.h" +#include "strnum.h" +#include "time-util.h" #include "imap-resp-code.h" #include "istream-binary-converter.h" #include "mail-storage-private.h" @@ -30,6 +32,9 @@ struct cmd_append_context { struct mailbox_transaction_context *t; time_t started; + struct mailbox_transaction_context *rep_trans; + struct mail *rep_mail; + struct istream_chain *catchain; uoff_t cat_msg_size; @@ -41,6 +46,7 @@ struct cmd_append_context { struct mail_save_context *save_ctx; unsigned int count; + bool replace:1; bool message_input:1; bool binary_input:1; bool catenate:1; @@ -58,9 +64,9 @@ get_disconnect_reason(struct cmd_append_context *ctx, uoff_t lit_offset) string_t *str = t_str_new(128); unsigned int secs = ioloop_time - ctx->started; - str_printfa(str, "%s (While APPENDing: %u msgs, %u secs", + str_printfa(str, "%s (While running %s command: %u msgs, %u secs", i_stream_get_disconnect_reason(ctx->input), - ctx->count, secs); + (ctx->replace ? "REPLACE" : "APPEND"), ctx->count, secs); if (ctx->literal_size > 0) { str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes", lit_offset, ctx->literal_size); @@ -141,6 +147,11 @@ static void cmd_append_finish(struct cmd_append_context *ctx) o_stream_set_flush_callback(ctx->client->output, client_output, ctx->client); + if (ctx->rep_trans != NULL) { + mail_free(&ctx->rep_mail); + mailbox_transaction_rollback(&ctx->rep_trans); + } + i_stream_unref(&ctx->litinput); i_stream_unref(&ctx->input); if (ctx->save_ctx != NULL) @@ -611,7 +622,14 @@ cmd_append_handle_args(struct client_command_context *cmd, mailbox_save_set_flags(ctx->save_ctx, flags, keywords); mailbox_save_set_received_date(ctx->save_ctx, internal_date, timezone_offset); - if (mailbox_save_begin(&ctx->save_ctx, ctx->input) < 0) { + if (!ctx->replace) + ret = mailbox_save_begin(&ctx->save_ctx, ctx->input); + else { + ret = mailbox_save_begin_replace(&ctx->save_ctx, + ctx->input, + ctx->rep_mail); + } + if (ret < 0) { /* save initialization failed */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; @@ -645,9 +663,11 @@ cmd_append_handle_args(struct client_command_context *cmd, static bool cmd_append_finish_parsing(struct client_command_context *cmd) { struct cmd_append_context *ctx = cmd->context; + struct client *client = cmd->client; enum mailbox_sync_flags sync_flags; enum imap_sync_flags imap_flags; struct mail_transaction_commit_changes changes; + const char *msg_suffix = ""; unsigned int save_count; string_t *msg; int ret; @@ -675,16 +695,49 @@ static bool cmd_append_finish_parsing(struct client_command_context *cmd) msg = t_str_new(256); save_count = seq_range_count(&changes.saved_uids); - if (save_count == 0 || changes.no_read_perm) { - /* not supported by backend (virtual) */ - str_append(msg, "OK Append completed."); + + if (ctx->replace) { + /* Send APPENDUID response code if possible */ + if (save_count > 0 && !changes.no_read_perm) { + i_assert(ctx->count == save_count); + str_printfa(msg, "* OK [APPENDUID %u ", + changes.uid_validity); + imap_write_seq_range(msg, &changes.saved_uids); + str_append(msg, "] Replacement message saved."); + client_send_line(client, str_c(msg)); + str_truncate(msg, 0); + } + + /* Commit removal of the replaced message */ + i_assert(ctx->rep_trans != NULL); + mail_free(&ctx->rep_mail); + if (mailbox_transaction_commit(&ctx->rep_trans) < 0) { + client_send_line(client, t_strflocaltime( + "* NO "MAIL_ERRSTR_CRITICAL_MSG_STAMP, + ioloop_time)); + e_error(client->event, "REPLACE: " + "Failed to expunge the old message: %s", + mailbox_get_last_error(client->mailbox, NULL)); + msg_suffix = ", but failed to expunge old message"; + } + } + + if (ctx->replace || save_count == 0 || changes.no_read_perm) { + /* This is a REPLACE command or APPENDUID is not supported by + backend (virtual) */ + if (ctx->replace) + str_append(msg, "OK Replace completed"); + else + str_append(msg, "OK Append completed"); } else { i_assert(ctx->count == save_count); str_printfa(msg, "OK [APPENDUID %u ", changes.uid_validity); imap_write_seq_range(msg, &changes.saved_uids); - str_append(msg, "] Append completed."); + str_append(msg, "] Append completed"); } + str_append(msg, msg_suffix); + str_append_c(msg, '.'); ctx->client->append_count += save_count; pool_unref(&changes.pool); @@ -791,6 +844,14 @@ static bool cmd_append_parse_new_msg(struct client_command_context *cmd) return FALSE; } + if (ctx->replace && ctx->count > 0 && !IMAP_ARG_IS_EOL(args)) { + /* Only one message allowed for REPLACE */ + if (!ctx->failed) + client_send_command_error(cmd, "Invalid arguments."); + cmd_append_finish(ctx); + return TRUE; + } + if (IMAP_ARG_IS_EOL(args)) { /* last message */ return cmd_append_finish_parsing(cmd); @@ -911,11 +972,13 @@ static bool cmd_append_continue_message(struct client_command_context *cmd) return FALSE; } -bool cmd_append(struct client_command_context *cmd) +static bool cmd_append_full(struct client_command_context *cmd, bool replace) { struct client *client = cmd->client; + const struct imap_arg *args; struct cmd_append_context *ctx; const char *mailbox; + uint32_t seqnum = 0; if (client->syncing) { /* if transaction is created while its view is synced, @@ -924,16 +987,46 @@ bool cmd_append(struct client_command_context *cmd) return FALSE; } - /* */ - if (!client_read_string_args(cmd, 1, &mailbox)) + if (!client_read_args(cmd, (replace ? 2 : 1), 0, &args)) return FALSE; + if (replace) { + if (!client_verify_open_mailbox(cmd)) + return TRUE; + + const char *seq; + + /* */ + if (!imap_arg_get_atom(args, &seq) || + str_to_uint32(seq, &seqnum) < 0) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + args++; + } + + /* */ + if (!imap_arg_get_astring(args, &mailbox)) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + if (replace) { + if (!cmd->uid && (seqnum > client->messages_count)) { + client_send_command_error( + cmd, "Invalid message sequence."); + return TRUE; + } + } + /* we keep the input locked all the time */ client->input_lock = cmd; ctx = p_new(cmd->pool, struct cmd_append_context, 1); ctx->cmd = cmd; ctx->client = client; + ctx->replace = replace; ctx->started = ioloop_time; if (client_open_save_dest_box(cmd, mailbox, &ctx->box) < 0) ctx->failed = TRUE; @@ -946,6 +1039,22 @@ bool cmd_append(struct client_command_context *cmd) imap_client_command_get_reason(cmd)); } + if (replace) { + ctx->rep_trans = mailbox_transaction_begin( + client->mailbox, 0, + imap_client_command_get_reason(cmd)); + ctx->rep_mail = mail_alloc(ctx->rep_trans, + MAIL_FETCH_PHYSICAL_SIZE | + MAIL_FETCH_VIRTUAL_SIZE, NULL); + if (!cmd->uid) { + mail_set_seq(ctx->rep_mail, seqnum); + } else if (!mail_set_uid(ctx->rep_mail, seqnum)) { + client_send_tagline(cmd, + "NO Invalid UID for replaced message."); + ctx->failed = TRUE; + } + } + io_remove(&client->io); client->io = io_add_istream(client->input, client_input_append, cmd); /* append is special because we're only waiting on client input, not @@ -962,3 +1071,13 @@ bool cmd_append(struct client_command_context *cmd) cmd->context = ctx; return cmd_append_parse_new_msg(cmd); } + +bool cmd_append(struct client_command_context *cmd) +{ + return cmd_append_full(cmd, FALSE); +} + +bool cmd_replace(struct client_command_context *cmd) +{ + return cmd_append_full(cmd, TRUE); +} diff --git a/src/imap/imap-commands.c b/src/imap/imap-commands.c index 2dd05e2477..672ef1298d 100644 --- a/src/imap/imap-commands.c +++ b/src/imap/imap-commands.c @@ -80,7 +80,16 @@ static const struct command imap_ext_commands[] = { /* IMAP URLAUTH (RFC4467): */ { "GENURLAUTH", cmd_genurlauth, 0 }, { "RESETKEY", cmd_resetkey, 0 }, - { "URLFETCH", cmd_urlfetch, 0 } + { "URLFETCH", cmd_urlfetch, 0 }, + /* IMAP REPLACE (RFC8508): */ + { "REPLACE", cmd_replace, COMMAND_FLAG_USES_SEQS | + COMMAND_FLAG_BREAKS_SEQS | + /* finish syncing and sending + all tagged commands before + we wait for REPLACE input */ + COMMAND_FLAG_BREAKS_MAILBOX }, + { "UID REPLACE", cmd_replace, COMMAND_FLAG_BREAKS_SEQS | + COMMAND_FLAG_BREAKS_MAILBOX }, }; #define IMAP_EXT_COMMANDS_COUNT N_ELEMENTS(imap_ext_commands) diff --git a/src/imap/imap-commands.h b/src/imap/imap-commands.h index b305ed0fdb..d1a20c1141 100644 --- a/src/imap/imap-commands.h +++ b/src/imap/imap-commands.h @@ -123,6 +123,9 @@ bool cmd_genurlauth(struct client_command_context *cmd); bool cmd_resetkey(struct client_command_context *cmd); bool cmd_urlfetch(struct client_command_context *cmd); +/* IMAP REPLACE (RFC8508): */ +bool cmd_replace(struct client_command_context *cmd); + /* private: */ bool cmd_list_full(struct client_command_context *cmd, bool lsub); bool cmd_select_full(struct client_command_context *cmd, bool readonly); diff --git a/src/imap/imap-settings.c b/src/imap/imap-settings.c index b4b24b5a0d..a861e59f7f 100644 --- a/src/imap/imap-settings.c +++ b/src/imap/imap-settings.c @@ -142,6 +142,7 @@ static const struct setting_keyvalue imap_default_settings_keyvalue[] = { { "service/imap/imap_capability/LIST-STATUS", "yes" }, { "service/imap/imap_capability/BINARY", "yes" }, { "service/imap/imap_capability/MOVE", "yes" }, + { "service/imap/imap_capability/REPLACE", "yes" }, { "service/imap/imap_capability/SNIPPET=FUZZY", "yes" }, { "service/imap/imap_capability/PREVIEW=FUZZY", "yes" }, { "service/imap/imap_capability/PREVIEW", "yes" },