summaryrefslogtreecommitdiffstats
path: root/src/imap/cmd-append.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/cmd-append.c')
-rw-r--r--src/imap/cmd-append.c957
1 files changed, 957 insertions, 0 deletions
diff --git a/src/imap/cmd-append.c b/src/imap/cmd-append.c
new file mode 100644
index 0000000..0d0c1c3
--- /dev/null
+++ b/src/imap/cmd-append.c
@@ -0,0 +1,957 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-chain.h"
+#include "ostream.h"
+#include "str.h"
+#include "imap-resp-code.h"
+#include "istream-binary-converter.h"
+#include "mail-storage-private.h"
+#include "imap-parser.h"
+#include "imap-date.h"
+#include "imap-util.h"
+#include "imap-commands.h"
+#include "imap-msgpart-url.h"
+
+#include <sys/time.h>
+
+/* Don't allow internaldates to be too far in the future. At least with Maildir
+ they can cause problems with incremental backups since internaldate is
+ stored in file's mtime. But perhaps there are also some other reasons why
+ it might not be wanted. */
+#define INTERNALDATE_MAX_FUTURE_SECS (2*3600)
+
+struct cmd_append_context {
+ struct client *client;
+ struct client_command_context *cmd;
+ struct mailbox *box;
+ struct mailbox_transaction_context *t;
+ time_t started;
+
+ struct istream_chain *catchain;
+ uoff_t cat_msg_size;
+
+ struct istream *input;
+ struct istream *litinput;
+ uoff_t literal_size;
+
+ struct imap_parser *save_parser;
+ struct mail_save_context *save_ctx;
+ unsigned int count;
+
+ bool message_input:1;
+ bool binary_input:1;
+ bool catenate:1;
+ bool cmd_args_set:1;
+ bool failed:1;
+};
+
+static void cmd_append_finish(struct cmd_append_context *ctx);
+static bool cmd_append_continue_message(struct client_command_context *cmd);
+static bool cmd_append_parse_new_msg(struct client_command_context *cmd);
+
+static const char *
+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",
+ i_stream_get_disconnect_reason(ctx->input),
+ ctx->count, secs);
+ if (ctx->literal_size > 0) {
+ str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes",
+ lit_offset, ctx->literal_size);
+ }
+ str_append_c(str, ')');
+ return str_c(str);
+}
+
+static void client_input_append(struct client_command_context *cmd)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ struct client *client = cmd->client;
+ const char *reason;
+ bool finished;
+ uoff_t lit_offset;
+
+ i_assert(!client->destroyed);
+
+ client->last_input = ioloop_time;
+ timeout_reset(client->to_idle);
+
+ switch (i_stream_read(client->input)) {
+ case -1:
+ /* disconnected */
+ lit_offset = ctx->litinput == NULL ? 0 :
+ ctx->litinput->v_offset;
+ reason = get_disconnect_reason(ctx, lit_offset);
+ cmd_append_finish(cmd->context);
+ /* Reset command so that client_destroy() doesn't try to call
+ cmd_append_continue_message() anymore. */
+ client_command_free(&cmd);
+ client_destroy(client, reason);
+ return;
+ case -2:
+ if (ctx->message_input) {
+ /* message data, this is handled internally by
+ mailbox_save_continue() */
+ break;
+ }
+ cmd_append_finish(cmd->context);
+
+ /* parameter word is longer than max. input buffer size.
+ this is most likely an error, so skip the new data
+ until newline is found. */
+ client->input_skip_line = TRUE;
+
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Too long argument.");
+ cmd->param_error = TRUE;
+ client_command_free(&cmd);
+ return;
+ }
+
+ o_stream_cork(client->output);
+ finished = command_exec(cmd);
+ if (!finished)
+ (void)client_handle_unfinished_cmd(cmd);
+ else
+ client_command_free(&cmd);
+ cmd_sync_delayed(client);
+ o_stream_uncork(client->output);
+
+ client_continue_pending_input(client);
+}
+
+static void cmd_append_finish(struct cmd_append_context *ctx)
+{
+ if (ctx->save_parser != NULL)
+ imap_parser_unref(&ctx->save_parser);
+
+ i_assert(ctx->client->input_lock == ctx->cmd);
+
+ io_remove(&ctx->client->io);
+ /* we must put back the original flush callback before beginning to
+ sync (the command is still unfinished at that point) */
+ o_stream_set_flush_callback(ctx->client->output,
+ client_output, ctx->client);
+
+ i_stream_unref(&ctx->litinput);
+ 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_send_literal_continue(struct cmd_append_context *ctx)
+{
+ if (ctx->failed) {
+ /* tagline was already sent, we can abort here */
+ return FALSE;
+ }
+
+ o_stream_nsend(ctx->client->output, "+ OK\r\n", 6);
+ o_stream_uncork(ctx->client->output);
+ o_stream_cork(ctx->client->output);
+ return TRUE;
+}
+
+static int
+cmd_append_catenate_mpurl(struct client_command_context *cmd,
+ const char *caturl, struct imap_msgpart_url *mpurl)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ struct imap_msgpart_open_result mpresult;
+ uoff_t newsize;
+ const char *client_error;
+ int ret;
+
+ /* catenate URL */
+ ret = imap_msgpart_url_read_part(mpurl, &mpresult, &client_error);
+ if (ret < 0) {
+ client_send_box_error(cmd, ctx->box);
+ return -1;
+ }
+ if (ret == 0) {
+ /* invalid url, abort */
+ client_send_tagline(cmd,
+ t_strdup_printf("NO [BADURL %s] %s.",
+ caturl, client_error));
+ return -1;
+ }
+ if (mpresult.size == 0) {
+ /* empty input */
+ return 0;
+ }
+
+ newsize = ctx->cat_msg_size + mpresult.size;
+ if (newsize < ctx->cat_msg_size) {
+ client_send_tagline(cmd,
+ "NO [TOOBIG] Composed message grows too big.");
+ return -1;
+ }
+
+ ctx->cat_msg_size = newsize;
+ /* add this input stream to chain */
+ i_stream_chain_append(ctx->catchain, mpresult.input);
+ /* save by reading the chain stream */
+ do {
+ ret = i_stream_read(mpresult.input);
+ i_assert(ret != 0); /* we can handle only blocking input here */
+ } while (mailbox_save_continue(ctx->save_ctx) == 0 && ret != -1);
+
+ if (mpresult.input->stream_errno != 0) {
+ mailbox_set_critical(ctx->box,
+ "read(%s) failed: %s (for CATENATE URL %s)",
+ i_stream_get_name(mpresult.input),
+ i_stream_get_error(mpresult.input), caturl);
+ client_send_box_error(cmd, ctx->box);
+ ret = -1;
+ } else if (!mpresult.input->eof) {
+ /* save failed */
+ client_send_box_error(cmd, ctx->box);
+ ret = -1;
+ } else {
+ /* all the input must be consumed, so istream-chain's read()
+ unreferences the stream and we can free its parent mail */
+ i_assert(!i_stream_have_bytes_left(mpresult.input));
+ ret = 0;
+ }
+ return ret;
+}
+
+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;
+ const char *client_error;
+ int ret;
+
+ if (ctx->failed)
+ return -1;
+
+ ret = imap_msgpart_url_parse(cmd->client->user, cmd->client->mailbox,
+ caturl, &mpurl, &client_error);
+ if (ret < 0) {
+ client_send_box_error(cmd, ctx->box);
+ return -1;
+ }
+ if (ret == 0) {
+ /* invalid url, abort */
+ client_send_tagline(cmd,
+ t_strdup_printf("NO [BADURL %s] %s.",
+ caturl, client_error));
+ return -1;
+ }
+ ret = cmd_append_catenate_mpurl(cmd, caturl, mpurl);
+ imap_msgpart_url_free(&mpurl);
+ return ret;
+}
+
+static void cmd_append_catenate_text(struct client_command_context *cmd)
+{
+ struct cmd_append_context *ctx = cmd->context;
+
+ if (ctx->literal_size > UOFF_T_MAX - ctx->cat_msg_size &&
+ !ctx->failed) {
+ client_send_tagline(cmd,
+ "NO [TOOBIG] Composed message grows too big.");
+ ctx->failed = TRUE;
+ }
+
+ /* save the mail */
+ ctx->cat_msg_size += ctx->literal_size;
+ if (ctx->literal_size == 0) {
+ /* zero length literal. RFC doesn't explicitly specify
+ what should be done with this, so we'll simply
+ handle it by skipping the empty text part. */
+ ctx->litinput = i_stream_create_from_data("", 0);
+ ctx->litinput->eof = TRUE;
+ } else {
+ ctx->litinput = i_stream_create_limit(cmd->client->input,
+ ctx->literal_size);
+ i_stream_chain_append(ctx->catchain, ctx->litinput);
+ }
+}
+
+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;
+
+ /* 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) {
+ /* delay failure until we can stop
+ parsing input */
+ ctx->failed = TRUE;
+ }
+ } else if (strcasecmp(catpart, "TEXT") == 0) {
+ /* TEXT <literal> */
+ args++;
+ if (!imap_arg_get_literal_size(args, &ctx->literal_size))
+ break;
+ if (args->literal8 && !ctx->binary_input &&
+ !ctx->failed) {
+ client_send_tagline(cmd,
+ "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] "
+ "Binary input allowed only when the first part is binary.");
+ ctx->failed = TRUE;
+ }
+ *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
+ cmd_append_catenate_text(cmd);
+ return 1;
+ } else {
+ break;
+ }
+ args++;
+ }
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ /* ")" */
+ return 0;
+ }
+ if (!ctx->failed)
+ 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_eof(ctx->catchain);
+ i_stream_unref(&ctx->input);
+ ctx->catenate = FALSE;
+ ctx->catchain = NULL;
+
+ if (ctx->failed) {
+ /* APPEND has already failed */
+ if (ctx->save_ctx != NULL)
+ mailbox_save_cancel(&ctx->save_ctx);
+ } else {
+ if (mailbox_save_finish(&ctx->save_ctx) < 0) {
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ }
+ }
+}
+
+static bool catenate_args_can_stop(struct cmd_append_context *ctx,
+ const struct imap_arg *args)
+{
+ /* eat away literal_sizes from URLs */
+ while (args->type != IMAP_ARG_EOL) {
+ if (imap_arg_atom_equals(args, "TEXT"))
+ return TRUE;
+ if (!imap_arg_atom_equals(args, "URL")) {
+ /* error - handle it later */
+ return TRUE;
+ }
+ args++;
+ if (args->type == IMAP_ARG_LITERAL_SIZE ||
+ args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC) {
+ if (args->type == IMAP_ARG_LITERAL_SIZE) {
+ if (!cmd_append_send_literal_continue(ctx))
+ return TRUE;
+ }
+ imap_parser_read_last_literal(ctx->save_parser);
+ return FALSE;
+ }
+ args++;
+ }
+ return TRUE;
+}
+
+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 *client_error;
+ enum imap_parser_error parse_error;
+ bool nonsync = FALSE;
+ int ret;
+
+ if (cmd->cancel) {
+ /* cancel the command immediately (disconnection) */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ /* we're parsing inside CATENATE (..) list after handling a TEXT part.
+ it's fine that this would need to fully fit into input buffer
+ (although clients attempting to DoS could simply insert an extra
+ {1+} between the URLs) */
+ do {
+ ret = imap_parser_read_args(ctx->save_parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_SIZE |
+ IMAP_PARSE_FLAG_LITERAL8 |
+ IMAP_PARSE_FLAG_INSIDE_LIST, &args);
+ } while (ret > 0 && !catenate_args_can_stop(ctx, args));
+ if (ret == -1) {
+ client_error = imap_parser_get_error(ctx->save_parser,
+ &parse_error);
+ switch (parse_error) {
+ case IMAP_PARSE_ERROR_NONE:
+ i_unreached();
+ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+ client_send_line(client, t_strconcat("* BYE ",
+ (client->set->imap_literal_minus ? "[TOOBIG] " : ""),
+ client_error, NULL));
+ client_disconnect(client, client_error);
+ break;
+ default:
+ if (!ctx->failed)
+ client_send_command_error(cmd, client_error);
+ }
+ 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) {
+ /* invalid parameters, abort immediately */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ if (ret == 0) {
+ /* ")" */
+ cmd_append_finish_catenate(cmd);
+
+ /* last catenate part */
+ imap_parser_reset(ctx->save_parser);
+ cmd->func = cmd_append_parse_new_msg;
+ return cmd_append_parse_new_msg(cmd);
+ }
+
+ /* TEXT <literal> */
+
+ if (!nonsync) {
+ if (!cmd_append_send_literal_continue(ctx)) {
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ }
+
+ i_assert(ctx->litinput != NULL);
+ 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;
+ struct istream *input;
+ const char *internal_date_str;
+ time_t internal_date;
+ int ret, timezone_offset;
+ bool valid;
+
+ if (!ctx->cmd_args_set) {
+ ctx->cmd_args_set = TRUE;
+ client_args_finished(cmd, args);
+ }
+
+ /* [<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++;
+ }
+
+ /* <message literal> | CATENATE (..) */
+ valid = FALSE;
+ *nonsync_r = FALSE;
+ ctx->catenate = FALSE;
+ if (imap_arg_get_literal_size(args, &ctx->literal_size)) {
+ *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
+ ctx->binary_input = args->literal8;
+ valid = TRUE;
+ } else if (!imap_arg_atom_equals(args, "CATENATE")) {
+ /* invalid */
+ } else if (!imap_arg_get_list(++args, &cat_list)) {
+ /* invalid */
+ } else {
+ valid = TRUE;
+ ctx->catenate = TRUE;
+ /* We'll do BINARY conversion only if the CATENATE's first
+ part is a literal8. If it doesn't and a literal8 is seen
+ later we'll abort the append with UNKNOWN-CTE. */
+ ctx->binary_input = imap_arg_atom_equals(&cat_list[0], "TEXT") &&
+ cat_list[1].literal8;
+
+ }
+ if (!IMAP_ARG_IS_EOL(&args[1]))
+ valid = FALSE;
+ if (!valid) {
+ client->input_skip_line = TRUE;
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Invalid arguments.");
+ return -1;
+ }
+
+ if (flags_list == NULL || ctx->failed) {
+ flags = 0;
+ keywords = NULL;
+ } else {
+ 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) {
+ /* invalid keywords - delay failure */
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ keywords = NULL;
+ }
+ }
+
+ if (internal_date_str == NULL || ctx->failed) {
+ /* 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_command_error(cmd, "Invalid internal date.");
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+ 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,
+ IO_BLOCK_SIZE);
+ } else {
+ if (ctx->literal_size == 0) {
+ /* no message data, abort */
+ if (!ctx->failed) {
+ client_send_tagline(cmd,
+ "NO Can't save a zero byte message.");
+ ctx->failed = TRUE;
+ }
+ if (!*nonsync_r) {
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+ return -1;
+ }
+ /* {0+} used. although there isn't any point in using
+ MULTIAPPEND here and adding more messages, it is
+ technically valid so we'll continue parsing.. */
+ }
+ ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size);
+ ctx->input = ctx->litinput;
+ i_stream_ref(ctx->input);
+ }
+ if (ctx->binary_input) {
+ input = i_stream_create_binary_converter(ctx->input);
+ i_stream_unref(&ctx->input);
+ ctx->input = input;
+ }
+
+ if (!ctx->failed) {
+ /* save the mail */
+ ctx->save_ctx = mailbox_save_alloc(ctx->t);
+ 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) {
+ /* save initialization failed */
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ }
+ }
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+ ctx->count++;
+
+ if (cat_list == NULL) {
+ /* normal APPEND */
+ return 1;
+ } else if (cat_list->type == IMAP_ARG_EOL) {
+ /* zero parts */
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Empty CATENATE list.");
+ client->input_skip_line = TRUE;
+ return -1;
+ } else if ((ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) < 0) {
+ /* invalid parameters, abort immediately */
+ 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 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 */
+ cmd->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_command_error(cmd, "Missing message size.");
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
+ if (ret < 0) {
+ client_send_box_error(cmd, ctx->box);
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ 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.");
+ } 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.");
+ }
+ ctx->client->append_count += save_count;
+ 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_args_can_stop(struct cmd_append_context *ctx,
+ const struct imap_arg *args,
+ bool *last_literal_r)
+{
+ const struct imap_arg *cat_list;
+
+ *last_literal_r = FALSE;
+ if (args->type == IMAP_ARG_EOL)
+ return TRUE;
+
+ /* [(flags)] ["internal date"] <message literal> | CATENATE (..) */
+ if (args->type == IMAP_ARG_LIST)
+ args++;
+ if (args->type == IMAP_ARG_STRING)
+ args++;
+
+ if (args->type == IMAP_ARG_LITERAL_SIZE ||
+ args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
+ return TRUE;
+ if (imap_arg_atom_equals(args, "CATENATE") &&
+ imap_arg_get_list(&args[1], &cat_list)) {
+ if (catenate_args_can_stop(ctx, cat_list))
+ return TRUE;
+ *last_literal_r = TRUE;
+ }
+ return FALSE;
+}
+
+static bool cmd_append_parse_new_msg(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+ const struct imap_arg *args;
+ const char *client_error;
+ enum imap_parser_error parse_error;
+ unsigned int arg_min_count;
+ bool nonsync, last_literal;
+ int ret;
+
+ /* this function gets called 1) after parsing APPEND <mailbox> and
+ 2) with MULTIAPPEND extension after already saving one or more
+ mails. */
+ if (cmd->cancel) {
+ /* cancel the command immediately (disconnection) */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ /* if error occurs, the CRLF is already read. */
+ client->input_skip_line = FALSE;
+
+ /* parse the entire line up to the first message literal, or in case
+ the input buffer is full of MULTIAPPEND CATENATE URLs, parse at
+ least until the beginning of the next message */
+ arg_min_count = 0; last_literal = FALSE;
+ do {
+ if (!last_literal)
+ arg_min_count++;
+ else {
+ /* we only read the literal size. now we read the
+ literal itself. */
+ }
+ ret = imap_parser_read_args(ctx->save_parser, arg_min_count,
+ IMAP_PARSE_FLAG_LITERAL_SIZE |
+ IMAP_PARSE_FLAG_LITERAL8, &args);
+ } while (ret >= (int)arg_min_count &&
+ !cmd_append_args_can_stop(ctx, args, &last_literal));
+ if (ret == -1) {
+ if (!ctx->failed) {
+ client_error = imap_parser_get_error(ctx->save_parser, &parse_error);
+ switch (parse_error) {
+ case IMAP_PARSE_ERROR_NONE:
+ i_unreached();
+ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+ client_send_line(client, t_strconcat("* BYE ",
+ (client->set->imap_literal_minus ? "[TOOBIG] " : ""),
+ client_error, NULL));
+ client_disconnect(client, client_error);
+ break;
+ default:
+ client_send_command_error(cmd, client_error);
+ }
+ }
+ 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);
+ }
+
+ ret = cmd_append_handle_args(cmd, args, &nonsync);
+ if (ret < 0) {
+ /* invalid parameters, abort immediately */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ if (ret == 0) {
+ /* CATENATE contained only URLs. Finish it and see if there
+ are more messages. */
+ cmd_append_finish_catenate(cmd);
+ imap_parser_reset(ctx->save_parser);
+ return cmd_append_parse_new_msg(cmd);
+ }
+
+ if (!nonsync) {
+ if (!cmd_append_send_literal_continue(ctx)) {
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ }
+
+ i_assert(ctx->litinput != NULL);
+ 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;
+ int ret = 0;
+
+ if (cmd->cancel) {
+ /* cancel the command immediately (disconnection) */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ if (ctx->save_ctx != NULL) {
+ while (ctx->litinput->v_offset != ctx->literal_size) {
+ ret = i_stream_read(ctx->litinput);
+ 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) {
+ /* saving has already failed, we're just eating away the
+ literal */
+ (void)i_stream_read(ctx->litinput);
+ i_stream_skip(ctx->litinput,
+ i_stream_get_data_size(ctx->litinput));
+ }
+
+ if (ctx->litinput->eof || client->input->closed) {
+ uoff_t lit_offset = ctx->litinput->v_offset;
+
+ /* finished - do one more read, to make sure istream-chain
+ unreferences its stream, which is needed for litinput's
+ unreferencing to seek the client->input to correct
+ position. the seek is needed to avoid trying to seek
+ backwards in the ctx->input's parent stream. */
+ i_stream_seek(ctx->input, ctx->input->v_offset);
+ (void)i_stream_read(ctx->input);
+ i_stream_unref(&ctx->litinput);
+
+ if (ctx->failed) {
+ if (ctx->save_ctx != NULL)
+ mailbox_save_cancel(&ctx->save_ctx);
+ } else if (ctx->save_ctx == NULL) {
+ /* failed above */
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ } else if (lit_offset != ctx->literal_size) {
+ /* client disconnected before it finished sending the
+ whole message. */
+ ctx->failed = TRUE;
+ mailbox_save_cancel(&ctx->save_ctx);
+ client_disconnect(client,
+ get_disconnect_reason(ctx, lit_offset));
+ } else if (ctx->catenate) {
+ /* CATENATE isn't finished yet */
+ } else if (mailbox_save_finish(&ctx->save_ctx) < 0) {
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ }
+
+ if (client->input->closed) {
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ /* prepare for the next message (or its part with catenate) */
+ 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_parse_new_msg;
+ return cmd_append_parse_new_msg(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 {
+ event_add_str(cmd->global_event, "mailbox",
+ mailbox_get_vname(ctx->box));
+ ctx->t = mailbox_transaction_begin(ctx->box,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL |
+ MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS,
+ imap_client_command_get_reason(cmd));
+ }
+
+ 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
+ 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);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(ctx->save_parser);
+
+ cmd->func = cmd_append_parse_new_msg;
+ cmd->context = ctx;
+ return cmd_append_parse_new_msg(cmd);
+}