dovecot-2.2: imap: Added initial support for METADATA extension.

dovecot at dovecot.org dovecot at dovecot.org
Sat Nov 2 21:30:00 EET 2013


details:   http://hg.dovecot.org/dovecot-2.2/rev/0a08efeb3f40
changeset: 16916:0a08efeb3f40
user:      Timo Sirainen <tss at iki.fi>
date:      Sat Nov 02 21:29:39 2013 +0200
description:
imap: Added initial support for METADATA extension.
For now this is enabled only when imap_metadata=yes setting is used. The
setting will go away once the feature is complete. Also mail_attribute_dict
must be set.

TODO:
 - Metadata doesn't work for public namespaces. There should probably be a
   mail_attribute_public_dict setting for that.
 - There isn't any kind of quota or other limits
 - After ENABLE METADATA start sending untagged METADATA entries to clients
 - /shared/admin should probably return postmaster_address URL
 - Check if we handle ACLs correctly
 - RFC says that it SHOULD be possible to set METADATA entries to \NoSelect
   mailboxes. We probably will never allow this though.

diffstat:

 src/imap/Makefile.am       |    4 +
 src/imap/cmd-getmetadata.c |  392 +++++++++++++++++++++++++++++++++++++++++++++
 src/imap/cmd-setmetadata.c |  321 ++++++++++++++++++++++++++++++++++++
 src/imap/imap-client.c     |    5 +
 src/imap/imap-client.h     |    1 +
 src/imap/imap-commands.c   |    2 +
 src/imap/imap-commands.h   |    2 +
 src/imap/imap-metadata.c   |   85 +++++++++
 src/imap/imap-metadata.h   |   13 +
 src/imap/imap-settings.c   |    2 +
 src/imap/imap-settings.h   |    1 +
 11 files changed, 828 insertions(+), 0 deletions(-)

diffs (truncated from 942 to 300 lines):

diff -r 0c4ee3b9fa3b -r 0a08efeb3f40 src/imap/Makefile.am
--- a/src/imap/Makefile.am	Sat Nov 02 20:09:28 2013 +0200
+++ b/src/imap/Makefile.am	Sat Nov 02 21:29:39 2013 +0200
@@ -39,6 +39,7 @@
 	cmd-expunge.c \
 	cmd-fetch.c \
 	cmd-genurlauth.c \
+	cmd-getmetadata.c \
 	cmd-id.c \
 	cmd-idle.c \
 	cmd-list.c \
@@ -51,6 +52,7 @@
 	cmd-resetkey.c \
 	cmd-search.c \
 	cmd-select.c \
+	cmd-setmetadata.c \
 	cmd-sort.c \
 	cmd-status.c \
 	cmd-store.c \
@@ -70,6 +72,7 @@
 	imap-fetch.c \
 	imap-fetch-body.c \
 	imap-list.c \
+	imap-metadata.c \
 	imap-notify.c \
 	imap-search.c \
 	imap-search-args.c \
@@ -87,6 +90,7 @@
 	imap-expunge.h \
 	imap-fetch.h \
 	imap-list.h \
+	imap-metadata.h \
 	imap-notify.h \
 	imap-search.h \
 	imap-search-args.h \
