changeset 18094:a350812e07bf

lib-imap-storage: Created new METADATA API.
author Stephan Bosch <stephan@rename-it.nl>
date Sat, 15 Nov 2014 02:27:27 +0200
parents 372de41933c0
children 7ef360d3fb09
files dovecot-config.in.in src/imap/Makefile.am src/imap/cmd-getmetadata.c src/imap/cmd-setmetadata.c src/imap/imap-metadata.c src/imap/imap-metadata.h src/lib-imap-storage/Makefile.am src/lib-imap-storage/imap-metadata.c src/lib-imap-storage/imap-metadata.h
diffstat 9 files changed, 531 insertions(+), 228 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-config.in.in	Sat Nov 15 02:22:56 2014 +0200
+++ b/dovecot-config.in.in	Sat Nov 15 02:27:27 2014 +0200
@@ -23,7 +23,7 @@
 LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-http -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-compression -I$(incdir)/src/lib-settings -I$(incdir)/src/lib-test"
 LIBDOVECOT_LDA_INCLUDE="-I$(incdir)/src/lib-lda -I$(incdir)/src/lda"
 LIBDOVECOT_DOVEADM_INCLUDE="-I$(incdir)/src/doveadm"
-LIBDOVECOT_STORAGE_INCLUDE="-I$(incdir)/src/lib-index -I$(incdir)/src/lib-storage -I$(incdir)/src/lib-storage/list -I$(incdir)/src/lib-storage/index -I$(incdir)/src/lib-storage/index/raw -I$(incdir)/src/plugins/quota"
+LIBDOVECOT_STORAGE_INCLUDE="-I$(incdir)/src/lib-index -I$(incdir)/src/lib-storage -I$(incdir)/src/lib-storage/list -I$(incdir)/src/lib-storage/index -I$(incdir)/src/lib-storage/index/raw -I$(incdir)/src/lib-imap-storage -I$(incdir)/src/plugins/quota"
 LIBDOVECOT_DSYNC_INCLUDE="-I$(incdir)/src/doveadm/dsync"
 LIBDOVECOT_LOGIN_INCLUDE="-I$(incdir)/src/login-common"
 LIBDOVECOT_IMAP_INCLUDE="-I$(incdir)/src/imap"
--- a/src/imap/Makefile.am	Sat Nov 15 02:22:56 2014 +0200
+++ b/src/imap/Makefile.am	Sat Nov 15 02:27:27 2014 +0200
@@ -72,7 +72,6 @@
 	imap-fetch.c \
 	imap-fetch-body.c \
 	imap-list.c \
-	imap-metadata.c \
 	imap-notify.c \
 	imap-search.c \
 	imap-search-args.c \
@@ -90,7 +89,6 @@
 	imap-expunge.h \
 	imap-fetch.h \
 	imap-list.h \
-	imap-metadata.h \
 	imap-notify.h \
 	imap-search.h \
 	imap-search-args.h \
--- a/src/imap/cmd-getmetadata.c	Sat Nov 15 02:22:56 2014 +0200
+++ b/src/imap/cmd-getmetadata.c	Sat Nov 15 02:27:27 2014 +0200
@@ -13,7 +13,7 @@
 	struct client_command_context *cmd;
 
 	struct mailbox *box;
-	struct mailbox_transaction_context *trans;
+	struct imap_metadata_transaction *trans;
 	struct mailbox_list_iterate_context *list_iter;
 
 	ARRAY_TYPE(const_string) entries;
@@ -24,16 +24,16 @@
 	struct istream *cur_stream;
 	uoff_t cur_stream_offset, cur_stream_size;
 
-	struct mailbox_attribute_iter *iter;
+	struct imap_metadata_iter *iter;
 	string_t *iter_entry_prefix;
 
-	const char *key_prefix;
 	unsigned int entry_idx;
 	bool first_entry_sent;
 	bool failed;
 };
 
