]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
mail-lua: Implement mailbox metadata API
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 16 Apr 2019 08:42:56 +0000 (11:42 +0300)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 24 Apr 2019 11:54:22 +0000 (11:54 +0000)
Adds methods for accessing and manipulating mailbox metadata

 * user:metadata_get("key", "key", ..) - returns value for keys
 * user:metadata_set("key", "value") - sets value for key
 * user:metadata_unset("key", "value") - unsets key
 * user:metadata_list("prefix", "prefix", ...) - lists keys in dict for prefixes

 * mailbox:metadata_get("key", "key", ..) - returns value for keys
 * mailbox:metadata_set("key", "value") - sets value for key
 * mailbox:metadata_unset("key", "value") - unsets key
 * mailbox:metadata_list("prefix", "prefix", ...) - lists keys in dict for prefixes

User metadata is stored under /(private|shared)/vendor/vendor.dovecot/pvt/server to
mailbox INBOX, and is accessible with mailbox metadata methods with INBOX.

Also mail lua exports constants to simplify usage

dovecot.storage.MAILBOX_ATTRIBUTE_PREFIX_DOVECOT
"vendor/vendor.dovecot/"
dovecot.storage.MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT
"vendor/vendor.dovecot/pvt/"
dovecot.storage.MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER
"vendor/vendor.dovecot/pvt/server/"

src/plugins/mail-lua/mail-storage-lua.c

index 45f92d0ced00df5915407ce44e6cd8240803db1e..264e3ff1c1d25a163745bc1bcc8216d8601de7cf 100644 (file)
@@ -2,11 +2,13 @@
 
 #include "lib.h"
 #include "str.h"
+#include "istream.h"
 #include "array.h"
 #include "var-expand.h"
 #include "dlua-script.h"
 #include "dlua-script-private.h"
 #include "mail-storage.h"
+#include "mailbox-attribute.h"
 #include "mail-storage-lua.h"
 #include "mail-user.h"
 
 #define LUA_STORAGE_MAILBOX "struct mailbox"
 #define LUA_STORAGE_MAIL "struct mail"
 
