summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-subscriptions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-subscriptions.c')
-rw-r--r--src/lib-storage/list/mailbox-list-subscriptions.c318
1 files changed, 318 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-subscriptions.c b/src/lib-storage/list/mailbox-list-subscriptions.c
new file mode 100644
index 0000000..2e27c96
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-subscriptions.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "unichar.h"
+#include "imap-match.h"
+#include "subscription-file.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-subscriptions.h"
+
+#include <sys/stat.h>
+
+struct subscriptions_mailbox_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+ struct mailbox_tree_context *tree;
+ struct mailbox_tree_iterate_context *iter;
+ struct mailbox_info info;
+};
+
+static int
+mailbox_list_subscription_fill_one(struct mailbox_list *list,
+ struct mailbox_list *src_list,
+ const char *name)
+{
+ struct mail_namespace *ns, *default_ns = list->ns;
+ struct mail_namespace *namespaces = default_ns->user->namespaces;
+ struct mailbox_node *node;
+ const char *vname, *ns_name, *error;
+ size_t len;
+ bool created;
+
+ /* default_ns is whatever namespace we're currently listing.
+ if we have e.g. prefix="" and prefix=pub/ namespaces with
+ pub/ namespace having subscriptions=no, we want to:
+
+ 1) when listing "" namespace we want to skip over any names
+ that begin with pub/. */
+ if (src_list->ns->prefix_len == 0)
+ ns_name = name;
+ else {
+ /* we could have two-level namespace: ns/ns2/ */
+ ns_name = t_strconcat(src_list->ns->prefix, name, NULL);
+ }
+ ns = mail_namespace_find_unsubscribable(namespaces, ns_name);
+ if (ns != NULL && ns != default_ns) {
+ if (ns->prefix_len > 0)
+ return 0;
+ /* prefix="" namespace=no : catching this is basically the
+ same as not finding any namespace. */
+ ns = NULL;
+ }
+
+ /* 2) when listing pub/ namespace, skip over entries that don't
+ begin with pub/. */
+ if (ns == NULL &&
+ (default_ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0)
+ return 0;
+
+ /* When listing shared namespace's subscriptions, we need to
+ autocreate all the visible child namespaces. their subscriptions
+ are listed later. */
+ if (ns != NULL && mail_namespace_is_shared_user_root(ns)) {
+ /* we'll need to get the namespace autocreated.
+ one easy way is to just ask to join a reference and
+ pattern */
+ (void)mailbox_list_join_refpattern(ns->list, ns_name, "");
+ }
+
+ /* When listing pub/ namespace, skip over the namespace
+ prefix in the name. the rest of the name is storage_name. */
+ if (ns == NULL)
+ ns = default_ns;
+ else if (strncmp(ns_name, ns->prefix, ns->prefix_len) == 0) {
+ ns_name += ns->prefix_len;
+ name = ns_name;
+ } else {
+ /* "pub" entry - this shouldn't be possible normally, because
+ it should be saved as "pub/", but handle it anyway */
+ i_assert(strncmp(ns_name, ns->prefix, ns->prefix_len-1) == 0 &&
+ ns_name[ns->prefix_len-1] == '\0');
+ name = "";
+ /* ns_name = ""; */
+ }
+
+ len = strlen(name);
+ if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) {
+ /* entry ends with hierarchy separator, remove it.
+ this exists mainly for backwards compatibility with old
+ Dovecot versions and non-Dovecot software that added them */
+ name = t_strndup(name, len-1);
+ }
+
+ if (!mailbox_list_is_valid_name(list, name, &error)) {
+ /* we'll only get into trouble if we show this */
+ return -1;
+ } else {
+ vname = mailbox_list_get_vname(list, name);
+ if (!uni_utf8_str_is_valid(vname))
+ return -1;
+ node = mailbox_tree_get(list->subscriptions, vname, &created);
+ node->flags = MAILBOX_SUBSCRIBED;
+ }
+ return 0;
+}
+
+int mailbox_list_subscriptions_refresh(struct mailbox_list *src_list,
+ struct mailbox_list *dest_list)
+{
+ struct subsfile_list_context *subsfile_ctx;
+ struct stat st;
+ enum mailbox_list_path_type type;
+ const char *path, *name;
+ char sep;
+ int ret;
+
+ /* src_list is subscriptions=yes, dest_list is subscriptions=no
+ (or the same as src_list) */
+ i_assert((src_list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0);
+
+ if (dest_list->subscriptions == NULL) {
+ sep = mail_namespace_get_sep(src_list->ns);
+ dest_list->subscriptions = mailbox_tree_init(sep);
+ }
+
+ type = src_list->set.control_dir != NULL ?
+ MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR;
+ if (!mailbox_list_get_root_path(src_list, type, &path) ||
+ src_list->set.subscription_fname == NULL) {
+ /* no subscriptions (e.g. pop3c) */
+ return 0;
+ }
+ path = t_strconcat(path, "/", src_list->set.subscription_fname, NULL);
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ /* no subscriptions */
+ mailbox_tree_clear(dest_list->subscriptions);
+ dest_list->subscriptions_mtime = 0;
+ return 0;
+ }
+ mailbox_list_set_critical(dest_list, "stat(%s) failed: %m",
+ path);
+ return -1;
+ }
+ if (st.st_mtime == dest_list->subscriptions_mtime &&
+ st.st_mtime < dest_list->subscriptions_read_time-1) {
+ /* we're up to date */
+ return 0;
+ }
+
+ mailbox_tree_clear(dest_list->subscriptions);
+ dest_list->subscriptions_read_time = ioloop_time;
+
+ subsfile_ctx = subsfile_list_init(dest_list, path);
+ if (subsfile_list_fstat(subsfile_ctx, &st) == 0)
+ dest_list->subscriptions_mtime = st.st_mtime;
+ while ((name = subsfile_list_next(subsfile_ctx)) != NULL) T_BEGIN {
+ T_BEGIN {
+ ret = mailbox_list_subscription_fill_one(dest_list,
+ src_list, name);
+ } T_END;
+ if (ret < 0) {
+ e_warning(dest_list->ns->user->event,
+ "Subscriptions file %s: "
+ "Removing invalid entry: %s",
+ path, name);
+ (void)subsfile_set_subscribed(src_list, path,
+ mailbox_list_get_temp_prefix(src_list),
+ name, FALSE);
+
+ }
+ } T_END;
+
+ if (subsfile_list_deinit(&subsfile_ctx) < 0) {
+ dest_list->subscriptions_mtime = (time_t)-1;
+ return -1;
+ }
+ return 0;
+}
+
+void mailbox_list_set_subscription_flags(struct mailbox_list *list,
+ const char *vname,
+ enum mailbox_info_flags *flags)
+{
+ struct mailbox_node *node;
+
+ *flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+
+ node = mailbox_tree_lookup(list->subscriptions, vname);
+ if (node != NULL) {
+ *flags |= node->flags & MAILBOX_SUBSCRIBED;
+
+ /* the only reason why node might have a child is if one of
+ them is subscribed */
+ if (node->children != NULL)
+ *flags |= MAILBOX_CHILD_SUBSCRIBED;
+ }
+}
+
+void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
+ struct mailbox_tree_context *tree,
+ bool default_nonexistent)
+{
+ struct mailbox_list_iter_update_context update_ctx;
+ struct mailbox_tree_iterate_context *iter;
+ const char *name;
+
+ i_zero(&update_ctx);
+ update_ctx.iter_ctx = ctx;
+ update_ctx.tree_ctx = tree;
+ update_ctx.glob = ctx->glob;
+ update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
+ if (default_nonexistent)
+ update_ctx.leaf_flags |= MAILBOX_NONEXISTENT;
+ update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
+ update_ctx.match_parents =
+ (ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
+
+ iter = mailbox_tree_iterate_init(ctx->list->subscriptions, NULL,
+ MAILBOX_SUBSCRIBED);
+ while (mailbox_tree_iterate_next(iter, &name) != NULL)
+ mailbox_list_iter_update(&update_ctx, name);
+ mailbox_tree_iterate_deinit(&iter);
+}
+
+struct mailbox_list_iterate_context *
+mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct subscriptions_mailbox_list_iterate_context *ctx;
+ pool_t pool;
+ char sep = mail_namespace_get_sep(list->ns);
+
+ pool = pool_alloconly_create("mailbox list subscriptions iter", 1024);
+ ctx = p_new(pool, struct subscriptions_mailbox_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = list;
+ ctx->ctx.flags = flags;
+ ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, sep);
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+
+ ctx->tree = mailbox_tree_init(sep);
+ mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree, FALSE);
+
+ ctx->info.ns = list->ns;
+ /* the tree usually has only those entries we want to iterate through,
+ but there are also non-matching root entries (e.g. "LSUB foo/%" will
+ include the "foo"), which we'll drop with MAILBOX_MATCHED. */
+ ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, MAILBOX_MATCHED);
+ return &ctx->ctx;
+}
+
+const struct mailbox_info *
+mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct subscriptions_mailbox_list_iterate_context *ctx =
+ (struct subscriptions_mailbox_list_iterate_context *)_ctx;
+ struct mailbox_list *list = _ctx->list;
+ struct mailbox_node *node;
+ enum mailbox_info_flags subs_flags;
+ const char *vname, *storage_name, *error;
+ int ret;
+
+ node = mailbox_tree_iterate_next(ctx->iter, &vname);
+ if (node == NULL)
+ return mailbox_list_iter_default_next(_ctx);
+
+ ctx->info.vname = vname;
+ subs_flags = node->flags & (MAILBOX_SUBSCRIBED |
+ MAILBOX_CHILD_SUBSCRIBED);
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
+ (_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) {
+ /* don't care about flags, just return it */
+ ctx->info.flags = subs_flags;
+ return &ctx->info;
+ }
+
+ storage_name = mailbox_list_get_storage_name(list, vname);
+ if (!mailbox_list_is_valid_name(list, storage_name, &error)) {
+ /* broken entry in subscriptions file */
+ ctx->info.flags = MAILBOX_NONEXISTENT;
+ } else if (mailbox_list_mailbox(list, storage_name,
+ &ctx->info.flags) < 0) {
+ ctx->info.flags = 0;
+ _ctx->failed = TRUE;
+ } else if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 &&
+ (ctx->info.flags & (MAILBOX_CHILDREN |
+ MAILBOX_NOCHILDREN)) == 0) {
+ ret = mailbox_has_children(list, storage_name);
+ if (ret < 0)
+ _ctx->failed = TRUE;
+ else if (ret == 0)
+ ctx->info.flags |= MAILBOX_NOCHILDREN;
+ else
+ ctx->info.flags |= MAILBOX_CHILDREN;
+
+ }
+
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+ ctx->info.flags |=
+ node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+ return &ctx->info;
+}
+
+int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct subscriptions_mailbox_list_iterate_context *ctx =
+ (struct subscriptions_mailbox_list_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ mailbox_tree_iterate_deinit(&ctx->iter);
+ mailbox_tree_deinit(&ctx->tree);
+ pool_unref(&_ctx->pool);
+ return ret;
+}