summaryrefslogtreecommitdiffstats
path: root/src/imap/cmd-setmetadata.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/cmd-setmetadata.c')
-rw-r--r--src/imap/cmd-setmetadata.c378
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);
+}