+/** shared functions
+ */
+
+struct lua_storage_keyvalue {
+       const char *key;
+       const char *value;
+       size_t value_len;
+};
+
+ARRAY_DEFINE_TYPE(lua_storage_keyvalue, struct lua_storage_keyvalue);
+
+/* lookup mailbox attribute */
+static int lua_storage_mailbox_attribute_get(struct mailbox *box, const char *key,
+                                            const char **value_r, size_t *value_len_r,
+                                            const char **error_r)
+{
+       struct mail_attribute_value value;
+       enum mail_attribute_type attr_type;
+       int ret;
+
+       if (str_begins(key, "/private/")) {
+               attr_type = MAIL_ATTRIBUTE_TYPE_PRIVATE;
+               key += 9;
+       } else if (str_begins(key, "/shared/")) {
+               attr_type = MAIL_ATTRIBUTE_TYPE_SHARED;
+               key += 8;
+       } else {
+               *error_r = "Invalid key prefix, must be /private/ or /shared/";
+               return -1;
+       }
+
+       /* get the attribute */
+       if ((ret = mailbox_attribute_get_stream(box, attr_type, key, &value)) < 0) {
+               *error_r = mailbox_get_last_error(box, NULL);
+               return ret;
+       } else if (ret == 0) {
+               /* was not found */
+               *value_r = NULL;
+               *value_len_r = 0;
+               return 0;
+       }
+
+       if (value.value_stream != NULL) {
+               string_t *str = t_str_new(128);
+               const unsigned char *data;
+               size_t siz;
+               while((ret = i_stream_read_more(value.value_stream, &data, &siz))>0) {
+                       str_append_data(str, data, siz);
+                       i_stream_skip(value.value_stream, siz);
+               }
+               i_assert(ret != 0);
+               if (ret == -1 && !value.value_stream->eof) {
+                       /* we could not read the stream */
+                       *error_r = i_stream_get_error(value.value_stream);
+                       ret = -1;
+               } else {
+                       *value_r = str->data;
+                       *value_len_r = str->used;
+                       ret = 1;
+               }
+               i_stream_unref(&value.value_stream);
+               return ret;
+       }
+
+       *value_r = value.value;
+       if (value.value != NULL)
+               *value_len_r = strlen(value.value);
+       else
+               *value_len_r = 0;
+       return 1;
+}
+
+static int lua_storage_mailbox_attribute_set(struct mailbox *box, const char *key,
+                                            const char *value, size_t value_len,
+                                            const char **error_r)
+{
+       struct mail_attribute_value attr_value;
+       enum mail_attribute_type attr_type;
+       int ret;
+
+       i_assert(value != NULL || value_len == 0);
+
+       if (str_begins(key, "/private/")) {
+               attr_type = MAIL_ATTRIBUTE_TYPE_PRIVATE;
+               key += 9;
+       } else if (str_begins(key, "/shared/")) {
+               attr_type = MAIL_ATTRIBUTE_TYPE_SHARED;
+               key += 8;
+       } else {
+               *error_r = "Invalid key prefix, must be /private/ or /shared/";
+               return -1;
+       }
+
+       struct mailbox_transaction_context *t =
+               mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_NO_NOTIFY, __func__);
+       i_zero(&attr_value);
+
+       if (value != NULL) {
+               /* use stream API to allow NULs in data */
+               attr_value.value_stream = i_stream_create_from_data(value, value_len);
+       }
+
+       ret = mailbox_attribute_set(t, attr_type, key, &attr_value);
+
+       if (ret < 0) {
+               *error_r = mailbox_get_last_error(box, NULL);
+               mailbox_transaction_rollback(&t);
+       } else {
+               mailbox_transaction_commit(&t);
+       }
+
+       if (attr_value.value_stream != NULL)
+               i_stream_unref(&attr_value.value_stream);
+
+       return ret;
+}
+
+static int lua_storage_mailbox_attribute_list(struct mailbox *box, const char *prefix,
+                                             ARRAY_TYPE(lua_storage_keyvalue) *items_r,
+                                             const char **error_r)
+{
+       const char *key, *orig_prefix = prefix;
+       enum mail_attribute_type attr_type;
+       int ret;
+
+       if (str_begins(prefix, "/private/")) {
+               attr_type = MAIL_ATTRIBUTE_TYPE_PRIVATE;
+               prefix += 9;
+       } else if (str_begins(prefix, "/shared/")) {
+               attr_type = MAIL_ATTRIBUTE_TYPE_SHARED;
+               prefix += 8;
+       } else {
+               *error_r = "Invalid key prefix, must be /private/ or /shared/";
+               return -1;
+       }
+
+       struct mailbox_attribute_iter *iter =
+                       mailbox_attribute_iter_init(box, attr_type, prefix);
+
+       ret = 0;
+       *error_r = NULL;
+       while((key = mailbox_attribute_iter_next(iter)) != NULL) {
+               struct lua_storage_keyvalue *item = array_append_space(items_r);
+               item->key = t_strdup_printf("%s%s", orig_prefix, key);
+               if (lua_storage_mailbox_attribute_get(box, item->key, &item->value,
+                                                     &item->value_len, error_r) < 0) {
+                       ret = -1;
+                       break;
+               }
+       }
+
+       if (mailbox_attribute_iter_deinit(&iter) < 0 || ret == -1) {
+               if (*error_r == NULL)
+                       *error_r = mailbox_get_last_error(box, NULL);
+               return -1;
+       }
+
+       return 0;
+}
+
 /** MAIL USER
  */
 
@@ -191,6 +353,218 @@ static int lua_storage_mail_user_unref(lua_State *L)
        return 0;
 }
 
