# HG changeset patch # User Stephan Bosch # Date 1416011247 -7200 # Node ID a350812e07bf31da6ac94ace21f7e96aaa0aed23 # Parent 372de41933c0fc02147a9f74380c3a0a3d5f5084 lib-imap-storage: Created new METADATA API. diff -r 372de41933c0 -r a350812e07bf dovecot-config.in.in --- 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" diff -r 372de41933c0 -r a350812e07bf src/imap/Makefile.am --- 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 \ diff -r 372de41933c0 -r a350812e07bf src/imap/cmd-getmetadata.c --- 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); } } diff -r 372de41933c0 -r a350812e07bf src/imap/cmd-setmetadata.c --- 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); } diff -r 372de41933c0 -r a350812e07bf src/imap/imap-metadata.c --- 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); -} diff -r 372de41933c0 -r a350812e07bf src/imap/imap-metadata.h --- 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 diff -r 372de41933c0 -r a350812e07bf src/lib-imap-storage/Makefile.am --- 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) diff -r 372de41933c0 -r a350812e07bf src/lib-imap-storage/imap-metadata.c --- /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); +} diff -r 372de41933c0 -r a350812e07bf src/lib-imap-storage/imap-metadata.h --- /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