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