+static const char *lua_storage_mail_user_metadata_key(const char *key)
+{
+       if (str_begins(key, "/private/")) {
+               return t_strdup_printf("/private/%s%s",
+                                      MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
+                                      key + 9);
+       } else if (str_begins(key, "/shared/")) {
+               return t_strdup_printf("/shared/%s%s",
+                                      MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
+                                      key + 8);
+       }
+       return NULL;
+}
+
+static int lua_storage_mail_user_metadata_get(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       if (lua_gettop(script->L) < 2)
+               return luaL_error(script->L, "expecting at least 1 parameter");
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+
+       const char *value, *error;
+       size_t value_len;
+       int ret, i, top = lua_gettop(script->L);
+
+       /* fetch INBOX, as user metadata is stored there */
+       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX",
+                                            MAILBOX_FLAG_READONLY);
+
+       if (mailbox_open(mbox) < 0) {
+               error = mailbox_get_last_error(mbox, NULL);
+               mailbox_free(&mbox);
+               return luaL_error(script->L, "Cannot open INBOX: %s", error);
+       }
+
+       ret = 0;
+       for(i = 2; i <= top; i++) {
+               /* reformat key */
+               const char *key = lua_tostring(script->L, i);
+
+               if (key == NULL) {
+                       ret = -1;
+                       error = t_strdup_printf("expected string at #%d", i);
+                       break;
+               }
+
+               if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
+                       ret = -1;
+                       error = "Invalid key prefix, must be "
+                               "/private/ or /shared/";
+                       break;
+               }
+
+               if ((ret = lua_storage_mailbox_attribute_get(mbox, key, &value,
+                                                            &value_len, &error)) < 0) {
+                       break;
+               } else if (ret == 0) {
+                       lua_pushnil(script->L);
+               } else {
+                       lua_pushlstring(script->L, value, value_len);
+               }
+       }
+
+       mailbox_free(&mbox);
+
+       if (ret < 0)
+               return luaL_error(script->L, "%s", error);
+
+       i_assert(i>=2);
+       return i-2;
+}
+
+static int
+lua_storage_mail_user_set_metadata_unset(struct dlua_script *script,
+                                        struct mail_user *user,
+                                        const char *key, const char *value,
+                                        size_t value_len)
+{
+       const char *error;
+       int ret;
+
+       /* reformat key */
+       if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
+               return luaL_error(script->L, "Invalid key prefix, must be "
+                                            "/private/ or /shared/");
+       }
+
+       /* fetch INBOX, as user metadata is stored there */
+       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX", 0);
+
+       if (mailbox_open(mbox) < 0) {
+               error = mailbox_get_last_error(mbox, NULL);
+               mailbox_free(&mbox);
+               return luaL_error(script->L,
+                                 "Cannot open INBOX: %s", error);
+       }
+
+       if ((ret = lua_storage_mailbox_attribute_set(mbox, key, value,
+            value_len, &error)) < 0) {
+               mailbox_free(&mbox);
+               return luaL_error(script->L,
+                                 "Cannot get attribute: %s", error);
+       }
+
+       mailbox_free(&mbox);
+       return 0;
+}
+
+static int lua_storage_mail_user_metadata_set(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 3);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *key = luaL_checkstring(script->L, 2);
+       const char *value;
+       size_t value_len;
+
+       value = lua_tolstring(script->L, 3, &value_len);
+
+       return lua_storage_mail_user_set_metadata_unset(script, user, key,
+                                                       value, value_len);
+}
+
+static int lua_storage_mail_user_metadata_unset(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *key = luaL_checkstring(script->L, 2);
+
+       return lua_storage_mail_user_set_metadata_unset(script, user, key, NULL, 0);
+}
+
+static int lua_storage_mail_user_metadata_list(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       if (lua_gettop(script->L) < 2)
+               return luaL_error(script->L, "expecting at least 1 parameter");
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const struct lua_storage_keyvalue *item;
+       const char *error;
+       ARRAY_TYPE(lua_storage_keyvalue) items;
+       int i, ret;
+
+       /* fetch INBOX, as user metadata is stored there */
+       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX", 0);
+
+       if (mailbox_open(mbox) < 0) {
+               error = mailbox_get_last_error(mbox, NULL);
+               mailbox_free(&mbox);
+               return luaL_error(script->L,
+                                 "Cannot open INBOX: %s", error);
+       }
+
+       T_BEGIN {
+               t_array_init(&items, 1);
+
+               ret = 0;
+               for(i = 2; i <= lua_gettop(script->L); i++) {
+                       const char *key = lua_tostring(script->L, i);
+
+                       if (key == NULL) {
+                               ret = -1;
+                               error = t_strdup_printf("expected string at #%d", i);
+                               break;
+                       }
+
+                       if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
+                               ret = -1;
+                               error = "Invalid key prefix, must be "
+                                       "/private/ or /shared/";
+                               break;
+                       }
+
+                       if (lua_storage_mailbox_attribute_list(mbox, key, &items,
+                                                              &error) < 0) {
+                               ret = -1;
+                               break;
+                       }
+               }
+
+               if (ret == 0) {
+                       lua_createtable(script->L, 0, array_count(&items));
+                       array_foreach(&items, item) {
+                               char *ptr;
+                               char *key = t_strdup_noconst(item->key);
+                               if ((ptr = strstr(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) != NULL) {
+                                       const char *endp = ptr+strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER);
+                                       memmove(ptr, endp, strlen(endp));
+                                       memset(ptr+strlen(endp), '\0', 1);
+                               }
+                               /* push value */
+                               lua_pushlstring(script->L, item->value,
+                                               item->value_len);
+                               /* set field */
+                               lua_setfield(script->L, -2, key);
+                       }
+               }
+       } T_END;
+
+       mailbox_free(&mbox);
+
+       if (ret == -1)
+               return luaL_error(script->L, "%s", error);
+
+       /* stack should have table with items */
+       return 1;
+}
+
 static luaL_Reg lua_storage_mail_user_methods[] = {
        { "__tostring", lua_storage_mail_user_tostring },
        { "__eq", lua_storage_mail_user_eq },
@@ -199,6 +573,10 @@ static luaL_Reg lua_storage_mail_user_methods[] = {
        { "plugin_getenv", lua_storage_mail_user_plugin_getenv },
        { "var_expand", lua_storage_mail_user_var_expand },
        { "mailbox", lua_storage_mail_user_mailbox_alloc },
+       { "metadata_get", lua_storage_mail_user_metadata_get },
+       { "metadata_set", lua_storage_mail_user_metadata_set },
+       { "metadata_unset", lua_storage_mail_user_metadata_unset },
+       { "metadata_list", lua_storage_mail_user_metadata_list },
        { NULL, NULL }
 };
 
@@ -442,6 +820,126 @@ static int lua_storage_mailbox_status(lua_State *L)
        return 1;
 }
 
