cmd-append.c revision 2f3d26e180a669116e1d456bd6dfdf5d0d3b0a40
b4c47b77878eb0eaaeb1e377de936a77fad4d144Aki Tuomi/* Copyright (C) 2002 Timo Sirainen */
b4c47b77878eb0eaaeb1e377de936a77fad4d144Aki Tuomi
b4c47b77878eb0eaaeb1e377de936a77fad4d144Aki Tuomi#include "common.h"
b4c47b77878eb0eaaeb1e377de936a77fad4d144Aki Tuomi#include "ioloop.h"
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen#include "istream.h"
24fed8aca238e6878aa9c85c82e83a0a7ee3ced3Timo Sirainen#include "ostream.h"
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen#include "str.h"
32f02789693d38b5470f0406bda0cbdf6fc1560eTimo Sirainen#include "commands.h"
d3b29d4b61f1549244a7509b798be6f806cf7d4eTimo Sirainen#include "imap-parser.h"
32f02789693d38b5470f0406bda0cbdf6fc1560eTimo Sirainen#include "imap-date.h"
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen#include "mail-storage.h"
285bfe946c2d54928b272270dd5eef9041b24271Timo Sirainen
316cbe323513a0f20d1cf519fe9405e231d633e2Aki Tuomi#include <sys/time.h>
17fc2a887a5683b2e1bbd6bd9fdf0cdb97b509fbTimo Sirainen
32f02789693d38b5470f0406bda0cbdf6fc1560eTimo Sirainenstruct cmd_append_context {
78919bf7cb55e84e5f289f33526579f63c4797d7Timo Sirainen struct client *client;
379175cfba8150d481d9898b78330b719d128d84Timo Sirainen struct client_command_context *cmd;
32f02789693d38b5470f0406bda0cbdf6fc1560eTimo Sirainen struct mail_storage *storage;
c33d3f93abf8392fdc60e12bea41ffd12cc85a8dTimo Sirainen struct mailbox *box;
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen struct mailbox_transaction_context *t;
792813592c69ddc0389b6f071d8120a7706b914dStephan Bosch
a4692ca37f2f670b99e8692e92064045322c42b3Stephan Bosch struct istream *input;
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen uoff_t msg_size;
4036c1ca99d2c517f68a5b67a419ae7fdfd45300Aki Tuomi
60c302a89ea286f0f2afff37c6e2d6649841d85fAki Tuomi struct imap_parser *save_parser;
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen struct mail_save_context *save_ctx;
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen unsigned int message_input:1;
ed77b0bf3a094fff279804e4f50ec0799680342fTimo Sirainen};
8b7e479f8d3d43eb8b98932a562dd5330b0d02bdTimo Sirainen
57614d9fe04311072508a2185472bdb977631c5dAki Tuomistatic void cmd_append_finish(struct cmd_append_context *ctx);
c865b0e9c65fd77f7b2ab6f8616d3def5501ecb3Timo Sirainenstatic bool cmd_append_continue_message(struct client_command_context *cmd);
5035404202587543bb4843d4aec46331651b6e16Timo Sirainen
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic void client_input(struct client_command_context *cmd)
f052a448696b1246ee9fc36b7c727237aed56058Timo Sirainen{
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen struct cmd_append_context *ctx = cmd->context;
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen struct client *client = cmd->client;
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen i_assert(!client->destroyed);
8e0e5842b5841cf3065ed18e8ba093b11517c51dTimo Sirainen
b691d7bad5acc2043cd7f598352f4c9a7f8aebb6Timo Sirainen client->last_input = ioloop_time;
6649a54aca033fcbbbb73e5999b95b828bd6b54cTimo Sirainen
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen switch (i_stream_read(client->input)) {
a34bb3d3ad889eb05e3f1c0a446692e1f3527e14Timo Sirainen case -1:
17fc2a887a5683b2e1bbd6bd9fdf0cdb97b509fbTimo Sirainen /* disconnected */
d9e404180ff26dbbaea68534a5f176765022b76bTimo Sirainen cmd_append_finish(cmd->context);
83942ac160cdfb922c3a2f29ddfae2a13ebf8b5dTimo Sirainen /* Reset command so that client_destroy() doesn't try to call
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen cmd_append_continue_message() anymore. */
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen client_command_free(cmd);
5ef28f68edef46f69961b19b7c1dcd8ec5a955e8Timo Sirainen client_destroy(client, "Disconnected in APPEND");
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen return;
a7c6667c80d7363f5110ec1ab1ae9198833411d3Timo Sirainen case -2:
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch cmd_append_finish(cmd->context);
52cb31b413be19de11cdf9ad84b9ccde7740b5cfTimo Sirainen if (ctx->message_input) {
52cb31b413be19de11cdf9ad84b9ccde7740b5cfTimo Sirainen /* message data, this is handled internally by
58c61ac5650583d21c891e61e051c614290d31fbTimo Sirainen mailbox_save_continue() */
315ce5be539bfe8bc7777ab0654499c49583cea2Timo Sirainen break;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen }
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen /* parameter word is longer than max. input buffer size.
447e086422f1ab7cc16833583ed70a4af7a84bc5Timo Sirainen this is most likely an error, so skip the new data
1795e934ebcd58175d3b5bbdd811b13c7889efa3Timo Sirainen until newline is found. */
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen client->input_skip_line = TRUE;
ede6bdb42c76bf84add9071b9fe14586646aeaf7Timo Sirainen
1795e934ebcd58175d3b5bbdd811b13c7889efa3Timo Sirainen client_send_command_error(cmd, "Too long argument.");
cmd->param_error = TRUE;
client_command_free(cmd);
return;
}
if (cmd->func(cmd)) {
client_command_free(cmd);
client_continue_pending_input(client);
}
}
/* Returns -1 = error, 0 = need more data, 1 = successful. flags and
internal_date may be NULL as a result, but mailbox and msg_size are always
set when successful. */
static int validate_args(struct imap_arg *args, struct imap_arg_list **flags,
const char **internal_date, uoff_t *msg_size,
bool *nonsync)
{
/* [<flags>] */
if (args->type != IMAP_ARG_LIST)
*flags = NULL;
else {
*flags = IMAP_ARG_LIST(args);
args++;
}
/* [<internal date>] */
if (args->type != IMAP_ARG_STRING)
*internal_date = NULL;
else {
*internal_date = IMAP_ARG_STR(args);
args++;
}
if (args->type != IMAP_ARG_LITERAL_SIZE &&
args->type != IMAP_ARG_LITERAL_SIZE_NONSYNC) {
*nonsync = FALSE;
return FALSE;
}
*nonsync = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
*msg_size = IMAP_ARG_LITERAL_SIZE(args);
return TRUE;
}
static void cmd_append_finish(struct cmd_append_context *ctx)
{
imap_parser_destroy(&ctx->save_parser);
i_assert(ctx->client->input_lock == ctx->cmd);
io_remove(&ctx->client->io);
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_close(&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 (ctx->input->v_offset == ctx->msg_size ||
cmd->client->input->closed) {
cmd_append_finish(ctx);
return TRUE;
}
return FALSE;
}
static bool cmd_append_cancel(struct cmd_append_context *ctx, bool nonsync)
{
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(default_pool, ctx->client->input,
ctx->client->input->v_offset,
ctx->msg_size);
ctx->message_input = TRUE;
ctx->cmd->func = cmd_append_continue_cancel;
ctx->cmd->context = ctx;
return cmd_append_continue_cancel(ctx->cmd);
}
static bool cmd_append_continue_parsing(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
struct imap_arg *args;
struct imap_arg_list *flags_list;
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 nonsync;
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->box != NULL)
client_send_command_error(cmd, NULL);
cmd_append_finish(ctx);
return TRUE;
}
if (ret < 0) {
/* need more data */
return FALSE;
}
if (args->type == IMAP_ARG_EOL) {
/* last message */
enum mailbox_sync_flags sync_flags;
/* eat away the trailing CRLF */
client->input_skip_line = TRUE;
if (ctx->box == NULL) {
/* we failed earlier, error message is sent */
cmd_append_finish(ctx);
return TRUE;
}
ret = mailbox_transaction_commit(&ctx->t, 0);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
cmd_append_finish(ctx);
return TRUE;
}
sync_flags = ctx->box == cmd->client->mailbox ?
0 : MAILBOX_SYNC_FLAG_FAST;
cmd_append_finish(ctx);
return cmd_sync(cmd, sync_flags, 0, "OK Append completed.");
}
if (!validate_args(args, &flags_list, &internal_date_str,
&ctx->msg_size, &nonsync)) {
client_send_command_error(cmd, "Invalid arguments.");
return cmd_append_cancel(ctx, nonsync);
}
if (ctx->box == NULL) {
/* we failed earlier, make sure we just eat nonsync-literal
if it's given. */
return cmd_append_cancel(ctx, nonsync);
}
if (flags_list != NULL) {
if (!client_parse_mail_flags(cmd, flags_list->args,
&flags, &keywords_list))
return cmd_append_cancel(ctx, nonsync);
keywords = keywords_list == NULL ? NULL :
mailbox_keywords_create(ctx->t, keywords_list);
} 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 cmd_append_cancel(ctx, nonsync);
}
if (ctx->msg_size == 0) {
/* no message data, abort */
client_send_tagline(cmd, "NO Append aborted.");
cmd_append_finish(ctx);
return TRUE;
}
/* save the mail */
ctx->input = i_stream_create_limit(default_pool, client->input,
client->input->v_offset,
ctx->msg_size);
ret = mailbox_save_init(ctx->t, flags, keywords,
internal_date, timezone_offset, NULL,
ctx->input, FALSE, &ctx->save_ctx);
if (keywords != NULL)
mailbox_keywords_free(ctx->t, &keywords);
if (ret < 0) {
/* save initialization failed */
client_send_storage_error(cmd, ctx->storage);
return cmd_append_cancel(ctx, nonsync);
}
/* 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);
}
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;
bool failed;
int ret;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->save_ctx != NULL) {
while (ctx->input->v_offset != ctx->msg_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);
}
if (ret == -1)
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->input->eof || client->input->closed) {
bool all_written = ctx->input->v_offset == ctx->msg_size;
/* finished */
i_stream_unref(&ctx->input);
ctx->input = NULL;
if (ctx->save_ctx == NULL) {
/* failed above */
client_send_storage_error(cmd, ctx->storage);
failed = TRUE;
} else if (!all_written) {
/* client disconnected before it finished sending the
whole message. */
failed = TRUE;
mailbox_save_cancel(&ctx->save_ctx);
} else if (mailbox_save_finish(&ctx->save_ctx) < 0) {
failed = TRUE;
client_send_storage_error(cmd, ctx->storage);
} else {
failed = client->input->closed;
}
ctx->save_ctx = NULL;
if (failed) {
cmd_append_finish(ctx);
return TRUE;
}
/* prepare for next message */
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 struct mailbox *
get_mailbox(struct client_command_context *cmd, const char *name)
{
struct mail_storage *storage;
struct mailbox *box;
if (!client_verify_mailbox_name(cmd, name, TRUE, FALSE))
return NULL;
storage = client_find_storage(cmd, &name);
if (storage == NULL)
return NULL;
if (cmd->client->mailbox != NULL &&
mailbox_equals(cmd->client->mailbox, storage, name))
return cmd->client->mailbox;
box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_SAVEONLY |
MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
if (box == NULL) {
client_send_storage_error(cmd, storage);
return NULL;
}
return box;
}
bool cmd_append(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx;
struct mailbox_status status;
const char *mailbox;
/* <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->box = get_mailbox(cmd, mailbox);
if (ctx->box != NULL) {
ctx->storage = mailbox_get_storage(ctx->box);
if (mailbox_get_status(ctx->box, STATUS_KEYWORDS,
&status) < 0) {
client_send_storage_error(cmd, ctx->storage);
mailbox_close(&ctx->box);
} else {
client_save_keywords(&client->keywords,
status.keywords);
}
ctx->t = ctx->box == NULL ? NULL :
mailbox_transaction_begin(ctx->box,
MAILBOX_TRANSACTION_FLAG_EXTERNAL);
}
io_remove(&client->io);
client->io = io_add(i_stream_get_fd(client->input), IO_READ,
client_input, 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,
imap_max_line_length);
cmd->func = cmd_append_continue_parsing;
cmd->context = ctx;
return cmd_append_continue_parsing(cmd);
}