Mercurial > dovecot > core-2.2
changeset 14592:5344ff4215b4
imap: Implemented CATENATE extension.
author | Stephan Bosch <stephan@rename-it.nl> |
---|---|
date | Sat, 02 Jun 2012 18:14:16 +0300 |
parents | 07e6ca397a72 |
children | e445670e7332 |
files | README configure.in src/imap/cmd-append.c |
diffstat | 3 files changed, 451 insertions(+), 181 deletions(-) [+] |
line wrap: on
line diff
--- a/README Sat Jun 02 17:56:27 2012 +0300 +++ b/README Sat Jun 02 18:14:16 2012 +0300 @@ -40,6 +40,7 @@ 3691 - IMAP4 UNSELECT command 4314 - IMAP4 Access Control List (ACL) Extension 4315 - IMAP UIDPLUS extension + 4469 - IMAP CATENATE Extension 4551 - IMAP Extension for Conditional STORE Operation or Quick Flag Changes Resynchronization 4731 - IMAP4 Extension to SEARCH Command for Controlling
--- a/configure.in Sat Jun 02 17:56:27 2012 +0300 +++ b/configure.in Sat Jun 02 18:14:16 2012 +0300 @@ -2704,7 +2704,7 @@ dnl IDLE doesn't really belong to banner. It's there just to make Blackberries dnl happy, because otherwise BIS server disables push email. capability_banner="IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE" -capability="$capability_banner SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE" +capability="$capability_banner SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE" AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities) AC_DEFINE_UNQUOTED(CAPABILITY_BANNER_STRING, "$capability_banner", IMAP capabilities advertised in banner)
--- a/src/imap/cmd-append.c Sat Jun 02 17:56:27 2012 +0300 +++ b/src/imap/cmd-append.c Sat Jun 02 18:14:16 2012 +0300 @@ -3,12 +3,14 @@ #include "imap-common.h" #include "ioloop.h" #include "istream.h" +#include "istream-chain.h" #include "ostream.h" #include "str.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> @@ -26,14 +28,19 @@ struct mailbox_transaction_context *t; time_t started; + struct istream_chain *catchain; + uoff_t cat_msg_size; + struct istream *input; - uoff_t msg_size; + struct istream *litinput; + uoff_t literal_size; struct imap_parser *save_parser; struct mail_save_context *save_ctx; unsigned int count; unsigned int message_input:1; + unsigned int catenate:1; unsigned int failed:1; }; @@ -50,7 +57,7 @@ ctx->count, secs); if (ctx->input != NULL) { str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes", - ctx->input->v_offset, ctx->msg_size); + ctx->input->v_offset, ctx->literal_size); } str_append_c(str, ')'); return str_c(str); @@ -112,37 +119,6 @@ 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(const struct imap_arg *args, - const struct imap_arg **flags_r, - const char **internal_date_r, uoff_t *msg_size_r, - bool *nonsync_r) -{ - /* [<flags>] */ - if (!imap_arg_get_list(args, flags_r)) - *flags_r = NULL; - else - args++; - - /* [<internal date>] */ - if (args->type != IMAP_ARG_STRING) - *internal_date_r = NULL; - else { - *internal_date_r = imap_arg_as_astring(args); - args++; - } - - if (!imap_arg_get_literal_size(args, msg_size_r)) { - *nonsync_r = FALSE; - return FALSE; - } - - *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC; - return TRUE; -} - static void cmd_append_finish(struct cmd_append_context *ctx) { imap_parser_unref(&ctx->save_parser); @@ -155,6 +131,8 @@ o_stream_set_flush_callback(ctx->client->output, client_output, ctx->client); + if (ctx->litinput != NULL) + i_stream_unref(&ctx->litinput); if (ctx->input != NULL) i_stream_unref(&ctx->input); if (ctx->save_ctx != NULL) @@ -184,11 +162,10 @@ return TRUE; } - if (ctx->input->v_offset == ctx->msg_size) { + if (ctx->litinput->v_offset == ctx->literal_size) { /* finished, but with MULTIAPPEND and LITERAL+ we may get more messages. */ - i_stream_unref(&ctx->input); - ctx->input = NULL; + i_stream_unref(&ctx->litinput); ctx->message_input = FALSE; imap_parser_reset(ctx->save_parser); @@ -210,7 +187,7 @@ /* 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->msg_size); + ctx->input = i_stream_create_limit(ctx->client->input, ctx->literal_size); ctx->message_input = TRUE; ctx->cmd->func = cmd_append_continue_cancel; @@ -218,20 +195,405 @@ return cmd_append_continue_cancel(ctx->cmd); } +static int +cmd_append_catenate(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; + struct imap_msgpart_url *mpurl; + const char *catpart, *error; + uoff_t newsize; + int ret; + + *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 (ctx->failed) + return -1; + + mpurl = imap_msgpart_url_parse(client->user, client->mailbox, caturl, &error); + if (mpurl == NULL) { + /* invalid url, abort */ + client_send_tagline(cmd, + t_strdup_printf("NO [BADURL %s] %s.", caturl, error)); + return -1; + } + + if (cmd->cancel) { + imap_msgpart_url_free(&mpurl); + cmd_append_finish(ctx); + return 1; + } + + /* catenate URL */ + if (ctx->save_ctx != NULL) { + struct istream *input = NULL; + uoff_t size; + + if (!imap_msgpart_url_read_part(mpurl, &input, &size, &error)) { + /* invalid url, abort */ + client_send_tagline(cmd, + t_strdup_printf("NO [BADURL %s] %s.", caturl, error)); + return -1; + } + + 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; + } + + if (input != NULL) { + ctx->cat_msg_size = newsize; + i_stream_chain_append(ctx->catchain, input); + + while (!input->eof) { + 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 (!input->eof) { + client_send_tagline(cmd, t_strdup_printf( + "NO [BADURL %s] Failed to read all data.", caturl)); + imap_msgpart_url_free(&mpurl); + return -1; + } + } + } + imap_msgpart_url_free(&mpurl); + } 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; + if (ctx->failed) { + /* we failed earlier, make sure we just eat + nonsync-literal if it's given. */ + return -1; + } + + 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(client->input, ctx->literal_size); + i_stream_chain_append(ctx->catchain, ctx->litinput); + return 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->input = NULL; + 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); + mailbox_save_set_received_date(ctx->save_ctx, + internal_date, timezone_offset); + ret = mailbox_save_begin(&ctx->save_ctx, ctx->input); + ctx->count++; + + if (cat_list != NULL && + (ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) <= 0) { + if (ret < 0) + client->input_skip_line = TRUE; + return ret; + } + + if (keywords != NULL) + mailbox_keywords_unref(&keywords); + + if (ret < 0) { + /* save initialization failed */ + client_send_storage_error(cmd, ctx->storage); + return -1; + } + 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 struct imap_arg *flags_list; - enum mail_flags flags; - const char *const *keywords_list; - struct mail_keywords *keywords; - const char *internal_date_str, *msg; - time_t internal_date; - int ret, timezone_offset; - unsigned int save_count; - bool nonsync, fatal; + const char *msg; + bool fatal, nonsync; + int ret; if (cmd->cancel) { cmd_append_finish(ctx); @@ -262,139 +624,40 @@ if (IMAP_ARG_IS_EOL(args)) { /* last message */ - enum mailbox_sync_flags sync_flags; - enum imap_sync_flags imap_flags; - struct mail_transaction_commit_changes changes; - string_t *msg; - - /* 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)); + return cmd_append_finish_parsing(cmd); } - if (!validate_args(args, &flags_list, &internal_date_str, - &ctx->msg_size, &nonsync)) { - client_send_command_error(cmd, "Invalid arguments."); - client->input_skip_line = TRUE; - return cmd_append_cancel(ctx, nonsync); - } + /* Handle multiple messages (IMAP URLs) while no literals are + encountered) */ + while ((ret = cmd_append_handle_args(cmd, &args, &nonsync)) == 0) { + cmd_append_finish_catenate(cmd); - if (ctx->failed) { - /* we failed earlier, make sure we just eat nonsync-literal - if it's given. */ - return cmd_append_cancel(ctx, nonsync); + /* 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 (flags_list != NULL) { - if (!client_parse_mail_flags(cmd, flags_list, - &flags, &keywords_list)) - return cmd_append_cancel(ctx, nonsync); - 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 cmd_append_cancel(ctx, nonsync); - } - } else { - flags = 0; - keywords = NULL; - } + 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 (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 (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 (!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; } - if (ctx->msg_size == 0) { - /* no message data, abort */ - client_send_tagline(cmd, "NO Can't save a zero byte message."); - return cmd_append_cancel(ctx, nonsync); - } - - /* save the mail */ - ctx->input = i_stream_create_limit(client->input, ctx->msg_size); - 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); - ret = mailbox_save_begin(&ctx->save_ctx, ctx->input); - - if (keywords != NULL) - mailbox_keywords_unref(&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); - o_stream_cork(client->output); - } - - ctx->count++; - ctx->message_input = TRUE; cmd->func = cmd_append_continue_message; return cmd_append_continue_message(cmd); } @@ -404,7 +667,7 @@ struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; size_t size; - int ret; + int ret = 0; if (cmd->cancel) { cmd_append_finish(ctx); @@ -412,7 +675,7 @@ } if (ctx->save_ctx != NULL) { - while (ctx->input->v_offset != ctx->msg_size) { + 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 @@ -431,12 +694,11 @@ i_stream_skip(ctx->input, size); } - if (ctx->input->eof || client->input->closed) { - bool all_written = ctx->input->v_offset == ctx->msg_size; + if (ctx->litinput->eof || client->input->closed) { + bool all_written = ctx->litinput->v_offset == ctx->literal_size; /* finished */ - i_stream_unref(&ctx->input); - ctx->input = NULL; + i_stream_unref(&ctx->litinput); if (ctx->save_ctx == NULL) { /* failed above */ @@ -448,24 +710,31 @@ 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); } - ctx->save_ctx = NULL; if (client->input->closed) { cmd_append_finish(ctx); return TRUE; } - /* prepare for next message */ + /* 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; }