+static int lua_storage_mailbox_metadata_get(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       if (lua_gettop(script->L) < 2)
+               return luaL_error(script->L, "expecting at least 1 parameter");
+       struct mailbox *mbox = lua_check_storage_mailbox(script, 1);
+       const char *value, *error;
+       size_t value_len;
+       int ret, i, top = lua_gettop(script->L);
+
+       ret = 0;
+       for(i = 2; i <= top; i++) {
+               const char *key = lua_tostring(script->L, i);
+               if (key == NULL) {
+                       ret = -1;
+                       error = t_strdup_printf("expected string at #%d", i);
+                       break;
+               }
+
+               if ((ret = lua_storage_mailbox_attribute_get(mbox, key, &value,
+                                                            &value_len, &error)) < 0) {
+                       break;
+               } else if (ret == 0) {
+                       lua_pushnil(script->L);
+               } else {
+                       lua_pushlstring(script->L, value, value_len);
+               }
+       }
+
+       if (ret < 0)
+               return luaL_error(script->L, "%s", error);
+
+       /* return number of pushed items */
+       i_assert(i>=2);
+       return i-2;
+}
+
+static int lua_storage_mailbox_metadata_set(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 3);
+       struct mailbox *mbox = lua_check_storage_mailbox(script, 1);
+       const char *key = luaL_checkstring(script->L, 2);
+       const char *value, *error;
+       size_t value_len;
+
+       value = lua_tolstring(script->L, 3, &value_len);
+
+       if (lua_storage_mailbox_attribute_set(mbox, key, value, value_len, &error) < 0)
+               return luaL_error(script->L,
+                                 t_strdup_printf("Cannot set attribute: %s", error));
+
+       return 0;
+}
+
+static int lua_storage_mailbox_metadata_unset(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       struct mailbox *mbox = lua_check_storage_mailbox(script, 1);
+       const char *key = luaL_checkstring(script->L, 2);
+       const char *error;
+
+       if (lua_storage_mailbox_attribute_set(mbox, key, NULL, 0,  &error) < 0)
+               return luaL_error(script->L,
+                                 t_strdup_printf("Cannot unset attribute: %s", error));
+
+       return 0;
+}
+
+static int lua_storage_mailbox_metadata_list(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       if (lua_gettop(script->L) < 2)
+               return luaL_error(script->L, "expecting at least 1 parameter");
+       struct mailbox *mbox = lua_check_storage_mailbox(script, 1);
+       const struct lua_storage_keyvalue *item;
+       const char *error;
+       ARRAY_TYPE(lua_storage_keyvalue) items;
+       int i, ret;
+
+       T_BEGIN {
+               t_array_init(&items, 1);
+
+               ret = 0;
+               for(i = 2; i <= lua_gettop(script->L); i++) {
+                       const char *key = lua_tostring(script->L, i);
+
+                       if (key == NULL) {
+                               ret = -1;
+                               error = t_strdup_printf("expected string at #%d", i);
+                               break;
+                       }
+
+                       if (lua_storage_mailbox_attribute_list(mbox, key, &items,
+                                                              &error) < 0) {
+                               ret = -1;
+                               break;
+                       }
+               }
+
+               if (ret == 0) {
+                       lua_createtable(script->L, 0, array_count(&items));
+                       array_foreach(&items, item) {
+                               /* push value */
+                               lua_pushlstring(script->L, item->value,
+                                               item->value_len);
+                               /* set field */
+                               lua_setfield(script->L, -2, item->key);
+                       }
+               }
+       } T_END;
+
+       if (ret == -1)
+               return luaL_error(script->L, "%s", error);
+
+       /* stack should have table with items */
+       return 1;
+}
+
 static luaL_Reg lua_storage_mailbox_methods[] = {
        { "__tostring", lua_storage_mailbox_tostring },
        { "__eq", lua_storage_mailbox_eq },
@@ -452,6 +950,10 @@ static luaL_Reg lua_storage_mailbox_methods[] = {
        { "open", lua_storage_mailbox_open },
        { "close", lua_storage_mailbox_close },
        { "sync", lua_storage_mailbox_sync },
+       { "metadata_get", lua_storage_mailbox_metadata_get },
+       { "metadata_set", lua_storage_mailbox_metadata_set },
+       { "metadata_unset", lua_storage_mailbox_metadata_unset },
+       { "metadata_list", lua_storage_mailbox_metadata_list },
        { NULL, NULL }
 };
 
@@ -630,6 +1132,13 @@ static struct dlua_table_values lua_storage_mail_storage_flags[] = {
        DLUA_TABLE_ENUM(MAILBOX_SYNC_FLAG_EXPUNGE),
        DLUA_TABLE_ENUM(MAILBOX_SYNC_FLAG_FORCE_RESYNC),
 
+       DLUA_TABLE_STRING("MAILBOX_ATTRIBUTE_PREFIX_DOVECOT",
+                         MAILBOX_ATTRIBUTE_PREFIX_DOVECOT),
+       DLUA_TABLE_STRING("MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT",
+                         MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT),
+       DLUA_TABLE_STRING("MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER",
+                         MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER),
+
        DLUA_TABLE_END
 };