diff options
Diffstat (limited to 'src/imap/cmd-setmetadata.c')
-rw-r--r-- | src/imap/cmd-setmetadata.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/src/imap/cmd-setmetadata.c b/src/imap/cmd-setmetadata.c new file mode 100644 index 0000000..247afce --- /dev/null +++ b/src/imap/cmd-setmetadata.c @@ -0,0 +1,378 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "ioloop.h" +#include "istream.h" +#include "istream-seekable.h" +#include "ostream.h" +#include "str.h" +#include "imap-metadata.h" +#include "mail-storage-private.h" + +#define METADATA_MAX_INMEM_SIZE (1024*128) + +struct imap_setmetadata_context { + struct client_command_context *cmd; + struct imap_parser *parser; + + struct mailbox *box; + struct imap_metadata_transaction *trans; + + char *entry_name; + uoff_t entry_value_len; + struct istream *input; + bool failed; + bool cmd_error_sent; + bool storage_failure; +}; + +static void cmd_setmetadata_deinit(struct imap_setmetadata_context *ctx) +{ + o_stream_set_flush_callback(ctx->cmd->client->output, + client_output, ctx->cmd->client); + + ctx->cmd->client->input_lock = NULL; + imap_parser_unref(&ctx->parser); + if (ctx->trans != NULL) + imap_metadata_transaction_rollback(&ctx->trans); + if (ctx->box != NULL && ctx->box != ctx->cmd->client->mailbox) + mailbox_free(&ctx->box); + i_free(ctx->entry_name); +} + +static int +cmd_setmetadata_parse_entryvalue(struct imap_setmetadata_context *ctx, + const char **entry_r, + const struct imap_arg **value_r) +{ + const struct imap_arg *args; + const char *name, *client_error; + enum imap_parser_error parse_error; + int ret; + + /* parse the entry name */ + ret = imap_parser_read_args(ctx->parser, 1, + IMAP_PARSE_FLAG_INSIDE_LIST, &args); + if (ret >= 0) { + if (ret == 0) { + /* ')' found */ + *entry_r = NULL; + return 1; + } + if (!imap_arg_get_astring(args, &name)) { + client_send_command_error(ctx->cmd, + "Entry name isn't astring"); + return -1; + } + + ret = imap_parser_read_args(ctx->parser, 2, + IMAP_PARSE_FLAG_INSIDE_LIST | + IMAP_PARSE_FLAG_LITERAL_SIZE | + IMAP_PARSE_FLAG_LITERAL8, &args); + } + if (ret < 0) { + if (ret == -2) + return 0; + client_error = imap_parser_get_error(ctx->parser, &parse_error); + switch (parse_error) { + case IMAP_PARSE_ERROR_NONE: + i_unreached(); + case IMAP_PARSE_ERROR_LITERAL_TOO_BIG: + client_disconnect_with_error(ctx->cmd->client, + client_error); + break; + default: + client_send_command_error(ctx->cmd, client_error); + break; + } + return -1; + } + if (args[1].type == IMAP_ARG_EOL) { + client_send_command_error(ctx->cmd, "Entry value missing"); + return -1; + } + if (args[1].type == IMAP_ARG_LIST) { + client_send_command_error(ctx->cmd, "Entry value can't be a list"); + return -1; + } + if (!ctx->cmd_error_sent && + !imap_metadata_verify_entry_name(name, &client_error)) { + client_send_command_error(ctx->cmd, client_error); + ctx->cmd_error_sent = TRUE; + } + if (ctx->cmd_error_sent) { + ctx->cmd->param_error = FALSE; + ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT; + + ctx->failed = TRUE; + if (args[1].type == IMAP_ARG_LITERAL_SIZE) { + /* client won't see "+ OK", so we can abort + immediately */ + ctx->cmd->client->input_skip_line = FALSE; + return -1; + } + } + + /* entry names are case-insensitive. handle this by using only + lowercase names. */ + *entry_r = t_str_lcase(name); + *value_r = &args[1]; + return 1; +} + +static int +cmd_setmetadata_entry_read_stream(struct imap_setmetadata_context *ctx) +{ + const unsigned char *data; + size_t size; + struct mail_attribute_value value; + int ret; + + while ((ret = i_stream_read_more(ctx->input, &data, &size)) > 0) + i_stream_skip(ctx->input, size); + if (ret == 0) + return 0; + + if (ctx->input->v_offset != ctx->entry_value_len) { + /* client disconnected */ + i_assert(ctx->input->eof); + return -1; + } + + /* finished reading the value */ + i_stream_seek(ctx->input, 0); + + if (ctx->failed) { + i_stream_unref(&ctx->input); + return 1; + } + + i_zero(&value); + value.value_stream = ctx->input; + if (imap_metadata_set(ctx->trans, ctx->entry_name, &value) < 0) { + /* delay reporting the failure so we'll finish + reading the command input */ + ctx->storage_failure = TRUE; + ctx->failed = TRUE; + } + i_stream_unref(&ctx->input); + return 1; +} + +static int +cmd_setmetadata_entry(struct imap_setmetadata_context *ctx, + const char *entry_name, + const struct imap_arg *entry_value) +{ + struct istream *inputs[2]; + struct mail_attribute_value value; + string_t *path; + int ret; + + switch (entry_value->type) { + case IMAP_ARG_NIL: + case IMAP_ARG_ATOM: + case IMAP_ARG_STRING: + /* we have the value already */ + if (ctx->failed) + return 1; + i_zero(&value); + /* NOTE: The RFC doesn't allow atoms as value, but since + Dovecot has traditionally supported it this is kept for + backwards compatibility just in case some client is + using it. */ + if (entry_value->type == IMAP_ARG_NIL) + ; + else if (!imap_arg_get_atom(entry_value, &value.value)) + value.value = imap_arg_as_nstring(entry_value); + ret = imap_metadata_set(ctx->trans, entry_name, &value); + if (ret < 0) { + /* delay reporting the failure so we'll finish + reading the command input */ + ctx->storage_failure = TRUE; + ctx->failed = TRUE; + } + return 1; + case IMAP_ARG_LITERAL_SIZE: + o_stream_nsend(ctx->cmd->client->output, "+ OK\r\n", 6); + o_stream_uncork(ctx->cmd->client->output); + o_stream_cork(ctx->cmd->client->output); + /* fall through */ + case IMAP_ARG_LITERAL_SIZE_NONSYNC: + i_free(ctx->entry_name); + ctx->entry_name = i_strdup(entry_name); + ctx->entry_value_len = imap_arg_as_literal_size(entry_value); + + inputs[0] = i_stream_create_limit(ctx->cmd->client->input, + ctx->entry_value_len); + inputs[1] = NULL; + + path = t_str_new(128); + mail_user_set_get_temp_prefix(path, ctx->cmd->client->user->set); + ctx->input = i_stream_create_seekable_path(inputs, + METADATA_MAX_INMEM_SIZE, str_c(path)); + i_stream_set_name(ctx->input, i_stream_get_name(inputs[0])); + i_stream_unref(&inputs[0]); + return cmd_setmetadata_entry_read_stream(ctx); + case IMAP_ARG_LITERAL: + case IMAP_ARG_LIST: + case IMAP_ARG_EOL: + break; + } + i_unreached(); +} + +static bool cmd_setmetadata_continue(struct client_command_context *cmd) +{ + struct imap_setmetadata_context *ctx = cmd->context; + const char *entry, *client_error; + enum mail_error error; + const struct imap_arg *value; + int ret; + + if (cmd->cancel) { + cmd_setmetadata_deinit(ctx); + return TRUE; + } + + if (ctx->input != NULL) { + if ((ret = cmd_setmetadata_entry_read_stream(ctx)) == 0) + return FALSE; + if (ret < 0) { + cmd_setmetadata_deinit(ctx); + return TRUE; + } + } + + while ((ret = cmd_setmetadata_parse_entryvalue(ctx, &entry, &value)) > 0 && + entry != NULL) { + ret = ctx->failed ? 1 : + cmd_setmetadata_entry(ctx, entry, value); + imap_parser_reset(ctx->parser); + if (ret <= 0) + break; + } + if (ret == 0) + return 0; + + if (ret < 0 || ctx->cmd_error_sent) { + /* already sent the error to client */ ; + } else if (ctx->storage_failure) { + if (ctx->box == NULL) + client_disconnect_if_inconsistent(cmd->client); + client_error = imap_metadata_transaction_get_last_error + (ctx->trans, &error); + client_send_tagline(cmd, + imap_get_error_string(cmd, client_error, error)); + } else if (imap_metadata_transaction_commit(&ctx->trans, + &error, &client_error) < 0) { + if (ctx->box == NULL) + client_disconnect_if_inconsistent(cmd->client); + client_send_tagline(cmd, + imap_get_error_string(cmd, client_error, error)); + } else { + client_send_tagline(cmd, "OK Setmetadata completed."); + } + cmd_setmetadata_deinit(ctx); + return TRUE; +} + +static bool +cmd_setmetadata_start(struct imap_setmetadata_context *ctx) +{ + struct client_command_context *cmd = ctx->cmd; + struct client *client = cmd->client; + + imap_metadata_transaction_validated_only(ctx->trans, + !cmd->client->set->imap_metadata); + /* we support large literals, so read the values from client + asynchronously the same way as APPEND does. */ + client->input_lock = cmd; + ctx->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->parser); + o_stream_unset_flush_callback(client->output); + + cmd->func = cmd_setmetadata_continue; + cmd->context = ctx; + return cmd_setmetadata_continue(cmd); +} + +static bool +cmd_setmetadata_server(struct imap_setmetadata_context *ctx) +{ + ctx->trans = imap_metadata_transaction_begin_server(ctx->cmd->client->user); + return cmd_setmetadata_start(ctx); +} + +static bool +cmd_setmetadata_mailbox(struct imap_setmetadata_context *ctx, + const char *mailbox) +{ + struct client_command_context *cmd = ctx->cmd; + struct client *client = cmd->client; + struct mail_namespace *ns; + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + if (client->mailbox != NULL && !client->mailbox_examined && + mailbox_equals(client->mailbox, ns, mailbox)) + ctx->box = client->mailbox; + else { + ctx->box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_ATTRIBUTE_SESSION); + enum mailbox_existence existence; + if (mailbox_exists(ctx->box, TRUE, &existence) < 0) { + client_send_box_error(cmd, ctx->box); + mailbox_free(&ctx->box); + return TRUE; + } else if (existence == MAILBOX_EXISTENCE_NONE) { + const char *err = t_strdup_printf(MAIL_ERRSTR_MAILBOX_NOT_FOUND, + mailbox_get_vname(ctx->box)); + mail_storage_set_error(ctx->box->storage, MAIL_ERROR_NOTFOUND, err); + client_send_box_error(cmd, ctx->box); + mailbox_free(&ctx->box); + return TRUE; + } + } + event_add_str(ctx->cmd->global_event, "mailbox", + mailbox_get_vname(ctx->box)); + ctx->trans = imap_metadata_transaction_begin(ctx->box); + return cmd_setmetadata_start(ctx); +} + +bool cmd_setmetadata(struct client_command_context *cmd) +{ + struct imap_setmetadata_context *ctx; + const struct imap_arg *args; + const char *mailbox; + int ret; + + ret = imap_parser_read_args(cmd->parser, 2, + IMAP_PARSE_FLAG_STOP_AT_LIST, &args); + if (ret == -1) { + client_send_command_error(cmd, NULL); + return TRUE; + } + if (ret == -2) + return FALSE; + if (!imap_arg_get_astring(&args[0], &mailbox) || + args[1].type != IMAP_ARG_LIST) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + ctx = p_new(cmd->pool, struct imap_setmetadata_context, 1); + ctx->cmd = cmd; + ctx->cmd->context = ctx; + + if (mailbox[0] == '\0') { + /* server attribute */ + return cmd_setmetadata_server(ctx); + } + + return cmd_setmetadata_mailbox(ctx, mailbox); +} |