Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib-storage/list/mailbox-list-fs-iter.c @ 5829:1d73153584d2 HEAD
Mailbox listing API changed to support more features. Used to implement
support for half of LIST-EXTENDED.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Fri, 29 Jun 2007 03:39:27 +0300 |
parents | 45735dd11f17 |
children | 978722ad6184 |
line wrap: on
line source
/* Copyright (C) 2002-2006 Timo Sirainen */ #include "lib.h" #include "home-expand.h" #include "unlink-directory.h" #include "imap-match.h" #include "mailbox-tree.h" #include "mailbox-list-subscriptions.h" #include "mailbox-list-fs.h" #include <dirent.h> struct list_dir_context { struct list_dir_context *prev; DIR *dirp; char *real_path, *virtual_path; }; struct fs_list_iterate_context { struct mailbox_list_iterate_context ctx; struct imap_match_glob *glob; struct mailbox_tree_context *subs_tree; struct mailbox_tree_iterate_context *tree_iter; bool inbox_found, inbox_listed; enum mailbox_info_flags inbox_flags; const struct mailbox_info *(*next)(struct fs_list_iterate_context *ctx); pool_t info_pool; struct mailbox_info info; struct list_dir_context *dir; }; static const struct mailbox_info * fs_list_subs(struct fs_list_iterate_context *ctx); static const struct mailbox_info * fs_list_path(struct fs_list_iterate_context *ctx); static const struct mailbox_info * fs_list_next(struct fs_list_iterate_context *ctx); static const char *mask_get_dir(const char *mask) { const char *p, *last_dir; last_dir = NULL; for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) { if (*p == '/') last_dir = p; } return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir); } static int list_opendir(struct mailbox_list *list, const char *path, bool root, DIR **dirp) { *dirp = opendir(*path == '\0' ? "/" : path); if (*dirp != NULL) return 1; if (ENOTFOUND(errno)) { /* root) user gave invalid hiearchy, ignore sub) probably just race condition with other client deleting the mailbox. */ return 0; } if (errno == EACCES) { if (!root) { /* subfolder, ignore */ return 0; } mailbox_list_set_error(list, MAIL_ERROR_PERM, MAIL_ERRSTR_NO_PERMISSION); return -1; } mailbox_list_set_critical(list, "opendir(%s) failed: %m", path); return -1; } struct mailbox_list_iterate_context * fs_list_iter_init(struct mailbox_list *_list, const char *mask, enum mailbox_list_iter_flags flags) { struct fs_list_iterate_context *ctx; const char *path, *virtual_path; DIR *dirp; ctx = i_new(struct fs_list_iterate_context, 1); ctx->ctx.list = _list; ctx->ctx.flags = flags; ctx->info_pool = pool_alloconly_create("fs list", 1024); ctx->next = fs_list_next; ctx->glob = imap_match_init(default_pool, mask, TRUE, '/'); /* check that we're not trying to do any "../../" lists */ if (!mailbox_list_is_valid_mask(_list, mask)) return &ctx->ctx; if ((flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED | MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0) { /* we want to return MAILBOX_SUBSCRIBED flags, possibly for all mailboxes. Build a mailbox tree of all the subscriptions. */ ctx->subs_tree = mailbox_tree_init('/'); if (mailbox_list_subscriptions_fill(&ctx->ctx, ctx->subs_tree, ctx->glob, FALSE) < 0) { ctx->ctx.failed = TRUE; return &ctx->ctx; } } if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) { ctx->next = fs_list_subs; ctx->tree_iter = mailbox_tree_iterate_init(ctx->subs_tree, NULL, MAILBOX_MATCHED); return &ctx->ctx; } /* if we're matching only subdirectories, don't bother scanning the parent directories */ virtual_path = mask_get_dir(mask); path = mailbox_list_get_path(_list, virtual_path, MAILBOX_LIST_PATH_TYPE_DIR); if (list_opendir(_list, path, TRUE, &dirp) < 0) return &ctx->ctx; /* if user gave invalid directory, we just don't show any results. */ if (virtual_path != NULL && dirp != NULL) ctx->next = fs_list_path; if (dirp != NULL) { ctx->dir = i_new(struct list_dir_context, 1); ctx->dir->dirp = dirp; ctx->dir->real_path = i_strdup(path); ctx->dir->virtual_path = i_strdup(virtual_path); } return &ctx->ctx; } static void list_dir_context_free(struct list_dir_context *dir) { (void)closedir(dir->dirp); i_free(dir->real_path); i_free(dir->virtual_path); i_free(dir); } int fs_list_iter_deinit(struct mailbox_list_iterate_context *_ctx) { struct fs_list_iterate_context *ctx = (struct fs_list_iterate_context *)_ctx; int ret = ctx->ctx.failed ? -1 : 0; while (ctx->dir != NULL) { struct list_dir_context *dir = ctx->dir; ctx->dir = dir->prev; list_dir_context_free(dir); } if (ctx->tree_iter != NULL) mailbox_tree_iterate_deinit(&ctx->tree_iter); if (ctx->subs_tree != NULL) mailbox_tree_deinit(&ctx->subs_tree); if (ctx->info_pool != NULL) pool_unref(ctx->info_pool); if (ctx->glob != NULL) imap_match_deinit(&ctx->glob); i_free(ctx); return ret; } const struct mailbox_info * fs_list_iter_next(struct mailbox_list_iterate_context *_ctx) { struct fs_list_iterate_context *ctx = (struct fs_list_iterate_context *)_ctx; if (ctx->ctx.failed) return NULL; return ctx->next(ctx); } static void path_split(const char *path, const char **dir_r, const char **fname_r) { const char *p; p = strrchr(path, '/'); if (p == NULL) { *dir_r = ""; *fname_r = path; } else { *dir_r = t_strdup_until(path, p); *fname_r = p + 1; } } static enum mailbox_info_flags fs_list_get_subscription_flags(struct fs_list_iterate_context *ctx, const char *mailbox) { struct mailbox_node *node; node = mailbox_tree_lookup(ctx->subs_tree, mailbox); if (node == NULL) return 0; return node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED); } static struct mailbox_info *fs_list_inbox(struct fs_list_iterate_context *ctx) { const char *inbox_path, *dir, *fname; ctx->info.flags = 0; ctx->info.name = "INBOX"; t_push(); inbox_path = mailbox_list_get_path(ctx->ctx.list, "INBOX", MAILBOX_LIST_PATH_TYPE_DIR); path_split(inbox_path, &dir, &fname); if (ctx->ctx.list->v.iter_is_mailbox(&ctx->ctx, dir, fname, MAILBOX_LIST_FILE_TYPE_UNKNOWN, &ctx->info.flags) < 0) ctx->ctx.failed = TRUE; t_pop(); ctx->info.flags |= fs_list_get_subscription_flags(ctx, "INBOX"); return &ctx->info; } static int list_file(struct fs_list_iterate_context *ctx, const struct dirent *d) { const char *fname = d->d_name; struct list_dir_context *dir; const char *list_path, *real_path, *path, *inbox_path; DIR *dirp; enum imap_match_result match, match2; int ret; /* skip . and .. */ if (fname[0] == '.' && (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))) return 0; /* check the mask */ if (ctx->dir->virtual_path == NULL) list_path = fname; else { list_path = t_strconcat(ctx->dir->virtual_path, "/", fname, NULL); } if ((match = imap_match(ctx->glob, list_path)) < 0) return 0; /* get the info.flags using callback */ ctx->info.flags = 0; ret = ctx->ctx.list->v. iter_is_mailbox(&ctx->ctx, ctx->dir->real_path, fname, mailbox_list_get_file_type(d), &ctx->info.flags); if (ret <= 0) return ret; if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) { ctx->info.flags |= fs_list_get_subscription_flags(ctx, list_path); } /* make sure we give only one correct INBOX */ real_path = t_strconcat(ctx->dir->real_path, "/", fname, NULL); if ((ctx->ctx.list->ns->flags & NAMESPACE_FLAG_INBOX) != 0 && strcasecmp(list_path, "INBOX") == 0) { if (ctx->inbox_listed) { /* already listed the INBOX */ return 0; } inbox_path = mailbox_list_get_path(ctx->ctx.list, "INBOX", MAILBOX_LIST_PATH_TYPE_DIR); if (strcmp(real_path, inbox_path) == 0) { /* delay listing in case there's a INBOX/ directory */ ctx->inbox_found = TRUE; ctx->inbox_flags = ctx->info.flags; return 0; } if (strcmp(fname, "INBOX") != 0 || (ctx->info.flags & MAILBOX_NOINFERIORS) != 0) { /* duplicate INBOX, can't show this */ return 0; } /* INBOX/ directory. show the INBOX list now */ if (!ctx->inbox_found) { enum mailbox_info_flags dir_flags = ctx->info.flags; (void)fs_list_inbox(ctx); ctx->info.flags &= ~(MAILBOX_NOINFERIORS | MAILBOX_NOCHILDREN); ctx->info.flags |= dir_flags; ctx->inbox_found = TRUE; } else { ctx->info.flags &= ~MAILBOX_NOSELECT; ctx->info.flags |= ctx->inbox_flags; } ctx->inbox_listed = TRUE; } if ((ctx->info.flags & MAILBOX_NOINFERIORS) == 0) { /* subdirectory. scan inside it. */ path = t_strconcat(list_path, "/", NULL); match2 = imap_match(ctx->glob, path); if (match > 0) ctx->info.name = p_strdup(ctx->info_pool, list_path); else if (match2 > 0) ctx->info.name = p_strdup(ctx->info_pool, path); else ctx->info.name = NULL; ret = match2 < 0 ? 0 : list_opendir(ctx->ctx.list, real_path, FALSE, &dirp); if (ret > 0) { dir = i_new(struct list_dir_context, 1); dir->dirp = dirp; dir->real_path = i_strdup(real_path); dir->virtual_path = i_strdup(list_path); dir->prev = ctx->dir; ctx->dir = dir; } else if (ret < 0) return -1; return match > 0 || match2 > 0; } else if (match > 0) { ctx->info.name = p_strdup(ctx->info_pool, list_path); return 1; } return 0; } static const struct mailbox_info * fs_list_subs(struct fs_list_iterate_context *ctx) { struct mailbox_node *node; enum mailbox_info_flags flags; const char *path, *dir, *fname; node = mailbox_tree_iterate_next(ctx->tree_iter, &ctx->info.name); if (node == NULL) return NULL; /* subscription list has real knowledge of only subscription flags */ flags = node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED); if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 && (ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) { ctx->info.flags = flags; return &ctx->info; } t_push(); path = mailbox_list_get_path(ctx->ctx.list, ctx->info.name, MAILBOX_LIST_PATH_TYPE_DIR); path_split(path, &dir, &fname); if (ctx->ctx.list->v.iter_is_mailbox(&ctx->ctx, dir, fname, MAILBOX_LIST_FILE_TYPE_UNKNOWN, &ctx->info.flags) < 0) ctx->ctx.failed = TRUE; t_pop(); ctx->info.flags |= flags; return &ctx->info; } static const struct mailbox_info * fs_list_path(struct fs_list_iterate_context *ctx) { ctx->next = fs_list_next; ctx->info.flags = MAILBOX_NOSELECT | MAILBOX_CHILDREN; ctx->info.name = p_strconcat(ctx->info_pool, ctx->dir->virtual_path, "/", NULL); if (imap_match(ctx->glob, ctx->info.name) > 0) return &ctx->info; else return ctx->next(ctx); } static const struct mailbox_info * fs_list_next(struct fs_list_iterate_context *ctx) { struct list_dir_context *dir; struct dirent *d; int ret; p_clear(ctx->info_pool); while (ctx->dir != NULL) { /* NOTE: list_file() may change ctx->dir */ while ((d = readdir(ctx->dir->dirp)) != NULL) { t_push(); ret = list_file(ctx, d); t_pop(); if (ret > 0) return &ctx->info; if (ret < 0) { ctx->ctx.failed = TRUE; return NULL; } } dir = ctx->dir; ctx->dir = dir->prev; list_dir_context_free(dir); } if (!ctx->inbox_found && (ctx->ctx.list->ns->flags & NAMESPACE_FLAG_INBOX) != 0 && ctx->glob != NULL && imap_match(ctx->glob, "INBOX") > 0) { /* INBOX wasn't seen while listing other mailboxes. It might be located elsewhere. */ ctx->inbox_listed = TRUE; ctx->inbox_found = TRUE; return fs_list_inbox(ctx); } if (!ctx->inbox_listed && ctx->inbox_found) { /* INBOX was found, but we delayed listing it. Show it now. */ ctx->inbox_listed = TRUE; ctx->info.flags = ctx->inbox_flags; ctx->info.name = "INBOX"; return &ctx->info; } /* finished */ return NULL; }