diff options
Diffstat (limited to 'src/lib-storage/list/mailbox-list-iter.c')
-rw-r--r-- | src/lib-storage/list/mailbox-list-iter.c | 1168 |
1 files changed, 1168 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-iter.c b/src/lib-storage/list/mailbox-list-iter.c new file mode 100644 index 0000000..4d3d135 --- /dev/null +++ b/src/lib-storage/list/mailbox-list-iter.c @@ -0,0 +1,1168 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "imap-match.h" +#include "mail-storage.h" +#include "mailbox-tree.h" +#include "mailbox-list-subscriptions.h" +#include "mailbox-list-private.h" +#include "mailbox-list-iter-private.h" + +enum autocreate_match_result { + /* list contains the mailbox */ + AUTOCREATE_MATCH_RESULT_YES = 0x01, + /* list contains children of the mailbox */ + AUTOCREATE_MATCH_RESULT_CHILDREN = 0x02, + /* list contains parents of the mailbox */ + AUTOCREATE_MATCH_RESULT_PARENT = 0x04 +}; + +struct ns_list_iterate_context { + struct mailbox_list_iterate_context ctx; + struct mailbox_list_iterate_context *backend_ctx; + struct mail_namespace *namespaces, *cur_ns; + struct mailbox_list *error_list; + pool_t pool; + const char **patterns, **patterns_ns_match; + enum mail_namespace_type type_mask; + + struct mailbox_info ns_info; + struct mailbox_info inbox_info; + const struct mailbox_info *pending_backend_info; + + bool cur_ns_prefix_sent:1; + bool inbox_list:1; + bool inbox_listed:1; + bool inbox_seen:1; +}; + +static void mailbox_list_ns_iter_failed(struct ns_list_iterate_context *ctx); +static bool ns_match_next(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, const char *pattern); +static int mailbox_list_match_anything(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, + const char *prefix); + +static struct mailbox_list_iterate_context mailbox_list_iter_failed; + +struct mailbox_list_iterate_context * +mailbox_list_iter_init(struct mailbox_list *list, const char *pattern, + enum mailbox_list_iter_flags flags) +{ + const char *patterns[2]; + + patterns[0] = pattern; + patterns[1] = NULL; + return mailbox_list_iter_init_multiple(list, patterns, flags); +} + +int mailbox_list_iter_subscriptions_refresh(struct mailbox_list *list) +{ + struct mail_namespace *ns = list->ns; + + if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0) { + /* no subscriptions in this namespace. find where they are. */ + ns = mail_namespace_find_subscribable(ns->user->namespaces, + ns->prefix); + if (ns == NULL) { + /* no subscriptions. avoid crashes by initializing + a subscriptions tree. */ + if (list->subscriptions == NULL) { + char sep = mail_namespace_get_sep(list->ns); + list->subscriptions = mailbox_tree_init(sep); + } + return 0; + } + } + return ns->list->v.subscriptions_refresh(ns->list, list); +} + +static struct mailbox_settings * +mailbox_settings_add_ns_prefix(pool_t pool, struct mail_namespace *ns, + struct mailbox_settings *in_set) +{ + struct mailbox_settings *out_set; + + if (ns->prefix_len == 0 || strcasecmp(in_set->name, "INBOX") == 0) + return in_set; + + out_set = p_new(pool, struct mailbox_settings, 1); + *out_set = *in_set; + if (*in_set->name == '\0') { + /* namespace prefix itself */ + out_set->name = p_strndup(pool, ns->prefix, ns->prefix_len-1); + } else { + out_set->name = + p_strconcat(pool, ns->prefix, in_set->name, NULL); + } + return out_set; +} + +static void +mailbox_list_iter_init_autocreate(struct mailbox_list_iterate_context *ctx) +{ + struct mail_namespace *ns = ctx->list->ns; + struct mailbox_list_autocreate_iterate_context *actx; + struct mailbox_settings *const *box_sets, *set; + struct autocreate_box *autobox; + unsigned int i, count; + + if (!array_is_created(&ns->set->mailboxes)) + return; + box_sets = array_get(&ns->set->mailboxes, &count); + if (count == 0) + return; + + actx = p_new(ctx->pool, struct mailbox_list_autocreate_iterate_context, 1); + ctx->autocreate_ctx = actx; + hash_table_create(&actx->duplicate_vnames, ctx->pool, 0, + str_hash, strcmp); + + /* build the list of mailboxes we need to consider as existing */ + p_array_init(&actx->boxes, ctx->pool, 16); + p_array_init(&actx->box_sets, ctx->pool, 16); + p_array_init(&actx->all_ns_box_sets, ctx->pool, 16); + for (i = 0; i < count; i++) { + if (strcmp(box_sets[i]->autocreate, MAILBOX_SET_AUTO_NO) == 0) + continue; + + set = mailbox_settings_add_ns_prefix(ctx->pool, + ns, box_sets[i]); + + /* autocreate mailbox belongs to listed namespace */ + array_push_back(&actx->all_ns_box_sets, &set); + if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 || + strcmp(set->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) == 0) { + array_push_back(&actx->box_sets, &set); + autobox = array_append_space(&actx->boxes); + autobox->name = set->name; + autobox->set = set; + if (strcasecmp(autobox->name, "INBOX") == 0) { + /* make sure duplicate INBOX/Inbox/etc. + won't get created */ + autobox->name = "INBOX"; + } + } + } +} + +struct mailbox_list_iterate_context * +mailbox_list_iter_init_multiple(struct mailbox_list *list, + const char *const *patterns, + enum mailbox_list_iter_flags flags) +{ + struct mailbox_list_iterate_context *ctx; + + i_assert(*patterns != NULL); + + if ((flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED | + MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0) { + if (mailbox_list_iter_subscriptions_refresh(list) < 0) + return &mailbox_list_iter_failed; + } + + ctx = list->v.iter_init(list, patterns, flags); + if ((flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0) + mailbox_list_iter_init_autocreate(ctx); + return ctx; +} + +static bool +ns_match_simple(struct ns_list_iterate_context *ctx, struct mail_namespace *ns) +{ + if ((ctx->type_mask & ns->type) == 0) + return FALSE; + + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) { + if (ns->alias_for != NULL) + return FALSE; + } + return TRUE; +} + +static bool +ns_is_match_within_ns(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, const char *prefix_without_sep, + const char *pattern, enum imap_match_result result) +{ + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_STAR_WITHIN_NS) == 0) { + switch (result) { + case IMAP_MATCH_YES: + case IMAP_MATCH_CHILDREN: + return TRUE; + case IMAP_MATCH_NO: + case IMAP_MATCH_PARENT: + break; + } + return FALSE; + } + + switch (result) { + case IMAP_MATCH_YES: + /* allow matching prefix only when it's done without + wildcards */ + if (strcmp(prefix_without_sep, pattern) == 0) + return TRUE; + break; + case IMAP_MATCH_CHILDREN: { + /* allow this only if there isn't another namespace + with longer prefix that matches this pattern + (namespaces are sorted by prefix length) */ + struct mail_namespace *tmp; + + T_BEGIN { + for (tmp = ns->next; tmp != NULL; tmp = tmp->next) { + if (ns_match_simple(ctx, tmp) && + ns_match_next(ctx, tmp, pattern)) + break; + } + } T_END; + if (tmp == NULL) + return TRUE; + break; + } + case IMAP_MATCH_NO: + case IMAP_MATCH_PARENT: + break; + } + return FALSE; +} + +static bool list_pattern_has_wildcards(const char *pattern) +{ + for (; *pattern != '\0'; pattern++) { + if (*pattern == '%' || *pattern == '*') + return TRUE; + } + return FALSE; +} + +static bool ns_match_next(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, const char *pattern) +{ + struct imap_match_glob *glob; + enum imap_match_result result; + const char *prefix_without_sep; + size_t len; + + len = ns->prefix_len; + if (len > 0 && ns->prefix[len-1] == mail_namespace_get_sep(ns)) + len--; + + if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX | + NAMESPACE_FLAG_LIST_CHILDREN)) == 0) { + /* non-listable namespace matches only with exact prefix */ + if (strncmp(ns->prefix, pattern, ns->prefix_len) != 0) + return FALSE; + /* with prefix="", list=no we don't want to show anything, + except when the client explicitly lists a mailbox without + wildcards (e.g. LIST "" mailbox). this is mainly useful + for working around client bugs (and supporting a specific + IMAP client behavior that's not exactly buggy but not very + good IMAP behavior either). */ + if (ns->prefix_len == 0 && list_pattern_has_wildcards(pattern)) + return FALSE; + } + + prefix_without_sep = t_strndup(ns->prefix, len); + if (*prefix_without_sep == '\0') + result = IMAP_MATCH_CHILDREN; + else { + glob = imap_match_init(pool_datastack_create(), pattern, + TRUE, mail_namespace_get_sep(ns)); + result = imap_match(glob, prefix_without_sep); + } + + return ns_is_match_within_ns(ctx, ns, prefix_without_sep, + pattern, result); +} + +static bool +mailbox_list_ns_match_patterns(struct ns_list_iterate_context *ctx) +{ + struct mail_namespace *ns = ctx->cur_ns; + unsigned int i; + + if (!ns_match_simple(ctx, ns)) + return FALSE; + + /* filter out namespaces whose prefix doesn't match. this same code + handles both with and without STAR_WITHIN_NS, so the "without" case + is slower than necessary, but this shouldn't matter much */ + T_BEGIN { + for (i = 0; ctx->patterns_ns_match[i] != NULL; i++) { + if (ns_match_next(ctx, ns, ctx->patterns_ns_match[i])) + break; + } + } T_END; + + return ctx->patterns_ns_match[i] != NULL; +} + +static bool +iter_next_try_prefix_pattern(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, const char *pattern) +{ + struct imap_match_glob *glob; + enum imap_match_result result; + const char *prefix_without_sep; + + i_assert(ns->prefix_len > 0); + + if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX | + NAMESPACE_FLAG_LIST_CHILDREN)) == 0) { + /* non-listable namespace matches only with exact prefix */ + if (strncmp(ns->prefix, pattern, ns->prefix_len) != 0) + return FALSE; + } + + prefix_without_sep = t_strndup(ns->prefix, ns->prefix_len-1); + glob = imap_match_init(pool_datastack_create(), pattern, + TRUE, mail_namespace_get_sep(ns)); + result = imap_match(glob, prefix_without_sep); + return result == IMAP_MATCH_YES && + ns_is_match_within_ns(ctx, ns, prefix_without_sep, + pattern, result); +} + +static bool +mailbox_list_ns_prefix_match(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns) +{ + unsigned int i; + bool ret = FALSE; + + for (i = 0; ctx->patterns_ns_match[i] != NULL; i++) { + T_BEGIN { + ret = iter_next_try_prefix_pattern(ctx, ns, + ctx->patterns_ns_match[i]); + } T_END; + if (ret) + break; + } + return ret; +} + +static int +ns_prefix_is_visible(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns) +{ + int ret; + + if ((ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0) + return 1; + if ((ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0) { + if ((ret = mailbox_list_match_anything(ctx, ns, ns->prefix)) != 0) + return ret; + } + return 0; +} + +static int +ns_prefix_has_visible_child_namespace(struct ns_list_iterate_context *ctx, + const char *prefix) +{ + struct mail_namespace *ns; + size_t prefix_len = strlen(prefix); + int ret; + + for (ns = ctx->namespaces; ns != NULL; ns = ns->next) { + if (ns->prefix_len > prefix_len && + strncmp(ns->prefix, prefix, prefix_len) == 0) { + ret = ns_prefix_is_visible(ctx, ns); + if (ret != 0) + return ret; + } + } + return 0; +} + +static bool +mailbox_ns_prefix_is_shared_inbox(struct mail_namespace *ns) +{ + return ns->type == MAIL_NAMESPACE_TYPE_SHARED && + (ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 && + !ns->list->mail_set->mail_shared_explicit_inbox; +} + +static bool +mailbox_is_shared_inbox(struct mail_namespace *ns, const char *vname) +{ + return mailbox_ns_prefix_is_shared_inbox(ns) && + strncmp(ns->prefix, vname, ns->prefix_len-1) == 0 && + vname[ns->prefix_len-1] == '\0'; +} + +static int +mailbox_list_match_anything(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, const char *prefix) +{ + enum mailbox_list_iter_flags list_flags = + MAILBOX_LIST_ITER_RETURN_NO_FLAGS; + struct mailbox_list_iterate_context *list_iter; + const struct mailbox_info *info; + const char *pattern; + int ret; + + if ((ret = ns_prefix_has_visible_child_namespace(ctx, prefix)) != 0) + return ret; + + pattern = t_strconcat(prefix, "%", NULL); + list_iter = mailbox_list_iter_init(ns->list, pattern, list_flags); + info = mailbox_list_iter_next(list_iter); + if (info != NULL && mailbox_ns_prefix_is_shared_inbox(ns) && + mailbox_is_shared_inbox(ns, info->vname)) { + /* we don't want to see this, try the next one */ + info = mailbox_list_iter_next(list_iter); + } + ret = info != NULL ? 1 : 0; + if (mailbox_list_iter_deinit(&list_iter) < 0) { + if (ret == 0) + ret = -1; + } + return ret; +} + +static bool +mailbox_ns_prefix_check_selection_criteria(struct ns_list_iterate_context *ctx) +{ + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) { + if ((ctx->ns_info.flags & MAILBOX_SUBSCRIBED) != 0) + return TRUE; + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0 && + (ctx->ns_info.flags & MAILBOX_CHILD_SUBSCRIBED) != 0) + return TRUE; + return FALSE; + } + return TRUE; +} + +static bool +mailbox_list_ns_prefix_return(struct ns_list_iterate_context *ctx, + struct mail_namespace *ns, bool has_children) +{ + struct mailbox *box; + enum mailbox_existence existence; + int ret; + + if (strncasecmp(ns->prefix, "INBOX", 5) == 0 && + ns->prefix[5] == mail_namespace_get_sep(ns)) { + /* prefix=INBOX/ (or prefix=INBOX/something/) namespace exists. + so we can create children to INBOX. */ + ctx->inbox_info.flags &= ENUM_NEGATE(MAILBOX_NOINFERIORS); + } + + if (ns->prefix_len == 0 || !mailbox_list_ns_prefix_match(ctx, ns)) + return FALSE; + + i_zero(&ctx->ns_info); + ctx->ns_info.ns = ns; + ctx->ns_info.vname = p_strndup(ctx->pool, ns->prefix, + ns->prefix_len-1); + if (ns->special_use_mailboxes) + ctx->ns_info.flags |= MAILBOX_CHILD_SPECIALUSE; + + if (strcasecmp(ctx->ns_info.vname, "INBOX") == 0) { + i_assert(!ctx->inbox_listed); + ctx->inbox_listed = TRUE; + ctx->ns_info.flags |= ctx->inbox_info.flags | MAILBOX_SELECT; + } + + if ((ctx->ctx.flags & (MAILBOX_LIST_ITER_RETURN_SUBSCRIBED | + MAILBOX_LIST_ITER_SELECT_SUBSCRIBED)) != 0) { + /* Refresh subscriptions first, this won't cause a duplicate + call later on as this is only called when the namespace's + children definitely don't match */ + if (mailbox_list_iter_subscriptions_refresh(ns->list) < 0) { + mailbox_list_ns_iter_failed(ctx); + return FALSE; + } + mailbox_list_set_subscription_flags(ns->list, + ctx->ns_info.vname, + &ctx->ns_info.flags); + } + if (!mailbox_ns_prefix_check_selection_criteria(ctx)) + return FALSE; + + /* see if the namespace has children */ + if (has_children) + ctx->ns_info.flags |= MAILBOX_CHILDREN; + else if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 || + (ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0) { + /* need to check this explicitly */ + if ((ret = mailbox_list_match_anything(ctx, ns, ns->prefix)) > 0) + ctx->ns_info.flags |= MAILBOX_CHILDREN; + else if (ret == 0) { + if ((ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 && + !mailbox_ns_prefix_is_shared_inbox(ns)) { + /* no children -> not visible */ + return FALSE; + } + ctx->ns_info.flags |= MAILBOX_NOCHILDREN; + } + } + + if ((ctx->ns_info.flags & MAILBOX_SELECT) == 0) { + /* see if namespace prefix is selectable */ + box = mailbox_alloc(ns->list, ctx->ns_info.vname, 0); + if (mailbox_exists(box, TRUE, &existence) == 0 && + existence == MAILBOX_EXISTENCE_SELECT) + ctx->ns_info.flags |= MAILBOX_SELECT; + else + ctx->ns_info.flags |= MAILBOX_NONEXISTENT; + mailbox_free(&box); + } + return TRUE; +} + +static void inbox_set_children_flags(struct ns_list_iterate_context *ctx) +{ + struct mail_namespace *ns; + const char *prefix; + int ret; + + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0) + return; + if ((ctx->inbox_info.flags & (MAILBOX_CHILDREN | MAILBOX_NOINFERIORS | + MAILBOX_NOCHILDREN)) != 0) + return; + + ns = mail_namespace_find_prefix(ctx->namespaces, ""); + if (ns == NULL || (ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) { + /* prefix="" namespace doesn't exist, and neither does + anything beginning with prefix=INBOX/ (we checked this + earlier). there's no way to create children for INBOX. */ + ctx->inbox_info.flags |= MAILBOX_NOINFERIORS; + return; + } + + /* INBOX namespace doesn't exist and we didn't see any children listed + for INBOX. this could be because there truly aren't any children, + or that the list patterns just didn't match them. */ + prefix = t_strdup_printf("INBOX%c", + mail_namespace_get_sep(ctx->inbox_info.ns)); + ret = mailbox_list_match_anything(ctx, ctx->inbox_info.ns, prefix); + if (ret > 0) + ctx->inbox_info.flags |= MAILBOX_CHILDREN; + else if (ret == 0) + ctx->inbox_info.flags |= MAILBOX_NOCHILDREN; +} + +static void mailbox_list_ns_iter_failed(struct ns_list_iterate_context *ctx) +{ + enum mail_error error; + const char *errstr; + + if (ctx->cur_ns->list != ctx->error_list) { + errstr = mailbox_list_get_last_error(ctx->cur_ns->list, &error); + mailbox_list_set_error(ctx->error_list, error, errstr); + } + ctx->ctx.failed = TRUE; +} + +static bool +mailbox_list_ns_iter_try_next(struct mailbox_list_iterate_context *_ctx, + const struct mailbox_info **info_r) +{ + struct ns_list_iterate_context *ctx = + (struct ns_list_iterate_context *)_ctx; + struct mail_namespace *ns; + const struct mailbox_info *info; + bool has_children; + + if (ctx->cur_ns == NULL) { + if (!ctx->inbox_listed && ctx->inbox_list && !_ctx->failed && + ((_ctx->flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0 || + ctx->inbox_seen)) { + /* send delayed INBOX reply */ + ctx->inbox_listed = TRUE; + inbox_set_children_flags(ctx); + *info_r = &ctx->inbox_info; + return TRUE; + } + *info_r = NULL; + return TRUE; + } + + if (ctx->backend_ctx == NULL) { + i_assert(ctx->pending_backend_info == NULL); + if (!mailbox_list_ns_match_patterns(ctx)) { + /* namespace's children don't match the patterns, + but the namespace prefix itself might */ + ns = ctx->cur_ns; + ctx->cur_ns = ctx->cur_ns->next; + if (mailbox_list_ns_prefix_return(ctx, ns, FALSE)) { + *info_r = &ctx->ns_info; + return TRUE; + } + return FALSE; + } + /* start listing this namespace's mailboxes */ + ctx->backend_ctx = + mailbox_list_iter_init_multiple(ctx->cur_ns->list, + ctx->patterns, + _ctx->flags); + ctx->cur_ns_prefix_sent = FALSE; + } + if (ctx->pending_backend_info == NULL) + info = mailbox_list_iter_next(ctx->backend_ctx); + else { + info = ctx->pending_backend_info; + ctx->pending_backend_info = NULL; + } + if (!ctx->cur_ns_prefix_sent) { + /* delayed sending of namespace prefix */ + ctx->cur_ns_prefix_sent = TRUE; + has_children = info != NULL && + !mailbox_is_shared_inbox(info->ns, info->vname); + if (mailbox_list_ns_prefix_return(ctx, ctx->cur_ns, + has_children)) { + ctx->pending_backend_info = info; + *info_r = &ctx->ns_info; + return TRUE; + } + } + if (info != NULL) { + if (strcasecmp(info->vname, "INBOX") == 0 && ctx->inbox_list) { + /* delay sending INBOX reply. we already saved its + flags at init stage, except for \Noinferiors + and subscription states */ + ctx->inbox_seen = TRUE; + ctx->inbox_info.flags |= + (info->flags & (MAILBOX_NOINFERIORS | + MAILBOX_SUBSCRIBED | + MAILBOX_CHILD_SUBSCRIBED)); + return FALSE; + } + if (strncasecmp(info->vname, "INBOX", 5) == 0 && + info->vname[5] == mail_namespace_get_sep(info->ns)) { + /* we know now that INBOX has children */ + ctx->inbox_info.flags |= MAILBOX_CHILDREN; + ctx->inbox_info.flags &= ENUM_NEGATE(MAILBOX_NOINFERIORS); + } + if (info->ns->prefix_len > 0 && + strncmp(info->vname, info->ns->prefix, + info->ns->prefix_len-1) == 0 && + info->vname[info->ns->prefix_len-1] == '\0') { + /* this is an entry for namespace prefix, which we + already returned. (e.g. shared/$user/INBOX entry + returned as shared/$user, or when listing + subscribed namespace prefix). */ + return FALSE; + } + + *info_r = info; + return TRUE; + } + + /* finished with this namespace */ + if (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0) + mailbox_list_ns_iter_failed(ctx); + ctx->cur_ns = ctx->cur_ns->next; + return FALSE; +} + +static const struct mailbox_info * +mailbox_list_ns_iter_next(struct mailbox_list_iterate_context *_ctx) +{ + const struct mailbox_info *info = NULL; + + while (!mailbox_list_ns_iter_try_next(_ctx, &info)) ; + return info; +} + +static int +mailbox_list_ns_iter_deinit(struct mailbox_list_iterate_context *_ctx) +{ + struct ns_list_iterate_context *ctx = + (struct ns_list_iterate_context *)_ctx; + int ret; + + if (ctx->backend_ctx != NULL) { + if (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0) + mailbox_list_ns_iter_failed(ctx); + } + ret = _ctx->failed ? -1 : 0; + pool_unref(&ctx->pool); + return ret; +} + +static const char ** +dup_patterns_without_stars(pool_t pool, const char *const *patterns, + unsigned int count) +{ + const char **dup; + unsigned int i; + + dup = p_new(pool, const char *, count + 1); + for (i = 0; i < count; i++) { + char *p = p_strdup(pool, patterns[i]); + dup[i] = p; + + for (; *p != '\0'; p++) { + if (*p == '*') + *p = '%'; + } + } + return dup; +} + +static bool +patterns_match_inbox(struct mail_namespace *namespaces, + const char *const *patterns) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(namespaces); + struct imap_match_glob *glob; + + glob = imap_match_init_multiple(pool_datastack_create(), patterns, + TRUE, mail_namespace_get_sep(ns)); + return imap_match(glob, "INBOX") == IMAP_MATCH_YES; +} + +static int inbox_info_init(struct ns_list_iterate_context *ctx, + struct mail_namespace *namespaces) +{ + enum mailbox_info_flags flags; + int ret; + + ctx->inbox_info.vname = "INBOX"; + ctx->inbox_info.ns = mail_namespace_find_inbox(namespaces); + i_assert(ctx->inbox_info.ns != NULL); + + if ((ret = mailbox_list_mailbox(ctx->inbox_info.ns->list, "INBOX", &flags)) > 0) + ctx->inbox_info.flags = flags; + else if (ret < 0) { + ctx->cur_ns = ctx->inbox_info.ns; + mailbox_list_ns_iter_failed(ctx); + } + return ret; +} + +struct mailbox_list_iterate_context * +mailbox_list_iter_init_namespaces(struct mail_namespace *namespaces, + const char *const *patterns, + enum mail_namespace_type type_mask, + enum mailbox_list_iter_flags flags) +{ + struct ns_list_iterate_context *ctx; + unsigned int i, count; + pool_t pool; + + i_assert(namespaces != NULL); + + pool = pool_alloconly_create("mailbox list namespaces", 1024); + ctx = p_new(pool, struct ns_list_iterate_context, 1); + ctx->pool = pool; + ctx->type_mask = type_mask; + ctx->ctx.flags = flags; + ctx->ctx.list = p_new(pool, struct mailbox_list, 1); + ctx->ctx.list->v.iter_next = mailbox_list_ns_iter_next; + ctx->ctx.list->v.iter_deinit = mailbox_list_ns_iter_deinit; + ctx->namespaces = namespaces; + ctx->error_list = namespaces->list; + + count = str_array_length(patterns); + ctx->patterns = p_new(pool, const char *, count + 1); + for (i = 0; i < count; i++) + ctx->patterns[i] = p_strdup(pool, patterns[i]); + if (patterns_match_inbox(namespaces, ctx->patterns) && + (flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) { + /* we're going to list the INBOX. get its own flags (i.e. not + [no]children) immediately, so if we end up seeing something + else called INBOX (e.g. namespace prefix) we can show it + immediately with the proper flags. */ + ctx->inbox_list = TRUE; + if (inbox_info_init(ctx, namespaces) < 0) { + pool_unref(&pool); + return &mailbox_list_iter_failed; + } + } + + if ((flags & MAILBOX_LIST_ITER_STAR_WITHIN_NS) != 0) { + /* create copies of patterns with '*' wildcard changed to '%'. + this is used only when checking which namespaces to list */ + ctx->patterns_ns_match = + dup_patterns_without_stars(pool, ctx->patterns, count); + } else { + ctx->patterns_ns_match = ctx->patterns; + } + + ctx->cur_ns = namespaces; + ctx->ctx.list->ns = namespaces; + return &ctx->ctx; +} + +static enum autocreate_match_result +autocreate_box_match(const ARRAY_TYPE(mailbox_settings) *boxes, + struct mail_namespace *ns, const char *name, + bool only_subscribed, unsigned int *idx_r) +{ + struct mailbox_settings *const *sets; + unsigned int i, count; + size_t len, name_len = strlen(name); + enum autocreate_match_result result = 0; + char sep = mail_namespace_get_sep(ns); + + *idx_r = UINT_MAX; + + sets = array_get(boxes, &count); + for (i = 0; i < count; i++) { + if (only_subscribed && + strcmp(sets[i]->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) != 0) + continue; + len = I_MIN(name_len, strlen(sets[i]->name)); + if (strncmp(name, sets[i]->name, len) != 0) + continue; + + if (name[len] == '\0' && sets[i]->name[len] == '\0') { + result |= AUTOCREATE_MATCH_RESULT_YES; + *idx_r = i; + } else if (name[len] == '\0' && sets[i]->name[len] == sep) + result |= AUTOCREATE_MATCH_RESULT_CHILDREN; + else if (name[len] == sep && sets[i]->name[len] == '\0') + result |= AUTOCREATE_MATCH_RESULT_PARENT; + } + return result; +} + +const struct mailbox_info * +mailbox_list_iter_autocreate_filter(struct mailbox_list_iterate_context *ctx, + const struct mailbox_info *_info) +{ + struct mailbox_list_autocreate_iterate_context *actx = + ctx->autocreate_ctx; + if (actx == NULL || _info == NULL) + return _info; + actx->new_info = *_info; + struct mailbox_info *info = &actx->new_info; + enum autocreate_match_result match, match2; + unsigned int idx; + + match = autocreate_box_match(&actx->box_sets, ctx->list->ns, + info->vname, FALSE, &idx); + + if (!actx->listing_autoboxes) { + if ((match & AUTOCREATE_MATCH_RESULT_YES) != 0) { + /* we have an exact match in the list. + don't list it at the end. */ + array_delete(&actx->boxes, idx, 1); + array_delete(&actx->box_sets, idx, 1); + } + if ((match & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0 && + hash_table_lookup(actx->duplicate_vnames, info->vname) == NULL) { + /* Prevent autocreate-iteration from adding this + mailbox as a duplicate. For example we're listing % + and we're here because "foo" was found. However, + there's also "foo/bar" with auto=create. We're + telling here to the autocreate iteration code that + "foo" was already found and it doesn't need to add + it again. */ + char *vname = p_strdup(ctx->pool, info->vname); + hash_table_insert(actx->duplicate_vnames, vname, vname); + } + } + + if ((match & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0) { + if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) + info->flags |= MAILBOX_CHILD_SUBSCRIBED; + else { + info->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN); + info->flags |= MAILBOX_CHILDREN; + } + } + + /* make sure the mailbox existence flags are correct. */ + if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) + match2 = match; + else { + match2 = autocreate_box_match(&actx->all_ns_box_sets, + ctx->list->ns, info->vname, + FALSE, &idx); + } + if ((match2 & AUTOCREATE_MATCH_RESULT_YES) != 0) + info->flags &= ENUM_NEGATE(MAILBOX_NONEXISTENT); + if ((match2 & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0) { + info->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN); + info->flags |= MAILBOX_CHILDREN; + } + + if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 && + (ctx->flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) { + /* we're listing all mailboxes and want \Subscribed flag */ + match2 = autocreate_box_match(&actx->all_ns_box_sets, + ctx->list->ns, info->vname, + TRUE, &idx); + if ((match2 & AUTOCREATE_MATCH_RESULT_YES) != 0) { + /* mailbox is also marked as autosubscribe */ + info->flags |= MAILBOX_SUBSCRIBED; + } + if ((match2 & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0) { + /* mailbox also has a children marked as + autosubscribe */ + info->flags |= MAILBOX_CHILD_SUBSCRIBED; + } + } + + if ((match & AUTOCREATE_MATCH_RESULT_PARENT) != 0) { + /* there are autocreate parent boxes. + set their children flag states. */ + struct autocreate_box *autobox; + size_t name_len; + char sep = mail_namespace_get_sep(ctx->list->ns); + + array_foreach_modifiable(&actx->boxes, autobox) { + name_len = strlen(autobox->name); + if (!str_begins(info->vname, autobox->name) || + info->vname[name_len] != sep) + continue; + + if ((info->flags & MAILBOX_NONEXISTENT) == 0) + autobox->flags |= MAILBOX_CHILDREN; + if ((info->flags & MAILBOX_SUBSCRIBED) != 0) + autobox->flags |= MAILBOX_CHILD_SUBSCRIBED; + autobox->child_listed = TRUE; + } + } + return info; +} + +static bool autocreate_iter_autobox(struct mailbox_list_iterate_context *ctx, + const struct autocreate_box *autobox) +{ + struct mailbox_list_autocreate_iterate_context *actx = + ctx->autocreate_ctx; + enum imap_match_result match; + + i_zero(&actx->new_info); + actx->new_info.ns = ctx->list->ns; + actx->new_info.vname = autobox->name; + actx->new_info.flags = autobox->flags; + + if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) + actx->new_info.flags |= MAILBOX_SUBSCRIBED; + + if ((actx->new_info.flags & MAILBOX_CHILDREN) == 0) { + if ((ctx->list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0 && + ctx->list->set.maildir_name[0] == '\0') { + /* mailbox format using files (e.g. mbox) + without DIRNAME specified */ + actx->new_info.flags |= MAILBOX_NOINFERIORS; + } else { + actx->new_info.flags |= MAILBOX_NOCHILDREN; + } + } + + match = imap_match(ctx->glob, actx->new_info.vname); + if (match == IMAP_MATCH_YES) { + actx->new_info.special_use = + *autobox->set->special_use == '\0' ? NULL : + autobox->set->special_use; + return TRUE; + } + if ((match & IMAP_MATCH_PARENT) != 0 && !autobox->child_listed) { + enum mailbox_info_flags old_flags = actx->new_info.flags; + char sep = mail_namespace_get_sep(ctx->list->ns); + const char *p; + char *vname; + + /* e.g. autocreate=foo/bar and we're listing % */ + actx->new_info.flags = MAILBOX_NONEXISTENT | + (old_flags & (MAILBOX_CHILDREN | + MAILBOX_CHILD_SUBSCRIBED)); + if ((old_flags & MAILBOX_NONEXISTENT) == 0) { + actx->new_info.flags |= MAILBOX_CHILDREN; + actx->new_info.flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN); + } + if ((old_flags & MAILBOX_SUBSCRIBED) != 0) + actx->new_info.flags |= MAILBOX_CHILD_SUBSCRIBED; + do { + p = strrchr(actx->new_info.vname, sep); + i_assert(p != NULL); + actx->new_info.vname = vname = + p_strdup_until(ctx->pool, + actx->new_info.vname, p); + match = imap_match(ctx->glob, actx->new_info.vname); + } while (match != IMAP_MATCH_YES); + + if (hash_table_lookup(actx->duplicate_vnames, vname) == NULL) { + hash_table_insert(actx->duplicate_vnames, vname, vname); + return TRUE; + } + } + return FALSE; +} + +static const struct mailbox_info * +mailbox_list_iter_next_call(struct mailbox_list_iterate_context *ctx) +{ + const struct mailbox_info *info; + const struct mailbox_settings *set; + + info = ctx->list->v.iter_next(ctx); + if (info == NULL) + return NULL; + + ctx->list->ns->flags |= NAMESPACE_FLAG_USABLE; + if ((ctx->flags & MAILBOX_LIST_ITER_RETURN_SPECIALUSE) != 0) { + set = mailbox_settings_find(ctx->list->ns, info->vname); + if (set != NULL && *set->special_use != '\0') { + ctx->specialuse_info = *info; + ctx->specialuse_info.special_use = + *set->special_use == '\0' ? NULL : + set->special_use; + info = &ctx->specialuse_info; + } + } + + return mailbox_list_iter_autocreate_filter(ctx, info); +} + +const struct mailbox_info * +mailbox_list_iter_default_next(struct mailbox_list_iterate_context *ctx) +{ + struct mailbox_list_autocreate_iterate_context *actx = + ctx->autocreate_ctx; + const struct autocreate_box *autoboxes, *autobox; + unsigned int count; + + if (actx == NULL) + return NULL; + + /* do not drop boxes anymore */ + actx->listing_autoboxes = TRUE; + + /* list missing mailboxes */ + autoboxes = array_get(&actx->boxes, &count); + while (actx->idx < count) { + autobox = &autoboxes[actx->idx++]; + if (autocreate_iter_autobox(ctx, autobox)) + return &actx->new_info; + } + i_assert(array_count(&actx->boxes) == array_count(&actx->box_sets)); + return NULL; +} + +static bool +special_use_selection(struct mailbox_list_iterate_context *ctx, + const struct mailbox_info *info) +{ + if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0 && + (ctx->flags & MAILBOX_LIST_ITER_SELECT_SPECIALUSE) != 0) { + /* LIST (SPECIAL-USE RECURSIVEMATCH) used. for now we support + this only for namespace prefixes */ + if ((info->flags & MAILBOX_CHILD_SPECIALUSE) != 0) + return TRUE; + } + return (ctx->flags & MAILBOX_LIST_ITER_SELECT_SPECIALUSE) == 0 || + info->special_use != NULL; +} + +const struct mailbox_info * +mailbox_list_iter_next(struct mailbox_list_iterate_context *ctx) +{ + const struct mailbox_info *info; + + if (ctx == &mailbox_list_iter_failed) + return NULL; + do { + T_BEGIN { + info = mailbox_list_iter_next_call(ctx); + } T_END; + } while (info != NULL && !special_use_selection(ctx, info)); + return info; +} + +int mailbox_list_iter_deinit(struct mailbox_list_iterate_context **_ctx) +{ + struct mailbox_list_iterate_context *ctx = *_ctx; + + *_ctx = NULL; + + if (ctx == &mailbox_list_iter_failed) + return -1; + if (ctx->autocreate_ctx != NULL) + hash_table_destroy(&ctx->autocreate_ctx->duplicate_vnames); + return ctx->list->v.iter_deinit(ctx); +} + +static void node_fix_parents(struct mailbox_node *node) +{ + /* If we happened to create any of the parents, we need to mark them + nonexistent. */ + node = node->parent; + for (; node != NULL; node = node->parent) { + if ((node->flags & MAILBOX_MATCHED) == 0) + node->flags |= MAILBOX_NONEXISTENT; + } +} + +static void +mailbox_list_iter_update_real(struct mailbox_list_iter_update_context *ctx, + const char *name) +{ + struct mail_namespace *ns = ctx->iter_ctx->list->ns; + struct mailbox_node *node; + enum mailbox_info_flags create_flags, always_flags; + enum imap_match_result match; + const char *p; + bool created, add_matched; + + create_flags = MAILBOX_NOCHILDREN; + always_flags = ctx->leaf_flags; + add_matched = TRUE; + + for (;;) { + created = FALSE; + match = imap_match(ctx->glob, name); + if (match == IMAP_MATCH_YES) { + node = ctx->update_only ? + mailbox_tree_lookup(ctx->tree_ctx, name) : + mailbox_tree_get(ctx->tree_ctx, name, &created); + if (created) { + node->flags = create_flags; + if (create_flags != 0) + node_fix_parents(node); + } + if (node != NULL) { + if (!ctx->update_only && add_matched) + node->flags |= MAILBOX_MATCHED; + if ((always_flags & MAILBOX_CHILDREN) != 0) + node->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN); + node->flags |= always_flags; + } + /* We don't want to show the parent mailboxes unless + something else matches them, but if they are matched + we want to show them having child subscriptions */ + add_matched = FALSE; + } else { + if ((match & IMAP_MATCH_PARENT) == 0) + break; + /* We've a (possibly) non-subscribed parent mailbox + which has a subscribed child mailbox. Make sure we + return the parent mailbox. */ + } + + if (!ctx->match_parents) + break; + + /* see if parent matches */ + p = strrchr(name, mail_namespace_get_sep(ns)); + if (p == NULL) + break; + + name = t_strdup_until(name, p); + create_flags |= MAILBOX_NONEXISTENT; + create_flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN); + always_flags = MAILBOX_CHILDREN | ctx->parent_flags; + } +} + +void mailbox_list_iter_update(struct mailbox_list_iter_update_context *ctx, + const char *name) +{ + T_BEGIN { + mailbox_list_iter_update_real(ctx, name); + } T_END; +} |