const struct mailbox_info * mailbox_list_index_iter_next(struct mailbox_list_iterate_context *_ctx) { struct mailbox_list_index_iterate_context *ctx = (struct mailbox_list_index_iterate_context *)_ctx; struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(_ctx->list); bool follow_children; enum imap_match_result match; if (ctx->backend_ctx != NULL) { /* index isn't being used */ return ilist->module_ctx.super.iter_next(ctx->backend_ctx); } /* listing mailboxes from index */ while (ctx->next_node != NULL) { mailbox_list_index_update_info(ctx); match = imap_match(_ctx->glob, ctx->info.vname); follow_children = (match & (IMAP_MATCH_YES | IMAP_MATCH_CHILDREN)) != 0; if (match == IMAP_MATCH_YES && iter_subscriptions_ok(ctx)) { mailbox_list_index_update_next(ctx, TRUE); return &ctx->info; } else if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 && (ctx->info.flags & MAILBOX_CHILD_SUBSCRIBED) == 0) { /* listing only subscriptions, but there are no subscribed children. */ follow_children = FALSE; } mailbox_list_index_update_next(ctx, follow_children); } return NULL; }
static bool iter_is_listing_all_children(struct acl_mailbox_list_iterate_context *ctx) { const char *child; /* If all patterns (with '.' separator) are in "name*", "name.*" or "%.*" style format, simple_star_glob=TRUE and we can easily test this by simply checking if name/child mailbox matches. */ child = t_strdup_printf("%s%cx", ctx->info.vname, ctx->sep); return ctx->simple_star_glob && imap_match(ctx->ctx.glob, child) == IMAP_MATCH_YES; }
const struct mailbox_info * mailbox_list_index_iter_next(struct mailbox_list_iterate_context *_ctx) { struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_ctx->list); if (!_ctx->index_iteration) { /* index isn't being used */ return ilist->module_ctx.super.iter_next(_ctx); } struct mailbox_list_index_iterate_context *ctx = (struct mailbox_list_index_iterate_context *)_ctx; bool follow_children; enum imap_match_result match; /* listing mailboxes from index */ while (ctx->next_node != NULL) { mailbox_list_index_update_info(ctx); match = imap_match(_ctx->glob, ctx->info.vname); follow_children = (match & (IMAP_MATCH_YES | IMAP_MATCH_CHILDREN)) != 0; if (match == IMAP_MATCH_YES && iter_subscriptions_ok(ctx)) { /* If this is a) \NoSelect leaf, b) not LAYOUT=index and c) NO-NOSELECT is set, try to rmdir the leaf directores from filesystem. (With LAYOUT=index the \NoSelect mailboxes aren't on the filesystem.) */ if (ilist->has_backing_store && mailbox_list_iter_try_delete_noselect(_ctx, &ctx->info, str_c(ctx->path))) { /* Deleted \NoSelect leaf. Refresh the index later on so it gets removed from the index as well. */ mailbox_list_index_refresh_later(_ctx->list); } else { mailbox_list_index_update_next(ctx, TRUE); return &ctx->info; } } else if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 && (ctx->info.flags & MAILBOX_CHILD_SUBSCRIBED) == 0) { /* listing only subscriptions, but there are no subscribed children. */ follow_children = FALSE; } mailbox_list_index_update_next(ctx, follow_children); } return mailbox_list_iter_default_next(_ctx); }
static int maildir_fill_inbox(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, const char *inbox_name, bool update_only) { struct mailbox_node *node; enum mailbox_info_flags flags; enum imap_match_result match; bool created; int ret; if ((ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0) { /* always show INBOX */ } else { /* INBOX may be Maildir root or completely elsewhere. show it only if it has already been created */ ret = mailbox_list_mailbox(ctx->ctx.list, "INBOX", &flags); if (ret < 0) return -1; if ((flags & MAILBOX_NONEXISTENT) != 0) update_only = TRUE; } if (update_only) { node = mailbox_tree_lookup(ctx->tree_ctx, inbox_name); if (node != NULL) node->flags &= ~MAILBOX_NONEXISTENT; return 0; } /* add the INBOX only if it matches the patterns */ match = imap_match(glob, inbox_name); if (match == IMAP_MATCH_PARENT) maildir_fill_parents(ctx, glob, FALSE, inbox_name); else if (match == IMAP_MATCH_YES) { node = mailbox_tree_get(ctx->tree_ctx, inbox_name, &created); if (created) node->flags = MAILBOX_NOCHILDREN; else node->flags &= ~MAILBOX_NONEXISTENT; node->flags |= MAILBOX_MATCHED; } return 0; }
static void maildir_fill_parents(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, bool update_only, const char *vname) { struct mail_namespace *ns = ctx->ctx.list->ns; struct mailbox_node *node; const char *p; unsigned int vname_len = strlen(vname); bool created; char ns_sep = mail_namespace_get_sep(ns); while ((p = strrchr(vname, ns_sep)) != NULL) { vname = t_strdup_until(vname, p); if (imap_match(glob, vname) != IMAP_MATCH_YES) continue; if (ns->prefix_len > 0 && vname_len == ns->prefix_len-1 && strncmp(vname, ns->prefix, ns->prefix_len - 1) == 0 && vname[ns->prefix_len-1] == ns_sep) { /* don't return matches to namespace prefix itself */ continue; } created = FALSE; node = update_only ? mailbox_tree_lookup(ctx->tree_ctx, vname) : mailbox_tree_get(ctx->tree_ctx, vname, &created); if (node != NULL) { if (created) { /* we haven't yet seen this mailbox, but we might see it later */ node->flags = MAILBOX_NONEXISTENT; } if (!update_only) node->flags |= MAILBOX_MATCHED; node->flags |= MAILBOX_CHILDREN; node->flags &= ~MAILBOX_NOCHILDREN; node_fix_parents(node); } } }
/* Returns >0 = matched, 0 = not matched, -1 = unknown */ static int search_arg_match_mailbox(struct index_search_context *ctx, struct mail_search_arg *arg) { const char *str; switch (arg->type) { case SEARCH_MAILBOX: if (mail_get_special(ctx->mail, MAIL_FETCH_MAILBOX_NAME, &str) < 0) return -1; if (strcasecmp(str, "INBOX") == 0) return strcasecmp(arg->value.str, "INBOX") == 0; return strcmp(str, arg->value.str) == 0; case SEARCH_MAILBOX_GLOB: if (mail_get_special(ctx->mail, MAIL_FETCH_MAILBOX_NAME, &str) < 0) return -1; return imap_match(arg->value.mailbox_glob, str) == IMAP_MATCH_YES; default: return -1; } }
static bool iter_mailbox_has_visible_children(struct acl_mailbox_list_iterate_context *ctx, bool only_nonpatterns, bool subscribed) { struct mailbox_list_iterate_context *iter; const struct mailbox_info *info; string_t *pattern; const char *prefix; unsigned int i, prefix_len; bool stars = FALSE, ret = FALSE; /* do we have child mailboxes with LOOKUP right that don't match the list pattern? */ if (ctx->lookup_boxes != NULL) { /* we have a list of mailboxes with LOOKUP rights. before starting the slow list iteration, check check first if there even are any children with LOOKUP rights. */ struct mailbox_node *node; node = mailbox_tree_lookup(ctx->lookup_boxes, ctx->info.vname); i_assert(node != NULL); if (node->children == NULL) return FALSE; } /* if mailbox name has '*' characters in it, they'll conflict with the LIST wildcard. replace then with '%' and verify later that all results have the correct prefix. */ pattern = t_str_new(128); for (i = 0; ctx->info.vname[i] != '\0'; i++) { if (ctx->info.vname[i] != '*') str_append_c(pattern, ctx->info.vname[i]); else { stars = TRUE; str_append_c(pattern, '%'); } } if (i > 0 && ctx->info.vname[i-1] != ctx->sep) str_append_c(pattern, ctx->sep); str_append_c(pattern, '*'); prefix = str_c(pattern); prefix_len = str_len(pattern) - 1; iter = mailbox_list_iter_init(ctx->ctx.list, str_c(pattern), (!subscribed ? 0 : MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) | MAILBOX_LIST_ITER_RETURN_NO_FLAGS); while ((info = mailbox_list_iter_next(iter)) != NULL) { if (only_nonpatterns && imap_match(ctx->ctx.glob, info->vname) == IMAP_MATCH_YES) { /* at least one child matches also the original list patterns. we don't need to show this mailbox. */ ret = FALSE; break; } if (!stars || strncmp(info->vname, prefix, prefix_len) == 0) ret = TRUE; } (void)mailbox_list_iter_deinit(&iter); return ret; }
static int maildir_fill_readdir_entry(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, const struct dirent *d, bool update_only) { struct mailbox_list *list = ctx->ctx.list; const char *fname, *storage_name, *vname; enum mailbox_info_flags flags; enum imap_match_result match; struct mailbox_node *node; bool created; int ret; fname = d->d_name; if (fname[0] == ctx->prefix_char) storage_name = fname + 1; else { if (ctx->prefix_char != '\0' || fname[0] == '.') return 0; storage_name = fname; } /* skip . and .. */ if (fname[0] == '.' && (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))) return 0; vname = mailbox_list_get_vname(list, storage_name); if (!uni_utf8_str_is_valid(vname)) { /* the storage_name is completely invalid, rename it to something more sensible. we could do this for all names that aren't valid mUTF-7, but that might lead to accidents in future when UTF-8 storage names are used */ const char *src = t_strdup_printf("%s/%s", ctx->dir, fname); string_t *destvname = t_str_new(128); string_t *dest = t_str_new(128); (void)uni_utf8_get_valid_data((const void *)fname, strlen(fname), destvname); str_append(dest, ctx->dir); str_append_c(dest, '/'); (void)imap_utf8_to_utf7(str_c(destvname), dest); if (rename(src, str_c(dest)) < 0 && errno != ENOENT) i_error("rename(%s, %s) failed: %m", src, str_c(dest)); /* just skip this in this iteration, we'll see it on the next list */ return 0; } /* make sure the pattern matches */ match = imap_match(glob, vname); if ((match & (IMAP_MATCH_YES | IMAP_MATCH_PARENT)) == 0) return 0; /* check if this is an actual mailbox */ if (maildir_delete_trash_dir(ctx, fname)) return 0; if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) { ret = mailbox_list_dirent_is_alias_symlink(list, ctx->dir, d); if (ret != 0) return ret < 0 ? -1 : 0; } T_BEGIN { ret = list->v.get_mailbox_flags(list, ctx->dir, fname, mailbox_list_get_file_type(d), &flags); } T_END; if (ret <= 0) return ret; /* we know the children flags ourself, so ignore if any of them were set. */ flags &= ~(MAILBOX_NOINFERIORS | MAILBOX_CHILDREN | MAILBOX_NOCHILDREN); if ((match & IMAP_MATCH_PARENT) != 0) maildir_fill_parents(ctx, glob, update_only, vname); else { created = FALSE; node = update_only ? mailbox_tree_lookup(ctx->tree_ctx, vname) : mailbox_tree_get(ctx->tree_ctx, vname, &created); if (node != NULL) { if (created) node->flags = MAILBOX_NOCHILDREN; else node->flags &= ~MAILBOX_NONEXISTENT; if (!update_only) node->flags |= MAILBOX_MATCHED; node->flags |= flags; node_fix_parents(node); } else { i_assert(update_only); maildir_set_children(ctx, vname); } } return 0; }
static void test_imap_match(void) { struct test_imap_match test[] = { { "", "", IMAP_MATCH_YES }, { "a", "b", IMAP_MATCH_NO }, { "foo", "foo", IMAP_MATCH_YES }, { "foo", "foo/", IMAP_MATCH_PARENT }, { "%", "", IMAP_MATCH_YES }, { "%", "foo", IMAP_MATCH_YES }, { "%", "foo/", IMAP_MATCH_PARENT }, { "%/", "foo/", IMAP_MATCH_YES }, { "%", "foo/bar", IMAP_MATCH_PARENT }, { "%/%", "foo", IMAP_MATCH_CHILDREN }, { "%/%", "foo/", IMAP_MATCH_YES }, { "foo/bar/%", "foo", IMAP_MATCH_CHILDREN }, { "foo/bar/%", "foo/", IMAP_MATCH_CHILDREN }, { "foo*", "foo", IMAP_MATCH_YES }, { "foo*", "foo/", IMAP_MATCH_YES }, { "foo*", "fobo", IMAP_MATCH_NO }, { "*foo*", "bar/foo/", IMAP_MATCH_YES }, { "*foo*", "fobo", IMAP_MATCH_CHILDREN }, { "foo*bar", "foobar/baz", IMAP_MATCH_CHILDREN | IMAP_MATCH_PARENT }, { "*foo*", "fobo", IMAP_MATCH_CHILDREN }, { "%/%/%", "foo/", IMAP_MATCH_CHILDREN }, { "%/%o/%", "foo/", IMAP_MATCH_CHILDREN }, { "%/%o/%", "foo", IMAP_MATCH_CHILDREN }, { "inbox", "inbox", IMAP_MATCH_YES }, { "inbox", "INBOX", IMAP_MATCH_NO } }; struct test_imap_match inbox_test[] = { { "inbox", "inbox", IMAP_MATCH_YES }, { "inbox", "iNbOx", IMAP_MATCH_YES }, { "i%X", "iNbOx", IMAP_MATCH_YES }, { "%I%N%B%O%X%", "inbox", IMAP_MATCH_YES }, { "i%X/foo", "iNbOx/foo", IMAP_MATCH_YES }, { "%I%N%B%O%X%/foo", "inbox/foo", IMAP_MATCH_YES }, { "i%X/foo", "inbx/foo", IMAP_MATCH_NO } }; struct imap_match_glob *glob, *glob2; unsigned int i; pool_t pool; pool = pool_alloconly_create("imap match", 1024); /* first try tests without inboxcasing */ test_begin("imap match"); for (i = 0; i < N_ELEMENTS(test); i++) { glob = imap_match_init(pool, test[i].pattern, FALSE, '/'); test_assert(imap_match(glob, test[i].input) == test[i].result); glob2 = imap_match_dup(default_pool, glob); test_assert(imap_match_globs_equal(glob, glob2)); p_clear(pool); /* test the dup after clearing first one's memory */ test_assert(imap_match(glob2, test[i].input) == test[i].result); imap_match_deinit(&glob2); } /* inboxcasing tests */ for (i = 0; i < N_ELEMENTS(inbox_test); i++) { glob = imap_match_init(pool, inbox_test[i].pattern, TRUE, '/'); test_assert(imap_match(glob, inbox_test[i].input) == inbox_test[i].result); glob2 = imap_match_dup(default_pool, glob); test_assert(imap_match_globs_equal(glob, glob2)); p_clear(pool); /* test the dup after clearing first one's memory */ test_assert(imap_match(glob2, inbox_test[i].input) == inbox_test[i].result); imap_match_deinit(&glob2); } pool_unref(&pool); test_end(); }