cmd-expunge.c \
cmd-fetch.c \
cmd-genurlauth.c \
+ cmd-getmetadata.c \
cmd-id.c \
cmd-idle.c \
cmd-list.c \
cmd-resetkey.c \
cmd-search.c \
cmd-select.c \
+ cmd-setmetadata.c \
cmd-sort.c \
cmd-status.c \
cmd-store.c \
imap-fetch.c \
imap-fetch-body.c \
imap-list.c \
+ imap-metadata.c \
imap-notify.c \
imap-search.c \
imap-search-args.c \
imap-expunge.h \
imap-fetch.h \
imap-list.h \
+ imap-metadata.h \
imap-notify.h \
imap-search.h \
imap-search-args.h \
--- /dev/null
+/* 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, '/');
+
+ imap_metadata_entry2key(entry, ctx->key_prefix, &type, &key);
+ type = type;
+ ctx->iter = mailbox_attribute_iter_init(ctx->box, type,
+ key[0] == '\0' ? "" : t_strconcat(key, "/", NULL));
+ return 1;
+ }
+}
+
+static void cmd_getmetadata_deinit(struct imap_getmetadata_context *ctx)
+{
+ ctx->cmd->client->output_cmd_lock = NULL;
+
+ if (ctx->iter != NULL)
+ (void)mailbox_attribute_iter_deinit(&ctx->iter);
+ (void)mailbox_transaction_commit(&ctx->trans);
+ mailbox_free(&ctx->box);
+}
+
+static bool cmd_getmetadata_continue(struct client_command_context *cmd)
+{
+ struct imap_getmetadata_context *ctx = cmd->context;
+ const char *const *entries;
+ unsigned int count;
+ int ret;
+
+ if (cmd->cancel) {
+ cmd_getmetadata_deinit(ctx);
+ return TRUE;
+ }
+
+ if (ctx->cur_stream != NULL) {
+ if (!cmd_getmetadata_stream_continue(ctx))
+ return FALSE;
+ i_stream_unref(&ctx->cur_stream);
+ }
+
+ entries = array_get(&ctx->entries, &count);
+ for (; ctx->entry_idx < count; ctx->entry_idx++) {
+ do {
+ T_BEGIN {
+ ret = cmd_getmetadata_send_entry_tree(ctx, entries[ctx->entry_idx]);
+ } T_END;
+ if (ret == 0)
+ return FALSE;
+ } while (ret > 0);
+ }
+ if (ctx->first_entry_sent)
+ o_stream_nsend_str(cmd->client->output, ")\r\n");
+
+ if (ctx->failed) {
+ client_send_tagline(cmd, "NO Getmetadata failed to send some entries");
+ } else if (ctx->largest_seen_size != 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "OK [METADATA LONGENTRIES %"PRIuUOFF_T"] "
+ "Getmetadata completed.", ctx->largest_seen_size));
+ } else {
+ client_send_tagline(cmd, "OK Getmetadata completed.");
+ }
+ cmd_getmetadata_deinit(ctx);
+ return TRUE;
+}
+
+bool cmd_getmetadata(struct client_command_context *cmd)
+{
+ struct imap_getmetadata_context *ctx;
+ struct mail_namespace *ns;
+ const struct imap_arg *args, *options, *entries;
+ const char *mailbox;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!cmd->client->imap_metadata_enabled) {
+ client_send_command_error(cmd, "METADATA disabled.");
+ return TRUE;
+ }
+
+ ctx = p_new(cmd->pool, struct imap_getmetadata_context, 1);
+ ctx->cmd = cmd;
+ ctx->maxsize = (uint32_t)-1;
+ ctx->cmd->context = ctx;
+
+ if (imap_arg_get_list(&args[0], &options)) {
+ if (!cmd_getmetadata_parse_options(ctx, options))
+ return TRUE;
+ }
+ if (!imap_arg_get_astring(&args[1], &mailbox)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ if (!imap_arg_get_list(&args[2], &entries)) {
+ if (!IMAP_ARG_IS_ASTRING(&args[2]) ||
+ !IMAP_ARG_IS_EOL(&args[3])) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ entries = args+2;
+ }
+ if (!imap_metadata_parse_entry_names(ctx, entries))
+ return TRUE;
+
+ 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;
+ }
+
+ ctx->box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
+ if (mailbox_open(ctx->box) < 0) {
+ client_send_storage_error(cmd, mailbox_get_storage(ctx->box));
+ mailbox_free(&ctx->box);
+ return TRUE;
+ }
+ 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 FALSE;
+ }
+ return TRUE;
+}
--- /dev/null
+/* Copyright (c) 2013 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"
+
+#define METADATA_MAX_INMEM_SIZE (1024*128)
+
+struct imap_setmetadata_context {
+ struct client_command_context *cmd;
+ struct imap_parser *parser;
+
+ const char *key_prefix;
+
+ struct mailbox *box;
+ struct mailbox_transaction_context *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)
+{
+ 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)
+ 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, *error;
+ int ret;
+ bool fatal;
+
+ /* 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;
+ error = imap_parser_get_error(ctx->parser, &fatal);
+ if (fatal) {
+ client_disconnect_with_error(ctx->cmd->client, error);
+ return -1;
+ }
+ client_send_command_error(ctx->cmd, error);
+ return -1;
+ }
+ i_assert(!IMAP_ARG_IS_EOL(&args[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(ctx->cmd, name)) {
+ 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
+ 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;
+ enum mail_attribute_type type;
+ const char *key;
+ struct mail_attribute_value value;
+ int ret;
+
+ while ((ret = i_stream_read_data(ctx->input, &data, &size, 0)) > 0)
+ i_stream_skip(ctx->input, size);
+ if (ctx->input->v_offset == ctx->entry_value_len) {
+ /* finished reading the value */
+ i_stream_seek(ctx->input, 0);
+
+ if (ctx->failed) {
+ i_stream_unref(&ctx->input);
+ 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) {
+ /* 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;
+ }
+ if (ctx->input->eof) {
+ /* client disconnected */
+ return -1;
+ }
+ return 0;
+}
+
+static int
+cmd_setmetadata_entry(struct imap_setmetadata_context *ctx,
+ const char *entry_name,
+ 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;
+
+ switch (entry_value->type) {
+ case IMAP_ARG_NIL:
+ 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);
+ 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_nflush(ctx->cmd->client->output);
+ 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;
+ 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 = 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)
+ client_send_storage_error(cmd, mailbox_get_storage(ctx->box));
+ else if (mailbox_transaction_commit(&ctx->trans) < 0)
+ client_send_storage_error(cmd, mailbox_get_storage(ctx->box));
+ else
+ client_send_tagline(cmd, "OK Setmetadata completed.");
+ cmd_setmetadata_deinit(ctx);
+ return TRUE;
+}
+
+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,
+ 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;
+ }
+
+ if (!cmd->client->imap_metadata_enabled) {
+ client_send_command_error(cmd, "METADATA disabled.");
+ 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 */
+ 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;
+ }
+
+ 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_storage_error(cmd,
+ mailbox_get_storage(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);
+}
if (!explicit_capability)
str_append(client->capability_string, " URLAUTH URLAUTH=BINARY");
}
+ if (set->imap_metadata && *mail_set->mail_attribute_dict != '\0' &&
+ !explicit_capability) {
+ client->imap_metadata_enabled = TRUE;
+ str_append(client->capability_string, " METADATA");
+ }
ident = mail_user_get_anvil_userip_ident(client->user);
if (ident != NULL) {
unsigned int notify_immediate_expunges:1;
unsigned int notify_count_changes:1;
unsigned int notify_flag_changes:1;
+ unsigned int imap_metadata_enabled:1;
};
struct imap_module_register {
{ "ID", cmd_id, 0 },
{ "IDLE", cmd_idle, COMMAND_FLAG_BREAKS_SEQS |
COMMAND_FLAG_REQUIRES_SYNC },
+ { "GETMETADATA", cmd_getmetadata, 0 },
+ { "SETMETADATA", cmd_setmetadata, 0 },
{ "NAMESPACE", cmd_namespace, 0 },
{ "NOTIFY", cmd_notify, COMMAND_FLAG_BREAKS_SEQS },
{ "SORT", cmd_sort, COMMAND_FLAG_USES_SEQS },
bool cmd_id(struct client_command_context *cmd);
bool cmd_idle(struct client_command_context *cmd);
bool cmd_namespace(struct client_command_context *cmd);
+bool cmd_getmetadata(struct client_command_context *cmd);
+bool cmd_setmetadata(struct client_command_context *cmd);
bool cmd_notify(struct client_command_context *cmd);
bool cmd_sort(struct client_command_context *cmd);
bool cmd_thread(struct client_command_context *cmd);
--- /dev/null
+/* Copyright (c) 2013 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);
+}
--- /dev/null
+#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
DEF(SET_STR, imap_logout_format),
DEF(SET_STR, imap_id_send),
DEF(SET_STR, imap_id_log),
+ DEF(SET_BOOL, imap_metadata),
DEF(SET_STR, imap_urlauth_host),
DEF(SET_UINT, imap_urlauth_port),
.imap_logout_format = "in=%i out=%o",
.imap_id_send = "name *",
.imap_id_log = "",
+ .imap_metadata = FALSE,
.imap_urlauth_host = "",
.imap_urlauth_port = 143
const char *imap_logout_format;
const char *imap_id_send;
const char *imap_id_log;
+ bool imap_metadata;
/* imap urlauth: */
const char *imap_urlauth_host;