]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imap: Implement support for the REPLACE capability
authorStephan Bosch <stephan.bosch@dovecot.fi>
Tue, 13 Nov 2018 21:25:07 +0000 (22:25 +0100)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 26 Feb 2025 10:45:00 +0000 (10:45 +0000)
src/imap/cmd-append.c
src/imap/imap-commands.c
src/imap/imap-commands.h
src/imap/imap-settings.c

index 30ac9a67b62ff7fc43022b8f1c5b47688732d771..a72f16fa0dddb2098772df73ca67d2188e678514 100644 (file)
@@ -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;
        }
 
-       /* <mailbox> */
-       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;
+
+               /* <seq-number> */
+               if (!imap_arg_get_atom(args, &seq) ||
+                    str_to_uint32(seq, &seqnum) < 0) {
+                       client_send_command_error(cmd, "Invalid arguments.");
+                       return TRUE;
+               }
+
+               args++;
+       }
+
+       /* <mailbox> */
+       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);
+}
index 2dd05e2477c0394f9db8a450b956857df2a5aa76..672ef1298d092034116bb1fb089afbf3ed170fc9 100644 (file)
@@ -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)
 
index b305ed0fdb62a6326c946a471cfd685c4b782a8e..d1a20c1141a6043d93ba841e08c217c44a5bbed5 100644 (file)
@@ -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);
index b4b24b5a0db1fdf7450c87e1a4ae8091c3c0fbda..a861e59f7f0a832b76e1460817997f6b8134e611 100644 (file)
@@ -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" },