cmd-append.c revision 57b38969882b61538660652b70e213f64bf20cb7
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "imap-common.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "ioloop.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "istream.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "istream-chain.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "ostream.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "str.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "mail-storage-private.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "imap-parser.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "imap-date.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "imap-util.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "imap-commands.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include "imap-msgpart-url.h"
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#include <sys/time.h>
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen/* Don't allow internaldates to be too far in the future. At least with Maildir
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen they can cause problems with incremental backups since internaldate is
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen stored in file's mtime. But perhaps there are also some other reasons why
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen it might not be wanted. */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen#define INTERNALDATE_MAX_FUTURE_SECS (2*3600)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstruct cmd_append_context {
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct client *client;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct client_command_context *cmd;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct mail_storage *storage;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct mailbox *box;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct mailbox_transaction_context *t;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen time_t started;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct istream_chain *catchain;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen uoff_t cat_msg_size;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct istream *input;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct istream *litinput;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen uoff_t literal_size;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct imap_parser *save_parser;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct mail_save_context *save_ctx;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen unsigned int count;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen unsigned int message_input:1;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen unsigned int catenate:1;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen unsigned int failed:1;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen};
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstatic void cmd_append_finish(struct cmd_append_context *ctx);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstatic bool cmd_append_continue_message(struct client_command_context *cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstatic bool cmd_append_continue_parsing(struct client_command_context *cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstatic const char *get_disconnect_reason(struct cmd_append_context *ctx)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen{
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen string_t *str = t_str_new(128);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen unsigned int secs = ioloop_time - ctx->started;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen str_printfa(str, "Disconnected in APPEND (%u msgs, %u secs",
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen ctx->count, secs);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen if (ctx->input != NULL) {
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes",
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen ctx->input->v_offset, ctx->literal_size);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen }
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen str_append_c(str, ')');
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen return str_c(str);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen}
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstatic void client_input_append(struct client_command_context *cmd)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen{
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct cmd_append_context *ctx = cmd->context;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen struct client *client = cmd->client;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen const char *reason;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen bool finished;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen i_assert(!client->destroyed);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client->last_input = ioloop_time;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen timeout_reset(client->to_idle);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen switch (i_stream_read(client->input)) {
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen case -1:
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen /* disconnected */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen reason = get_disconnect_reason(ctx);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen cmd_append_finish(cmd->context);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen /* Reset command so that client_destroy() doesn't try to call
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen cmd_append_continue_message() anymore. */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_command_free(&cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_destroy(client, reason);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen return;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen case -2:
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen if (ctx->message_input) {
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen /* message data, this is handled internally by
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen mailbox_save_continue() */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen break;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen }
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen cmd_append_finish(cmd->context);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen /* parameter word is longer than max. input buffer size.
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen this is most likely an error, so skip the new data
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen until newline is found. */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client->input_skip_line = TRUE;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_send_command_error(cmd, "Too long argument.");
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen cmd->param_error = TRUE;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_command_free(&cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen return;
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen }
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen o_stream_cork(client->output);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen finished = command_exec(cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen if (!finished && cmd->state != CLIENT_COMMAND_STATE_DONE)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen (void)client_handle_unfinished_cmd(cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen else
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_command_free(&cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen (void)cmd_sync_delayed(client);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen o_stream_uncork(client->output);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen if (client->disconnected)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_destroy(client, NULL);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen else
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_continue_pending_input(client);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen}
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainenstatic void cmd_append_finish(struct cmd_append_context *ctx)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen{
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen imap_parser_unref(&ctx->save_parser);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen i_assert(ctx->client->input_lock == ctx->cmd);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen io_remove(&ctx->client->io);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen /* we must put back the original flush callback before beginning to
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen sync (the command is still unfinished at that point) */
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen o_stream_set_flush_callback(ctx->client->output,
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen client_output, ctx->client);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen if (ctx->litinput != NULL)
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen i_stream_unref(&ctx->litinput);
55a7410569737197afb302b07b488973324b0cc5Timo Sirainen if (ctx->input != NULL)
i_stream_unref(&ctx->input);
if (ctx->save_ctx != NULL)
mailbox_save_cancel(&ctx->save_ctx);
if (ctx->t != NULL)
mailbox_transaction_rollback(&ctx->t);
if (ctx->box != ctx->cmd->client->mailbox && ctx->box != NULL)
mailbox_free(&ctx->box);
}
static bool cmd_append_continue_cancel(struct client_command_context *cmd)
{
struct cmd_append_context *ctx = cmd->context;
size_t size;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
(void)i_stream_read(ctx->input);
(void)i_stream_get_data(ctx->input, &size);
i_stream_skip(ctx->input, size);
if (cmd->client->input->closed) {
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->litinput->v_offset == ctx->literal_size) {
/* finished, but with MULTIAPPEND and LITERAL+ we may get
more messages. */
i_stream_unref(&ctx->litinput);
ctx->message_input = FALSE;
imap_parser_reset(ctx->save_parser);
cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(cmd);
}
return FALSE;
}
static bool cmd_append_cancel(struct cmd_append_context *ctx, bool nonsync)
{
ctx->failed = TRUE;
if (!nonsync) {
cmd_append_finish(ctx);
return TRUE;
}
/* we have to read the nonsynced literal so we don't treat the message
data as commands. */
ctx->input = i_stream_create_limit(ctx->client->input, ctx->literal_size);
ctx->message_input = TRUE;
ctx->cmd->func = cmd_append_continue_cancel;
ctx->cmd->context = ctx;
return cmd_append_continue_cancel(ctx->cmd);
}
static int
cmd_append_catenate_url(struct client_command_context *cmd, const char *caturl)
{
struct cmd_append_context *ctx = cmd->context;
struct imap_msgpart_url *mpurl;
struct istream *input;
uoff_t size, newsize;
const char *error;
int ret;
ret = imap_msgpart_url_parse(cmd->client->user, cmd->client->mailbox,
caturl, &mpurl, &error);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
return -1;
}
if (ret == 0) {
/* invalid url, abort */
client_send_tagline(cmd,
t_strdup_printf("NO [BADURL %s] %s.", caturl, error));
return -1;
}
/* catenate URL */
ret = imap_msgpart_url_read_part(mpurl, &input, &size, &error);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
return -1;
}
if (ret == 0) {
/* invalid url, abort */
client_send_tagline(cmd,
t_strdup_printf("NO [BADURL %s] %s.", caturl, error));
return -1;
}
if (size == 0) {
/* empty input */
imap_msgpart_url_free(&mpurl);
return 0;
}
newsize = ctx->cat_msg_size + size;
if (newsize < ctx->cat_msg_size) {
client_send_tagline(cmd,
"NO [TOOBIG] Composed message grows too big.");
imap_msgpart_url_free(&mpurl);
return -1;
}
ctx->cat_msg_size = newsize;
/* add this input stream to chain */
i_stream_chain_append(ctx->catchain, input);
input = NULL;
/* save by reading the chain stream */
while (!i_stream_is_eof(ctx->input)) {
ret = i_stream_read(ctx->input);
i_assert(ret != 0); /* we can handle only blocking input here */
if (mailbox_save_continue(ctx->save_ctx) < 0 || ret == -1)
break;
}
if (ctx->input->stream_errno != 0) {
errno = ctx->input->stream_errno;
mail_storage_set_critical(ctx->box->storage,
"read(%s) failed: %m (for CATENATE URL %s)",
i_stream_get_name(input), caturl);
client_send_storage_error(cmd, ctx->storage);
ret = -1;
} else if (!input->eof) {
/* save failed */
client_send_storage_error(cmd, ctx->storage);
ret = -1;
} else {
ret = 0;
}
imap_msgpart_url_free(&mpurl);
return 0;
}
static int cmd_append_catenate_text(struct client_command_context *cmd)
{
struct cmd_append_context *ctx = cmd->context;
uoff_t newsize;
newsize = ctx->cat_msg_size + ctx->literal_size;
if (newsize < ctx->cat_msg_size) {
client_send_tagline(cmd,
"NO [TOOBIG] Composed message grows too big.");
return -1;
}
/* save the mail */
ctx->cat_msg_size = newsize;
ctx->litinput = i_stream_create_limit(cmd->client->input,
ctx->literal_size);
i_stream_chain_append(ctx->catchain, ctx->litinput);
return 0;
}
static int
cmd_append_catenate(struct client_command_context *cmd,
const struct imap_arg *args, bool *nonsync_r)
{
struct cmd_append_context *ctx = cmd->context;
const char *catpart;
*nonsync_r = FALSE;
if (ctx->failed)
return -1;
/* Handle URLs until a TEXT literal is encountered */
while (imap_arg_get_atom(args, &catpart)) {
const char *caturl;
if (strcasecmp(catpart, "URL") == 0 ) {
/* URL <url> */
args++;
if (!imap_arg_get_astring(args, &caturl))
break;
if (cmd_append_catenate_url(cmd, caturl) < 0)
return -1;
} else if (strcasecmp(catpart, "TEXT") == 0) {
/* TEXT <literal> */
args++;
if (!imap_arg_get_literal_size(args, &ctx->literal_size))
break;
*nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
return cmd_append_catenate_text(cmd) < 0 ? -1 : 1;
} else {
break;
}
args++;
}
if (IMAP_ARG_IS_EOL(args)) {
/* ")" */
return 0;
}
client_send_command_error(cmd, "Invalid arguments.");
return -1;
}
static void cmd_append_finish_catenate(struct client_command_context *cmd)
{
struct cmd_append_context *ctx = cmd->context;
i_stream_chain_append(ctx->catchain, NULL);
i_stream_unref(&ctx->input);
ctx->catenate = FALSE;
if (mailbox_save_finish(&ctx->save_ctx) < 0) {
ctx->failed = TRUE;
client_send_storage_error(cmd, ctx->storage);
}
}
static bool cmd_append_continue_catenate(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *args;
const char *msg;
bool fatal, nonsync = FALSE;
int ret;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
ret = imap_parser_read_args(ctx->save_parser, 0,
IMAP_PARSE_FLAG_LITERAL_SIZE |
IMAP_PARSE_FLAG_INSIDE_LIST, &args);
if (ret == -1) {
if (!ctx->failed) {
msg = imap_parser_get_error(ctx->save_parser, &fatal);
if (fatal)
client_disconnect_with_error(client, msg);
else
client_send_command_error(cmd, msg);
}
client->input_skip_line = TRUE;
cmd_append_finish(ctx);
return TRUE;
}
if (ret < 0) {
/* need more data */
return FALSE;
}
if ((ret = cmd_append_catenate(cmd, args, &nonsync)) < 0) {
client->input_skip_line = TRUE;
return cmd_append_cancel(ctx, nonsync);
}
if (ret == 0) {
/* ")" */
cmd_append_finish_catenate(cmd);
/* last catenate part */
imap_parser_reset(ctx->save_parser);
cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(cmd);
}
/* TEXT <literal> */
/* after literal comes CRLF, if we fail make sure we eat it away */
client->input_skip_line = TRUE;
if (!nonsync) {
o_stream_send(client->output, "+ OK\r\n", 6);
o_stream_flush(client->output);
o_stream_uncork(client->output);
o_stream_cork(client->output);
}
ctx->message_input = TRUE;
cmd->func = cmd_append_continue_message;
return cmd_append_continue_message(cmd);
}
static int
cmd_append_handle_args(struct client_command_context *cmd,
const struct imap_arg **args, bool *nonsync_r)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *flags_list;
const struct imap_arg *cat_list = NULL;
enum mail_flags flags;
const char *const *keywords_list;
struct mail_keywords *keywords;
const char *internal_date_str;
time_t internal_date;
int ret, timezone_offset;
bool valid;
/* [<flags>] */
if (!imap_arg_get_list(*args, &flags_list))
flags_list = NULL;
else
(*args)++;
/* [<internal date>] */
if ((*args)->type != IMAP_ARG_STRING)
internal_date_str = NULL;
else {
internal_date_str = imap_arg_as_astring(*args);
(*args)++;
}
valid = FALSE;
*nonsync_r = FALSE;
ctx->catenate = FALSE;
if (imap_arg_atom_equals(*args, "CATENATE")) {
(*args)++;
if (imap_arg_get_list(*args, &cat_list)) {
valid = TRUE;
ctx->catenate = TRUE;
}
} else if (imap_arg_get_literal_size(*args, &ctx->literal_size)) {
*nonsync_r = (*args)->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
valid = TRUE;
}
if (!valid) {
client->input_skip_line = TRUE;
client_send_command_error(cmd, "Invalid arguments.");
return -1;
}
if (ctx->failed) {
/* we failed earlier, make sure we just eat nonsync-literal
if it's given. */
return -1;
}
if (flags_list != NULL) {
if (!client_parse_mail_flags(cmd, flags_list,
&flags, &keywords_list))
return -1;
if (keywords_list == NULL)
keywords = NULL;
else if (mailbox_keywords_create(ctx->box, keywords_list,
&keywords) < 0) {
client_send_storage_error(cmd, ctx->storage);
return -1;
}
} else {
flags = 0;
keywords = NULL;
}
if (internal_date_str == NULL) {
/* no time given, default to now. */
internal_date = (time_t)-1;
timezone_offset = 0;
} else if (!imap_parse_datetime(internal_date_str,
&internal_date, &timezone_offset)) {
client_send_tagline(cmd, "BAD Invalid internal date.");
return -1;
}
if (internal_date != (time_t)-1 &&
internal_date > ioloop_time + INTERNALDATE_MAX_FUTURE_SECS) {
/* the client specified a time in the future, set it to now. */
internal_date = (time_t)-1;
timezone_offset = 0;
}
if (cat_list != NULL) {
ctx->cat_msg_size = 0;
ctx->input = i_stream_create_chain(&ctx->catchain);
} else {
if (ctx->literal_size == 0) {
/* no message data, abort */
client_send_tagline(cmd, "NO Can't save a zero byte message.");
return -1;
}
ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size);
ctx->input = ctx->litinput;
i_stream_ref(ctx->input);
}
/* save the mail */
ctx->save_ctx = mailbox_save_alloc(ctx->t);
mailbox_save_set_flags(ctx->save_ctx, flags, keywords);
if (keywords != NULL)
mailbox_keywords_unref(&keywords);
mailbox_save_set_received_date(ctx->save_ctx,
internal_date, timezone_offset);
if (mailbox_save_begin(&ctx->save_ctx, ctx->input) < 0) {
/* save initialization failed */
client_send_storage_error(cmd, ctx->storage);
return -1;
}
ctx->count++;
if (cat_list == NULL) {
/* normal APPEND */
return 1;
} else if ((ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) < 0) {
client->input_skip_line = TRUE;
return -1;
} else if (ret == 0) {
/* CATENATE consisted only of URLs */
return 0;
} else {
/* TEXT part found from CATENATE */
return 1;
}
}
static bool cmd_append_finish_parsing(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
enum mailbox_sync_flags sync_flags;
enum imap_sync_flags imap_flags;
struct mail_transaction_commit_changes changes;
unsigned int save_count;
string_t *msg;
int ret;
/* eat away the trailing CRLF */
client->input_skip_line = TRUE;
if (ctx->failed) {
/* we failed earlier, error message is sent */
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->count == 0) {
client_send_tagline(cmd, "BAD Missing message size.");
cmd_append_finish(ctx);
return TRUE;
}
ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
cmd_append_finish(ctx);
return TRUE;
}
msg = t_str_new(256);
save_count = seq_range_count(&changes.saved_uids);
if (save_count == 0) {
/* not supported by backend (virtual) */
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.");
}
pool_unref(&changes.pool);
if (ctx->box == cmd->client->mailbox) {
sync_flags = 0;
imap_flags = IMAP_SYNC_FLAG_SAFE;
} else {
sync_flags = MAILBOX_SYNC_FLAG_FAST;
imap_flags = 0;
}
cmd_append_finish(ctx);
return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
}
static bool cmd_append_continue_parsing(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *args;
const char *msg;
bool fatal, nonsync;
int ret;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
/* if error occurs, the CRLF is already read. */
client->input_skip_line = FALSE;
/* [<flags>] [<internal date>] <message literal> */
ret = imap_parser_read_args(ctx->save_parser, 0,
IMAP_PARSE_FLAG_LITERAL_SIZE, &args);
if (ret == -1) {
if (!ctx->failed) {
msg = imap_parser_get_error(ctx->save_parser, &fatal);
if (fatal)
client_disconnect_with_error(client, msg);
else
client_send_command_error(cmd, msg);
}
cmd_append_finish(ctx);
return TRUE;
}
if (ret < 0) {
/* need more data */
return FALSE;
}
if (IMAP_ARG_IS_EOL(args)) {
/* last message */
return cmd_append_finish_parsing(cmd);
}
/* Handle MULTIAPPEND messages while CATENATE contains only URLs */
while ((ret = cmd_append_handle_args(cmd, &args, &nonsync)) == 0) {
cmd_append_finish_catenate(cmd);
/* Check for EOL, e.g.:
APPEND <box> ( URL <url> URL <url> URL <url> ) */
args++;
if (IMAP_ARG_IS_EOL(args)) {
/* last message */
return cmd_append_finish_parsing(cmd);
}
}
if (ret < 0)
return cmd_append_cancel(ctx, nonsync);
if (!ctx->catenate) {
/* after literal comes CRLF, if we fail make sure
we eat it away */
client->input_skip_line = TRUE;
if (!nonsync) {
o_stream_send(client->output, "+ OK\r\n", 6);
o_stream_flush(client->output);
o_stream_uncork(client->output);
o_stream_cork(client->output);
}
ctx->message_input = TRUE;
}
cmd->func = cmd_append_continue_message;
return cmd_append_continue_message(cmd);
}
static bool cmd_append_continue_message(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
size_t size;
int ret = 0;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->save_ctx != NULL) {
while (ctx->litinput->v_offset != ctx->literal_size) {
ret = i_stream_read(ctx->input);
if (mailbox_save_continue(ctx->save_ctx) < 0) {
/* we still have to finish reading the message
from client */
mailbox_save_cancel(&ctx->save_ctx);
break;
}
if (ret == -1 || ret == 0)
break;
}
}
if (ctx->save_ctx == NULL) {
(void)i_stream_read(ctx->input);
(void)i_stream_get_data(ctx->input, &size);
i_stream_skip(ctx->input, size);
}
if (ctx->litinput->eof || client->input->closed) {
bool all_written = ctx->litinput->v_offset == ctx->literal_size;
/* finished */
i_stream_unref(&ctx->litinput);
if (ctx->save_ctx == NULL) {
/* failed above */
client_send_storage_error(cmd, ctx->storage);
ctx->failed = TRUE;
} else if (!all_written) {
/* client disconnected before it finished sending the
whole message. */
ctx->failed = TRUE;
mailbox_save_cancel(&ctx->save_ctx);
client_disconnect(client, "EOF while appending");
} else if (ctx->catenate) {
/* CATENATE isn't finished yet */
} else if (mailbox_save_finish(&ctx->save_ctx) < 0) {
ctx->failed = TRUE;
client_send_storage_error(cmd, ctx->storage);
}
if (client->input->closed) {
cmd_append_finish(ctx);
return TRUE;
}
/* prepare for next message (part) */
ctx->message_input = FALSE;
imap_parser_reset(ctx->save_parser);
if (ctx->catenate) {
cmd->func = cmd_append_continue_catenate;
return cmd_append_continue_catenate(cmd);
}
i_stream_unref(&ctx->input);
cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(cmd);
}
return FALSE;
}
bool cmd_append(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx;
const char *mailbox;
if (client->syncing) {
/* if transaction is created while its view is synced,
appends aren't allowed for it. */
cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
return FALSE;
}
/* <mailbox> */
if (!client_read_string_args(cmd, 1, &mailbox))
return FALSE;
/* 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->started = ioloop_time;
if (client_open_save_dest_box(cmd, mailbox, &ctx->box) < 0)
ctx->failed = TRUE;
else {
ctx->storage = mailbox_get_storage(ctx->box);
ctx->t = mailbox_transaction_begin(ctx->box,
MAILBOX_TRANSACTION_FLAG_EXTERNAL |
MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
}
io_remove(&client->io);
client->io = io_add(i_stream_get_fd(client->input), IO_READ,
client_input_append, cmd);
/* append is special because we're only waiting on client input, not
client output, so disable the standard output handler until we're
finished */
o_stream_unset_flush_callback(client->output);
ctx->save_parser = imap_parser_create(client->input, client->output,
client->set->imap_max_line_length);
cmd->func = cmd_append_continue_parsing;
cmd->context = ctx;
return cmd_append_continue_parsing(cmd);
}