diff -r 0c4ee3b9fa3b -r 0a08efeb3f40 src/imap/cmd-getmetadata.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-getmetadata.c	Sat Nov 02 21:29:39 2013 +0200
@@ -0,0 +1,392 @@
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "imap-quote.h"
+#include "imap-metadata.h"
+
+struct imap_getmetadata_context {
+	struct client_command_context *cmd;
+
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+
+	ARRAY_TYPE(const_string) entries;
+	uint32_t maxsize;
+	uoff_t largest_seen_size;
+	unsigned int depth;
+
+	struct istream *cur_stream;
+	uoff_t cur_stream_offset, cur_stream_size;
+
+	struct mailbox_attribute_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_parse_options(struct imap_getmetadata_context *ctx,
+			      const struct imap_arg *options)
+{
+	const char *value;
+
+	while (!IMAP_ARG_IS_EOL(options)) {
+		if (imap_arg_atom_equals(options, "MAXSIZE")) {
+			options++;
+			if (!imap_arg_get_atom(options, &value) ||
+			    str_to_uint32(value, &ctx->maxsize) < 0) {
+				client_send_command_error(ctx->cmd,
+					"Invalid value for MAXSIZE option");
+				return FALSE;
+			}
+		} else if (imap_arg_atom_equals(options, "DEPTH")) {
+			options++;
+			if (!imap_arg_get_atom(options, &value)) {
+				client_send_command_error(ctx->cmd,
+					"Invalid value for DEPTH option");
+				return FALSE;
+			}
+			if (strcmp(value, "0") == 0)
+				ctx->depth = 0;
+			else if (strcmp(value, "1") == 0)
+				ctx->depth = 1;
+			else if (strcmp(value, "infinity") == 0)
+				ctx->depth = UINT_MAX;
+			else {
+				client_send_command_error(ctx->cmd,
+					"Invalid value for DEPTH option");
+				return FALSE;
+			}
+		} else {
+			client_send_command_error(ctx->cmd, "Unknown option");
+			return FALSE;
+		}
+		options++;
+	}
+	return TRUE;
+}
+
+static bool
+imap_metadata_parse_entry_names(struct imap_getmetadata_context *ctx,
+				const struct imap_arg *entries)
+{
+	const char *value;
+
+	p_array_init(&ctx->entries, ctx->cmd->pool, 4);
+	for (; !IMAP_ARG_IS_EOL(entries); entries++) {
+		if (!imap_arg_get_astring(entries, &value)) {
+			client_send_command_error(ctx->cmd, "Entry isn't astring");
+			return FALSE;
+		}
+		if (!imap_metadata_verify_entry_name(ctx->cmd, value))
+			return FALSE;
+
+		/* names are case-insensitive so we'll always lowercase them */
+		value = p_strdup(ctx->cmd->pool, t_str_lcase(value));
+		array_append(&ctx->entries, &value, 1);
+	}
+	return TRUE;
+}
+
+static void cmd_getmetadata_send_entry(struct imap_getmetadata_context *ctx,
+				       const char *entry)
+{
+	enum mail_attribute_type type;
+	struct mail_attribute_value value;
+	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.) */
+		return;
+	}
+
+	if (mailbox_attribute_get_stream(ctx->trans, type, key, &value) < 0) {
+		(void)mailbox_get_last_error(ctx->box, &error);
+		if (error != MAIL_ERROR_NOTFOUND && error != MAIL_ERROR_PERM) {
+			client_send_untagged_storage_error(ctx->cmd->client,
+				mailbox_get_storage(ctx->box));
+			ctx->failed = TRUE;
+		}
+	}
+	if (value.value != NULL)
+		value_len = strlen(value.value);
+	else if (value.value_stream != NULL) {
+		if (i_stream_get_size(value.value_stream, TRUE, &value_len) < 0) {
+			i_error("GETMETADATA %s: i_stream_get_size(%s) failed: %s", entry,
+				i_stream_get_name(value.value_stream),
+				i_stream_get_error(value.value_stream));
+			i_stream_unref(&value.value_stream);
+			ctx->failed = TRUE;
+			return;
+		}
+	} else {
+		/* skip nonexistent entries */
+		return;
+	}
+
+	if (value_len > ctx->maxsize) {
+		/* value length is larger than specified MAXSIZE,
+		   skip this entry */
+		if (ctx->largest_seen_size < value_len)
+			ctx->largest_seen_size = value_len;
+		if (value.value_stream != NULL)
+			i_stream_unref(&value.value_stream);
+		return;
+	}
+
+	str = t_str_new(64);
+	if (!ctx->first_entry_sent) {
+		ctx->first_entry_sent = TRUE;
+		str_append(str, "* METADATA ");
+		imap_append_astring(str, mailbox_get_vname(ctx->box));
+		str_append(str, " (");
+
+		/* nothing can be sent until untagged METADATA is finished */
+		ctx->cmd->client->output_cmd_lock = ctx->cmd;
+	} else {
+		str_append_c(str, ' ');
+	}
+	imap_append_astring(str, 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));
+	} else {
+		str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", value_len);
+		o_stream_send(ctx->cmd->client->output, str_data(str), str_len(str));
+
+		ctx->cur_stream_offset = 0;
+		ctx->cur_stream_size = value_len;
+		ctx->cur_stream = value.value_stream;
+	}
+}
+
+static bool
+cmd_getmetadata_stream_continue(struct imap_getmetadata_context *ctx)
+{
+	off_t ret;
+
+	o_stream_set_max_buffer_size(ctx->cmd->client->output, 0);
+	ret = o_stream_send_istream(ctx->cmd->client->output, ctx->cur_stream);
+	o_stream_set_max_buffer_size(ctx->cmd->client->output, (size_t)-1);
+
+	if (ret > 0)
+		ctx->cur_stream_offset += ret;
+
+	if (ctx->cur_stream_offset == ctx->cur_stream_size) {
+		/* finished */
+		return TRUE;
+	}
+	if (ctx->cur_stream->stream_errno != 0) {
+		i_error("read(%s) failed: %s",
+			i_stream_get_name(ctx->cur_stream),
+			i_stream_get_error(ctx->cur_stream));
+		client_disconnect(ctx->cmd->client,
+				  "Internal GETMETADATA failure");
+		return -1;
+	}
+	if (!i_stream_have_bytes_left(ctx->cur_stream)) {
+		/* Input stream gave less data than expected */
+		i_error("read(%s): GETMETADATA stream had less data than expected",
+			i_stream_get_name(ctx->cur_stream));
+		client_disconnect(ctx->cmd->client,
+				  "Internal GETMETADATA failure");
+		return -1;
+	}
+	o_stream_set_flush_pending(ctx->cmd->client->output, TRUE);
+	return FALSE;
+}
+
+static int cmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx,
+					   const char *entry)
+{
+	const char *key;
+	enum mail_attribute_type type;
+
+	if (o_stream_get_buffer_used_size(ctx->cmd->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);
+			return 0;
+		}
+	}
+
+	if (ctx->iter != NULL) {
+		/* DEPTH iteration */
+		do {
+			key = mailbox_attribute_iter_next(ctx->iter);
+			if (key == 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));
+					ctx->failed = TRUE;
+				}
+				return -1;
+			}
+		} while (ctx->depth == 1 && strchr(key, '/') != NULL);
+		entry = t_strconcat(str_c(ctx->iter_entry_prefix), key, NULL);
+	}
+	cmd_getmetadata_send_entry(ctx, entry);
+
+	if (ctx->cur_stream != NULL) {
+		if (!cmd_getmetadata_stream_continue(ctx))
+			return 0;
+		i_stream_unref(&ctx->cur_stream);
+	}
+
+	if (ctx->iter != NULL) {
+		/* already iterating the entry */
+		return 1;
+	} else if (ctx->depth == 0) {
+		/* no iteration for the entry */
+		return -1;
+	} else {
+		/* we just sent the entry root. iterate its children. */
+		str_truncate(ctx->iter_entry_prefix, 0);
+		str_append(ctx->iter_entry_prefix, entry);
+		str_append_c(ctx->iter_entry_prefix, '/');


More information about the dovecot-cvs mailing list