#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"
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;
struct mail_save_context *save_ctx;
unsigned int count;
+ bool replace:1;
bool message_input:1;
bool binary_input:1;
bool catenate:1;
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);
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)
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;
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;
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);
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);
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,
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;
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
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);
+}