]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-imap-storage: Created new METADATA API.
authorStephan Bosch <stephan@rename-it.nl>
Sat, 15 Nov 2014 00:27:27 +0000 (02:27 +0200)
committerStephan Bosch <stephan@rename-it.nl>
Sat, 15 Nov 2014 00:27:27 +0000 (02:27 +0200)
dovecot-config.in.in
src/imap/Makefile.am
src/imap/cmd-getmetadata.c
src/imap/cmd-setmetadata.c
src/imap/imap-metadata.c [deleted file]
src/imap/imap-metadata.h [deleted file]
src/lib-imap-storage/Makefile.am
src/lib-imap-storage/imap-metadata.c [new file with mode: 0644]
src/lib-imap-storage/imap-metadata.h [new file with mode: 0644]

index 6a716dfed0be327f5a88ae12d2df09fbb82d7076..774c974443d0cec06cee07c2db43f53754675710 100644 (file)
@@ -23,7 +23,7 @@ LIBDOVECOT_DSYNC_DEPS="@LIBDOVECOT_DSYNC@"
 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"
index a86c8f15ca5344344f82647c8a109d1c1e395bd1..e20533ab8d950e7714d798d364cdfb371d15fb6c 100644 (file)
@@ -72,7 +72,6 @@ imap_SOURCES = \
        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 @@ headers = \
        imap-expunge.h \
        imap-fetch.h \
        imap-list.h \
-       imap-metadata.h \
        imap-notify.h \
        imap-search.h \
        imap-search-args.h \
index ae24c1fcbe632dd1a1f2495c0d040e3cbc242403..f0378e3fa87fda9f39dea614027a79fe294e18e2 100644 (file)
@@ -13,7 +13,7 @@ struct imap_getmetadata_context {
        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 imap_getmetadata_context {
        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 @@ static bool
 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 @@ imap_metadata_parse_entry_names(struct imap_getmetadata_context *ctx,
                        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 @@ metadata_add_entry(struct imap_getmetadata_context *ctx, const char *entry)
 
                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 @@ cmd_getmetadata_send_nil_reply(struct imap_getmetadata_context *ctx,
 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 @@ static void cmd_getmetadata_send_entry(struct imap_getmetadata_context *ctx,
        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 @@ cmd_getmetadata_stream_continue(struct imap_getmetadata_context *ctx)
        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 @@ static int cmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx,
                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 @@ static void cmd_getmetadata_deinit(struct imap_getmetadata_context *ctx)
 {
        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 @@ static bool cmd_getmetadata_continue(struct client_command_context *cmd)
        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 @@ cmd_getmetadata_mailbox(struct imap_getmetadata_context *ctx,
        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 @@ bool cmd_getmetadata(struct client_command_context *cmd)
 
        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 @@ bool cmd_getmetadata(struct client_command_context *cmd)
                        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);
        }
 }
index 96ea80c56b05a8e921f94174f75da2f91711621d..0e9df4c8d5331d60fd48fcab0572bc05dfc6b44f 100644 (file)
@@ -14,10 +14,8 @@ 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;
+       struct imap_metadata_transaction *trans;
 
        char *entry_name;
        uoff_t entry_value_len;
@@ -32,8 +30,8 @@ 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)
+               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 @@ cmd_setmetadata_parse_entryvalue(struct imap_setmetadata_context *ctx,
                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 @@ 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;
 
@@ -127,11 +126,9 @@ cmd_setmetadata_entry_read_stream(struct imap_setmetadata_context *ctx)
                        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 @@ cmd_setmetadata_entry(struct imap_setmetadata_context *ctx,
                      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 @@ cmd_setmetadata_entry(struct imap_setmetadata_context *ctx,
        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 @@ cmd_setmetadata_entry(struct imap_setmetadata_context *ctx,
 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 @@ static bool cmd_setmetadata_continue(struct client_command_context *cmd)
        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 @@ bool cmd_setmetadata(struct client_command_context *cmd)
 
        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_box_error(cmd, ctx->box);
-                       mailbox_free(&ctx->box);
-                       return TRUE;
-               }
+               return cmd_setmetadata_server(ctx);
        }
-       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 --git a/src/imap/imap-metadata.c b/src/imap/imap-metadata.c
deleted file mode 100644 (file)
index a49bb7d..0000000
+++ /dev/null
@@ -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 --git a/src/imap/imap-metadata.h b/src/imap/imap-metadata.h
deleted file mode 100644 (file)
index 7864652..0000000
+++ /dev/null
@@ -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
index 573ba9d9cb3909d2638338f641a5f3cacccf12f1..b08465371fb7a50fe7d6914e6094a4aec55c1a75 100644 (file)
@@ -11,11 +11,13 @@ AM_CPPFLAGS = \
 
 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 --git a/src/lib-imap-storage/imap-metadata.c b/src/lib-imap-storage/imap-metadata.c
new file mode 100644 (file)
index 0000000..b002de6
--- /dev/null
@@ -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 --git a/src/lib-imap-storage/imap-metadata.h b/src/lib-imap-storage/imap-metadata.h
new file mode 100644 (file)
index 0000000..9213a07
--- /dev/null
@@ -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