#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
*/
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 },
{ "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 }
};
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 },
{ "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 }
};
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
};