-static bool cmd_getmetadata_iter_next(struct imap_getmetadata_context *ctx);
+static bool
+cmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx);
 
 static bool
 cmd_getmetadata_parse_options(struct imap_getmetadata_context *ctx,
@@ -81,7 +81,7 @@
 imap_metadata_parse_entry_names(struct imap_getmetadata_context *ctx,
 				const struct imap_arg *entries)
 {
-	const char *value;
+	const char *value, *error;
 
 	p_array_init(&ctx->entries, ctx->cmd->pool, 4);
 	for (; !IMAP_ARG_IS_EOL(entries); entries++) {
@@ -89,8 +89,10 @@
 			client_send_command_error(ctx->cmd, "Entry isn't astring");
 			return FALSE;
 		}
-		if (!imap_metadata_verify_entry_name(ctx->cmd, value))
+		if (!imap_metadata_verify_entry_name(value, &error)) {
+			client_send_command_error(ctx->cmd, error);
 			return FALSE;
+		}
 
 		/* names are case-insensitive so we'll always lowercase them */
 		value = p_strdup(ctx->cmd->pool, t_str_lcase(value));
@@ -110,9 +112,14 @@
 
 		ctx->first_entry_sent = TRUE;
 		str_append(str, "* METADATA ");
-		if (imap_utf8_to_utf7(mailbox_get_vname(ctx->box), mailbox_mutf7) < 0)
-			i_unreached();
-		imap_append_astring(str, str_c(mailbox_mutf7));
+		if (ctx->box == NULL) {
+			/* server metadata reply */
+			str_append(str, "\"\"");
+		} else {
+			if (imap_utf8_to_utf7(mailbox_get_vname(ctx->box), mailbox_mutf7) < 0)
+				i_unreached();
+			imap_append_astring(str, str_c(mailbox_mutf7));
+		}
 		str_append(str, " (");
 
 		/* nothing can be sent until untagged METADATA is finished */
@@ -140,33 +147,22 @@
 static void cmd_getmetadata_send_entry(struct imap_getmetadata_context *ctx,
 				       const char *entry, bool require_reply)
 {
-	enum mail_attribute_type type;
+	struct client *client = ctx->cmd->client;
 	struct mail_attribute_value value;
+	const char *error_string;
 	enum mail_error error;
 	uoff_t value_len;
-	const char *key;
 	string_t *str;
 
-	imap_metadata_entry2key(entry, ctx->key_prefix, &type, &key);
-	if (ctx->key_prefix == NULL &&
-	    strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT,
-		    strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) == 0) {
-		/* skip over dovecot's internal attributes. (if key_prefix
-		   isn't NULL, we're getting server metadata, which is handled
-		   inside the private metadata.) */
-		if (require_reply)
-			cmd_getmetadata_send_nil_reply(ctx, entry);
-		return;
-	}
-
-	if (mailbox_attribute_get_stream(ctx->trans, type, key, &value) < 0) {
-		(void)mailbox_get_last_error(ctx->box, &error);
+	if (imap_metadata_get_stream(ctx->trans, entry, &value) < 0) {
+		error_string = imap_metadata_transaction_get_last_error(
+			ctx->trans, &error);
 		if (error != MAIL_ERROR_NOTFOUND && error != MAIL_ERROR_PERM) {
-			client_send_untagged_storage_error(ctx->cmd->client,
-				mailbox_get_storage(ctx->box));
+			client_send_line(client, t_strconcat("* NO ", error_string, NULL));
 			ctx->failed = TRUE;
 		}
 	}
+
 	if (value.value != NULL)
 		value_len = strlen(value.value);
 	else if (value.value_stream != NULL) {
@@ -198,10 +194,10 @@
 	str = metadata_add_entry(ctx, entry);
 	if (value.value != NULL) {
 		str_printfa(str, " {%"PRIuUOFF_T"}\r\n%s", value_len, value.value);
-		o_stream_send(ctx->cmd->client->output, str_data(str), str_len(str));
+		o_stream_nsend(client->output, str_data(str), str_len(str));
 	} else {
 		str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", value_len);
-		o_stream_send(ctx->cmd->client->output, str_data(str), str_len(str));
+		o_stream_nsend(client->output, str_data(str), str_len(str));
 
 		ctx->cur_stream_offset = 0;
 		ctx->cur_stream_size = value_len;
@@ -245,35 +241,39 @@
 	return FALSE;
 }
 
-static int cmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx,
+static int
+cmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx,
 					   const char *entry)
 {
-	const char *key;
-	enum mail_attribute_type type;
+	struct client *client = ctx->cmd->client;
 
-	if (o_stream_get_buffer_used_size(ctx->cmd->client->output) >=
+	if (o_stream_get_buffer_used_size(client->output) >=
 	    CLIENT_OUTPUT_OPTIMAL_SIZE) {
-		if (o_stream_flush(ctx->cmd->client->output) <= 0) {
-			o_stream_set_flush_pending(ctx->cmd->client->output, TRUE);
+		if (o_stream_flush(client->output) <= 0) {
+			o_stream_set_flush_pending(client->output, TRUE);
 			return 0;
 		}
 	}
 
 	if (ctx->iter != NULL) {
+		const char *subentry;
+
 		/* DEPTH iteration */
 		do {
-			key = mailbox_attribute_iter_next(ctx->iter);
-			if (key == NULL) {
+			subentry = imap_metadata_iter_next(ctx->iter);
+			if (subentry == NULL) {
 				/* iteration finished, get to the next entry */
-				if (mailbox_attribute_iter_deinit(&ctx->iter) < 0) {
-					client_send_untagged_storage_error(ctx->cmd->client,
-						mailbox_get_storage(ctx->box));
+				if (imap_metadata_iter_deinit(&ctx->iter) < 0) {
+					enum mail_error error;
+					client_send_line(client, t_strconcat("* NO ",
+						imap_metadata_transaction_get_last_error(ctx->trans, &error),
+						NULL));
 					ctx->failed = TRUE;
 				}
 				return -1;
 			}
-		} while (ctx->depth == 1 && strchr(key, '/') != NULL);
-		entry = t_strconcat(str_c(ctx->iter_entry_prefix), key, NULL);
+		} while (ctx->depth == 1 && strchr(subentry, '/') != NULL);
+		entry = t_strconcat(str_c(ctx->iter_entry_prefix), subentry, NULL);
 	}
 	cmd_getmetadata_send_entry(ctx, entry, ctx->iter == NULL);
 
@@ -295,21 +295,19 @@
 		str_append(ctx->iter_entry_prefix, entry);
 		str_append_c(ctx->iter_entry_prefix, '/');
 
-		imap_metadata_entry2key(entry, ctx->key_prefix, &type, &key);
-		ctx->iter = mailbox_attribute_iter_init(ctx->box, type,
-			key[0] == '\0' ? "" : t_strconcat(key, "/", NULL));
+		ctx->iter = imap_metadata_iter_init(ctx->trans, entry);
 		return 1;
 	}
 }
 
-static void cmd_getmetadata_mailbox_deinit(struct imap_getmetadata_context *ctx)
+static void cmd_getmetadata_iter_deinit(struct imap_getmetadata_context *ctx)
 {
 	if (ctx->iter != NULL)
-		(void)mailbox_attribute_iter_deinit(&ctx->iter);
-	if (ctx->box != NULL) {
-		(void)mailbox_transaction_commit(&ctx->trans);
+		(void)imap_metadata_iter_deinit(&ctx->iter);
+	if (ctx->trans != NULL)
+		(void)imap_metadata_transaction_commit(&ctx->trans, NULL, NULL);
+	if (ctx->box != NULL)
 		mailbox_free(&ctx->box);
-	}
 	ctx->first_entry_sent = FALSE;
 	ctx->entry_idx = 0;
 }
@@ -318,7 +316,7 @@
 {
 	struct client_command_context *cmd = ctx->cmd;
 
-	cmd_getmetadata_mailbox_deinit(ctx);
+	cmd_getmetadata_iter_deinit(ctx);
 	cmd->client->output_cmd_lock = NULL;
 
 	if (ctx->list_iter != NULL &&
@@ -366,33 +364,46 @@
 	if (ctx->first_entry_sent)
 		o_stream_nsend_str(cmd->client->output, ")\r\n");
 
-	cmd_getmetadata_mailbox_deinit(ctx);
+	cmd_getmetadata_iter_deinit(ctx);
 	if (ctx->list_iter != NULL)
-		return cmd_getmetadata_iter_next(ctx);
+		return cmd_getmetadata_mailbox_iter_next(ctx);
 	cmd_getmetadata_deinit(ctx);
 	return TRUE;
 }
 
-static int
-cmd_getmetadata_try_mailbox(struct imap_getmetadata_context *ctx,
-			    struct mail_namespace *ns, const char *mailbox)
+static bool
+cmd_getmetadata_start(struct imap_getmetadata_context *ctx)
 {
 	struct client_command_context *cmd = ctx->cmd;
 
-	ctx->box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
-	if (mailbox_open(ctx->box) < 0)
-		return -1;
-	ctx->trans = mailbox_transaction_begin(ctx->box, 0);
-
 	if (ctx->depth > 0)
 		ctx->iter_entry_prefix = str_new(cmd->pool, 128);
 
 	if (!cmd_getmetadata_continue(cmd)) {
 		cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
 		cmd->func = cmd_getmetadata_continue;
-		return 0;
+		return FALSE;
 	}
-	return 1;
+	return TRUE;
+}
+
+static bool
+cmd_getmetadata_server(struct imap_getmetadata_context *ctx)
+{
+	ctx->trans = imap_metadata_transaction_begin_server(ctx->cmd->client->user);
+	return cmd_getmetadata_start(ctx);
+}
+
+static int
+cmd_getmetadata_try_mailbox(struct imap_getmetadata_context *ctx,
+			    struct mail_namespace *ns, const char *mailbox)
+{
+	ctx->box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
+	if (mailbox_open(ctx->box) < 0)
+		return -1;
+
+	ctx->trans = imap_metadata_transaction_begin(ctx->box);
+	return cmd_getmetadata_start(ctx) ? 1 : 0;
 }
 
 static bool
@@ -409,7 +420,8 @@
 	return ret != 0;
 }
 
-static bool cmd_getmetadata_iter_next(struct imap_getmetadata_context *ctx)
+static bool
+cmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx)
 {
 	const struct mailbox_info *info;
 	int ret;
@@ -480,11 +492,10 @@
 
 	if (mailbox[0] == '\0') {
 		/* server attribute */
-		ctx->key_prefix = MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER;
-		ns = mail_namespace_find_inbox(cmd->client->user->namespaces);
-		return cmd_getmetadata_mailbox(ctx, ns, "INBOX");
+		return cmd_getmetadata_server(ctx);
 	} else if (strchr(mailbox, '*') == NULL &&
 		   strchr(mailbox, '%') == NULL) {
+		/* mailbox attribute */
 		ns = client_find_namespace(cmd, &mailbox);
 		if (ns == NULL)
 			return TRUE;
@@ -500,6 +511,6 @@
 			mailbox_list_iter_init_namespaces(
 				cmd->client->user->namespaces,
 				patterns, MAIL_NAMESPACE_TYPE_MASK_ALL, 0);
-		return cmd_getmetadata_iter_next(ctx);
+		return cmd_getmetadata_mailbox_iter_next(ctx);
 	}
 }
--- a/src/imap/cmd-setmetadata.c	Sat Nov 15 02:22:56 2014 +0200
+++ b/src/imap/cmd-setmetadata.c	Sat Nov 15 02:27:27 2014 +0200
@@ -14,10 +14,8 @@
 	struct client_command_context *cmd;
 	struct imap_parser *parser;
 
-	const char *key_prefix;
-
 	struct mailbox *box;
-	struct mailbox_transaction_context *trans;
+	struct imap_metadata_transaction *trans;
 
 	char *entry_name;
 	uoff_t entry_value_len;
@@ -32,8 +30,8 @@
 	ctx->cmd->client->input_lock = NULL;
 	imap_parser_unref(&ctx->parser);
 	if (ctx->trans != NULL)
-		mailbox_transaction_rollback(&ctx->trans);
-	if (ctx->box != ctx->cmd->client->mailbox)
+		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);
 }
@@ -84,12 +82,15 @@
 		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(ctx->cmd, name)) {
+	if (!ctx->cmd_error_sent &&
+	    !imap_metadata_verify_entry_name(name, &error)) {
+		client_send_command_error(ctx->cmd, 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->cmd_error_sent = TRUE;
 		ctx->failed = TRUE;
 		if (args[1].type == IMAP_ARG_LITERAL_SIZE) {
 			/* client won't see "+ OK", so we can abort
@@ -111,8 +112,6 @@
 {
 	const unsigned char *data;
 	size_t size;
-	enum mail_attribute_type type;
-	const char *key;
 	struct mail_attribute_value value;
 	int ret;
 
@@ -127,11 +126,9 @@
 			return 1;
 		}
 
-		imap_metadata_entry2key(ctx->entry_name, ctx->key_prefix,
-					&type, &key);
 		memset(&value, 0, sizeof(value));
 		value.value_stream = ctx->input;
-		if (mailbox_attribute_set(ctx->trans, type, key, &value) < 0) {
+		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;
@@ -153,8 +150,6 @@
 		      const struct imap_arg *entry_value)
 {
 	struct istream *inputs[2];
-	enum mail_attribute_type type;
-	const char *key;
 	struct mail_attribute_value value;
 	string_t *path;
 	int ret;
@@ -164,15 +159,11 @@
 	case IMAP_ARG_ATOM:
 	case IMAP_ARG_STRING:
 		/* we have the value already */
-		imap_metadata_entry2key(entry_name, ctx->key_prefix,
-					&type, &key);
 		if (ctx->failed)
 			return 1;
 		memset(&value, 0, sizeof(value));
 		value.value = imap_arg_as_nstring(entry_value);
-		ret = value.value == NULL ?
-			mailbox_attribute_unset(ctx->trans, type, key) :
-			mailbox_attribute_set(ctx->trans, type, key, &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 */
@@ -213,7 +204,8 @@
 static bool cmd_setmetadata_continue(struct client_command_context *cmd)
 {
 	struct imap_setmetadata_context *ctx = cmd->context;
-	const char *entry;
+	const char *entry, *error_string;
+	enum mail_error error;
 	const struct imap_arg *value;
 	int ret;
 
@@ -242,24 +234,85 @@
 	if (ret == 0)
 		return 0;
 
-	if (ret < 0 || ctx->cmd_error_sent)
+	if (ret < 0 || ctx->cmd_error_sent) {
 		/* already sent the error to client */ ;
-	else if (ctx->storage_failure)
-		client_send_box_error(cmd, ctx->box);
-	else if (mailbox_transaction_commit(&ctx->trans) < 0)
-		client_send_box_error(cmd, ctx->box);
-	else
+	} else if (ctx->storage_failure) {
+		if (ctx->box == NULL)
+			client_disconnect_if_inconsistent(cmd->client);
+		error_string = imap_metadata_transaction_get_last_error
+			(ctx->trans, &error);
+		client_send_tagline(cmd,
+			imap_get_error_string(cmd, error_string, error));
+	} else if (imap_metadata_transaction_commit(&ctx->trans, 
+						&error, &error_string) < 0) {
+		if (ctx->box == NULL)
+			client_disconnect_if_inconsistent(cmd->client);
+		client_send_tagline(cmd,
+			imap_get_error_string(cmd, error_string, 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;
+
+	/* 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);
+	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, 0);
+		if (mailbox_open(ctx->box) < 0) {
+			client_send_box_error(cmd, ctx->box);
+			mailbox_free(&ctx->box);
+			return TRUE;
+		}
+	}
+	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;
-	struct mail_namespace *ns;
 	int ret;
 
 	ret = imap_parser_read_args(cmd->parser, 2,
@@ -287,35 +340,8 @@
 
 	if (mailbox[0] == '\0') {
 		/* server attribute */
-		ctx->key_prefix = MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER;
-		ns = mail_namespace_find_inbox(cmd->client->user->namespaces);
-		mailbox = "INBOX";
-	} else {
-		ns = client_find_namespace(cmd, &mailbox);
-		if (ns == NULL)
-			return TRUE;
+		return cmd_setmetadata_server(ctx);
 	}
 
-	if (cmd->client->mailbox != NULL && !cmd->client->mailbox_examined &&
-	    mailbox_equals(cmd->client->mailbox, ns, mailbox))
-		ctx->box = cmd->client->mailbox;
-	else {
-		ctx->box = mailbox_alloc(ns->list, mailbox, 0);
-		if (mailbox_open(ctx->box) < 0) {
-			client_send_box_error(cmd, ctx->box);
-			mailbox_free(&ctx->box);
-			return TRUE;
-		}
-	}
-	ctx->trans = mailbox_transaction_begin(ctx->box, 0);
-	/* we support large literals, so read the values from client
-	   asynchronously the same way as APPEND does. */
-	cmd->client->input_lock = cmd;
-	ctx->parser = imap_parser_create(cmd->client->input, cmd->client->output,
-					 cmd->client->set->imap_max_line_length);
-	o_stream_unset_flush_callback(cmd->client->output);
-
-	cmd->func = cmd_setmetadata_continue;
-	cmd->context = ctx;
-	return cmd_setmetadata_continue(cmd);
+	return cmd_setmetadata_mailbox(ctx, mailbox);
 }
--- a/src/imap/imap-metadata.c	Sat Nov 15 02:22:56 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/* Copyright (c) 2013-2014 Dovecot authors, see the included COPYING file */
-
-#include "imap-common.h"
-#include "imap-metadata.h"
-
-bool imap_metadata_verify_entry_name(struct client_command_context *cmd,
-				     const char *name)
-{
-	unsigned int i;
-	bool ok;
-
-	if (name[0] != '/') {
-		client_send_command_error(cmd,
-			"Entry name must begin with '/'");
-		return FALSE;
-	}
-	for (i = 0; name[i] != '\0'; i++) {
-		switch (name[i]) {
-		case '/':
-			if (i > 0 && name[i-1] == '/') {
-				client_send_command_error(cmd,
-					"Entry name can't contain consecutive '/'");
-				return FALSE;
-			}
-			if (name[i+1] == '\0') {
-				client_send_command_error(cmd,
-					"Entry name can't end with '/'");
-				return FALSE;
-			}
-			break;
-		case '*':
-			client_send_command_error(cmd,
-				"Entry name can't contain '*'");
-			return FALSE;
-		case '%':
-			client_send_command_error(cmd,
-				"Entry name can't contain '%'");
-			return FALSE;
-		default:
-			if (name[i] <= 0x19) {
-				client_send_command_error(cmd,
-					"Entry name can't contain control chars");
-				return FALSE;
-			}
-			break;
-		}
-	}
-	T_BEGIN {
-		const char *prefix, *p = strchr(name+1, '/');
-
-		prefix = p == NULL ? name : t_strdup_until(name, p);
-		ok = strcasecmp(prefix, IMAP_METADATA_PRIVATE_PREFIX) == 0 ||
-			strcasecmp(prefix, IMAP_METADATA_SHARED_PREFIX) == 0;
-	} T_END;
-	if (!ok) {
-		client_send_command_error(cmd,
-			"Entry name must begin with /private or /shared");
-		return FALSE;
-	}
-	return TRUE;
-}
-
-void imap_metadata_entry2key(const char *entry, const char *key_prefix,
-			     enum mail_attribute_type *type_r,
-			     const char **key_r)
-{
-	if (strncmp(entry, IMAP_METADATA_PRIVATE_PREFIX,
-		    strlen(IMAP_METADATA_PRIVATE_PREFIX)) == 0) {
-		*key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX);
-		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
-	} else {
-		i_assert(strncmp(entry, IMAP_METADATA_SHARED_PREFIX,
-				 strlen(IMAP_METADATA_SHARED_PREFIX)) == 0);
-		*key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX);
-		*type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
-	}
-	if ((*key_r)[0] == '\0') {
-		/* /private or /shared prefix has no value itself */
-	} else {
-		i_assert((*key_r)[0] == '/');
-		*key_r += 1;
-	}
-	if (key_prefix != NULL)
-		*key_r = t_strconcat(key_prefix, *key_r, NULL);
-}
--- a/src/imap/imap-metadata.h	Sat Nov 15 02:22:56 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-#ifndef IMAP_METADATA_H
-#define IMAP_METADATA_H
-
-#define IMAP_METADATA_PRIVATE_PREFIX "/private"
-#define IMAP_METADATA_SHARED_PREFIX "/shared"
-
-bool imap_metadata_verify_entry_name(struct client_command_context *cmd,
-				     const char *name);
-void imap_metadata_entry2key(const char *entry, const char *key_prefix,
-			     enum mail_attribute_type *type_r,
-			     const char **key_r);
-
-#endif
--- a/src/lib-imap-storage/Makefile.am	Sat Nov 15 02:22:56 2014 +0200
+++ b/src/lib-imap-storage/Makefile.am	Sat Nov 15 02:27:27 2014 +0200
@@ -11,11 +11,13 @@
 
 libimap_storage_la_SOURCES = \
 	imap-msgpart.c \
-	imap-msgpart-url.c
+	imap-msgpart-url.c \
+	imap-metadata.c
 
 headers = \
 	imap-msgpart.h \
-	imap-msgpart-url.h
+	imap-msgpart-url.h \
+	imap-metadata.h
 
 pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-storage/imap-metadata.c	Sat Nov 15 02:27:27 2014 +0200
@@ -0,0 +1,307 @@
+/* Copyright (c) 2013-2014 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "imap-metadata.h"
+
+struct imap_metadata_transaction {
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+
+	enum mail_error error;
+	char *error_string;
+
+	unsigned int server:1;
+};
+
+bool imap_metadata_verify_entry_name(const char *name, const char **error_r)
+{
+	unsigned int i;
+	bool ok;
+
+	if (name[0] != '/') {
+		*error_r = "Entry name must begin with '/'";
+		return FALSE;
+	}
+	for (i = 0; name[i] != '\0'; i++) {
+		switch (name[i]) {
+		case '/':
+			if (i > 0 && name[i-1] == '/') {
+				*error_r = "Entry name can't contain consecutive '/'";
+				return FALSE;
+			}
+			if (name[i+1] == '\0') {
+				*error_r = "Entry name can't end with '/'";
+				return FALSE;
+			}
+			break;
+		case '*':
+			*error_r = "Entry name can't contain '*'";
+			return FALSE;
+		case '%':
+			*error_r = "Entry name can't contain '%'";
+			return FALSE;
+		default:
+			if (name[i] <= 0x19) {
+				*error_r = "Entry name can't contain control chars";
+				return FALSE;
+			}
+			break;
+		}
+	}
+	T_BEGIN {
+		const char *prefix, *p = strchr(name+1, '/');
+
+		prefix = p == NULL ? name : t_strdup_until(name, p);
+		ok = strcasecmp(prefix, IMAP_METADATA_PRIVATE_PREFIX) == 0 ||
+			strcasecmp(prefix, IMAP_METADATA_SHARED_PREFIX) == 0;
+	} T_END;
+	if (!ok) {
+		*error_r = "Entry name must begin with /private or /shared";
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static void
+imap_metadata_transaction_set_error(struct imap_metadata_transaction *imtrans,
+				    enum mail_error error, const char *string)
+{
+	i_free(imtrans->error_string);
+	imtrans->error_string = i_strdup(string);
+	imtrans->error = error;
+}
+
+static bool
+imap_metadata_entry2key(struct imap_metadata_transaction *imtrans,
+			const char *entry, enum mail_attribute_type *type_r,
+			const char **key_r)
+{
+	const char *key_prefix = (imtrans->server ?
+		MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL);
+
+	/* names are case-insensitive so we'll always lowercase them */
+	entry = t_str_lcase(entry);
+
+	if (strncmp(entry, IMAP_METADATA_PRIVATE_PREFIX,
+		    strlen(IMAP_METADATA_PRIVATE_PREFIX)) == 0) {
+		*key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX);
+		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
+	} else {
+		i_assert(strncmp(entry, IMAP_METADATA_SHARED_PREFIX,
+				 strlen(IMAP_METADATA_SHARED_PREFIX)) == 0);
+		*key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX);
+		*type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
+	}
+	if ((*key_r)[0] == '\0') {
+		/* /private or /shared prefix has no value itself */
+	} else {
+		i_assert((*key_r)[0] == '/');
+		*key_r += 1;
+	}
+	if (key_prefix != NULL)
+		*key_r = t_strconcat(key_prefix, *key_r, NULL);
+
+	/* skip over dovecot's internal attributes. (server metadata is handled
+	   inside the private metadata.) */
+	return (imtrans->server ||
+		strncmp(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT,
+		    strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) != 0);
+}
+
+static int
+imap_metadata_get_mailbox_transaction(struct imap_metadata_transaction *imtrans)
+{
+	if (imtrans->trans != NULL)
+		return 0;
+
+	if (imtrans->box == NULL || mailbox_open(imtrans->box) < 0)
+		return -1;
+	imtrans->trans = mailbox_transaction_begin(imtrans->box, 0);
+	return 0;
+}
+
+int imap_metadata_set(struct imap_metadata_transaction *imtrans,
+	const char *entry, const struct mail_attribute_value *value)
+{
+	enum mail_attribute_type type;
+	const char *key;
+
+	if (!imap_metadata_entry2key(imtrans, entry, &type, &key)) {
+		imap_metadata_transaction_set_error(imtrans, MAIL_ERROR_PARAMS,
+			"Internal mailbox attributes cannot be accessed");
+		return -1;
+	}
+
+	if (imap_metadata_get_mailbox_transaction(imtrans) < 0)
+		return -1;
+	return (value->value == NULL ?
+		mailbox_attribute_unset(imtrans->trans, type, key) :
+		mailbox_attribute_set(imtrans->trans, type, key, value));
+}
+
+int imap_metadata_unset(struct imap_metadata_transaction *imtrans,
+			const char *entry)
+{
+	struct mail_attribute_value value;
+
+	memset(&value, 0, sizeof(value));
+	return imap_metadata_set(imtrans, entry, &value);
+}
+
+int imap_metadata_get(struct imap_metadata_transaction *imtrans,
+		      const char *entry, struct mail_attribute_value *value_r)
+{
+	enum mail_attribute_type type;
+	const char *key;
+
+	memset(value_r, 0, sizeof(*value_r));
+	if (!imap_metadata_entry2key(imtrans, entry, &type, &key))
+		return 0;
+	if (imap_metadata_get_mailbox_transaction(imtrans) < 0)
+		return -1;
+	if (mailbox_attribute_get(imtrans->trans, type, key, value_r) < 0)
+		return -1;
+	return 1;
+}
+
+int imap_metadata_get_stream(struct imap_metadata_transaction *imtrans,
+		      const char *entry, struct mail_attribute_value *value_r)
+{
+	enum mail_attribute_type type;
+	const char *key;
+
+	memset(value_r, 0, sizeof(*value_r));
+	if (!imap_metadata_entry2key(imtrans, entry, &type, &key))
+		return 0;
+	if (imap_metadata_get_mailbox_transaction(imtrans) < 0)
+		return -1;
+	if (mailbox_attribute_get_stream(imtrans->trans, type, key, value_r) < 0)
+		return -1;
+	return 1;
+}
+
+struct imap_metadata_iter {
+	struct mailbox_attribute_iter *iter;
+};
+
+struct imap_metadata_iter *
+imap_metadata_iter_init(struct imap_metadata_transaction *imtrans,
+			const char *entry)
+{
+	struct imap_metadata_iter *iter;
+	enum mail_attribute_type type;
+	const char *key;
+
+	iter = i_new(struct imap_metadata_iter, 1);
+	if (imap_metadata_entry2key(imtrans, entry, &type, &key)) {
+		const char *prefix =
+			key[0] == '\0' ? "" : t_strconcat(key, "/", NULL);
+		iter->iter = mailbox_attribute_iter_init(imtrans->box, type,
+							 prefix);
+	}
+	return iter;
+}
+
+const char *imap_metadata_iter_next(struct imap_metadata_iter *iter)
+{
+	if (iter->iter == NULL)
+		return NULL;
+	return mailbox_attribute_iter_next(iter->iter);
+}
+
+int imap_metadata_iter_deinit(struct imap_metadata_iter **_iter)
+{
+	struct imap_metadata_iter *iter = *_iter;
+	int ret;
+
+	*_iter = NULL;
+
+	if (iter->iter == NULL)
+		ret = 0;
+	else
+		ret = mailbox_attribute_iter_deinit(&iter->iter);
+	i_free(iter);
+	return ret;
+}
+
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin(struct mailbox *box)
+{
+	struct imap_metadata_transaction *imtrans;
+
+	imtrans = i_new(struct imap_metadata_transaction, 1);
+	imtrans->box = box;
+	return imtrans;
+}
+
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin_server(struct mail_user *user)
+{
+	struct mail_namespace *ns;
+	struct mailbox *box;
+	struct imap_metadata_transaction *imtrans;
+
+	ns = mail_namespace_find_inbox(user->namespaces);
+	box = mailbox_alloc(ns->list, "INBOX", 0);
+	imtrans = imap_metadata_transaction_begin(box);
+	imtrans->server = TRUE;
+	return imtrans;
+}
+
+static void
+imap_metadata_transaction_finish(struct imap_metadata_transaction **_imtrans)
+{
+	struct imap_metadata_transaction *imtrans = *_imtrans;
+
+	if (imtrans->server)
+		mailbox_free(&imtrans->box);
+
+	i_free(imtrans->error_string);
+	i_free(imtrans);
+	*_imtrans = NULL;
+}
+
+int imap_metadata_transaction_commit(
+	struct imap_metadata_transaction **_imtrans,
+	enum mail_error *error_code_r, const char **error_r)
+{
+	struct imap_metadata_transaction *imtrans = *_imtrans;
+	int ret = 0;
+
+	if (imtrans->trans != NULL) {
+		const char *error = NULL;
+		ret = mailbox_transaction_commit(&imtrans->trans);
+		if (ret < 0)
+			error = mailbox_get_last_error(imtrans->box, error_code_r);
+		if (error_r != NULL)
+			*error_r = error;
+	}
+	imap_metadata_transaction_finish(_imtrans);
+	return ret;
+}
+
+void imap_metadata_transaction_rollback(
+	struct imap_metadata_transaction **_imtrans)
+{
+	struct imap_metadata_transaction *imtrans = *_imtrans;
+
+	if (imtrans->trans != NULL)
+		mailbox_transaction_rollback(&imtrans->trans);
+	imap_metadata_transaction_finish(_imtrans);
+}
+
+const char *
+imap_metadata_transaction_get_last_error(
+	struct imap_metadata_transaction *imtrans,
+	enum mail_error *error_code_r)
+{
+	if  (imtrans->error != MAIL_ERROR_NONE) {
+		if (error_code_r != NULL)
+			*error_code_r = imtrans->error;
+		return imtrans->error_string;
+	}
+	i_assert(imtrans->box != NULL);
+	return mailbox_get_last_error(imtrans->box, error_code_r);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-storage/imap-metadata.h	Sat Nov 15 02:27:27 2014 +0200
@@ -0,0 +1,57 @@
+#ifndef IMAP_METADATA_H
+#define IMAP_METADATA_H
+
+#define IMAP_METADATA_PRIVATE_PREFIX "/private"
+#define IMAP_METADATA_SHARED_PREFIX "/shared"
+
+struct imap_metadata_iter;
+struct imap_metadata_transaction;
+
+/* Checks whether IMAP metadata entry name is valid */
+bool imap_metadata_verify_entry_name(
+	const char *name, const char **error_r);
+
+/* Set IMAP metadata entry to value. */
+int imap_metadata_set(struct imap_metadata_transaction *imtrans,
+	const char *entry, const struct mail_attribute_value *value);
+/* Delete IMAP metadata entry. This is just a wrapper to
+   imap_metadata_set() with value->value=NULL. */
+int imap_metadata_unset(struct imap_metadata_transaction *imtrans,
+	const char *entry);
+/* Returns value for IMAP metadata entry. Returns 1 if value was returned,
+   0 if value wasn't found (set to NULL), -1 if error */
+int imap_metadata_get(struct imap_metadata_transaction *imtrans,
+	const char *entry, struct mail_attribute_value *value_r);
+/* Same as imap_metadata_get(), but the returned value may be either an
+   input stream or a string. */
+int imap_metadata_get_stream(struct imap_metadata_transaction *imtrans,
+	const char *entry, struct mail_attribute_value *value_r);
+
+/* Iterate through IMAP metadata entries names under the specified entry. */
+struct imap_metadata_iter *
+imap_metadata_iter_init(struct imap_metadata_transaction *imtrans,
+	const char *entry);
+/* Returns the next IMAP metadata entry name or NULL if there are no more
+   entries. */
+const char *imap_metadata_iter_next(struct imap_metadata_iter *iter);
+int imap_metadata_iter_deinit(struct imap_metadata_iter **_iter);
+
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin(struct mailbox *box);
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin_mailbox(struct mail_user *user,
+					const char *mailbox);
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin_server(struct mail_user *user);
+
+int imap_metadata_transaction_commit(
+	struct imap_metadata_transaction **_imtrans,
+	enum mail_error *error_code_r, const char **error_r);
+void imap_metadata_transaction_rollback(
+	struct imap_metadata_transaction **_imtrans);
+const char *
+imap_metadata_transaction_get_last_error(
+	struct imap_metadata_transaction *imtrans,
+	enum mail_error *error_code_r);
+
+#endif