]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Rewrote LIST, LSUB and subscription file handling. LIST replies aren't
authorTimo Sirainen <tss@iki.fi>
Wed, 19 Feb 2003 19:55:27 +0000 (21:55 +0200)
committerTimo Sirainen <tss@iki.fi>
Wed, 19 Feb 2003 19:55:27 +0000 (21:55 +0200)
sorted anymore by default, it can be enabled with client_workarounds =
list-sort.

--HG--
branch : HEAD

14 files changed:
dovecot-example.conf
src/imap/cmd-list.c
src/lib-imap/imap-match.c
src/lib-imap/imap-match.h
src/lib-storage/index/maildir/maildir-list.c
src/lib-storage/index/maildir/maildir-storage.c
src/lib-storage/index/maildir/maildir-storage.h
src/lib-storage/index/mbox/mbox-list.c
src/lib-storage/index/mbox/mbox-storage.c
src/lib-storage/index/mbox/mbox-storage.h
src/lib-storage/mail-storage.c
src/lib-storage/mail-storage.h
src/lib-storage/subscription-file/subscription-file.c
src/lib-storage/subscription-file/subscription-file.h

index 5f61a064c9da1e481e9ffa97ea6bd18b66c75e8d..7930cd268ab2741fb3dc80c2a93ce72654406cce 100644 (file)
@@ -215,6 +215,11 @@ login = pop3
 #     seems to think they are FETCH replies and gives user "Message no longer
 #     in server" error. Note that OE6 still breaks even with this workaround
 #     if synchronization is set to "Headers Only".
+#   list-sort:
+#     When replying to LIST and LSUB requests, make sure that the parent
+#     mailboxes are sent before any of their children. This is mostly
+#     maildir-specific, mbox list replies are always sorted. MacOS X's Mail.app
+#     at least wants this.
 #client_workarounds = 
 
 # Dovecot can notify client of new mail in selected mailbox soon after it's
index acdbbe7ba913f5669ef9fbdaacc779d921582366..d3665e9eddcc31560cdb78fc0f66049d7c203964 100644 (file)
@@ -76,20 +76,6 @@ static struct list_node *list_node_get(pool_t pool, struct list_node **node,
        return *node;
 }
 
-static void list_cb(struct mail_storage *storage __attr_unused__,
-                   const char *name, enum mailbox_flags flags, void *context)
-{
-       struct list_context *ctx = context;
-       struct list_node *node;
-
-       node = list_node_get(ctx->pool, &ctx->nodes, name,
-                            ctx->storage->hierarchy_sep);
-
-       /* set the flags, this also nicely overrides the NOSELECT flag
-          set by list_node_get() */
-       node->flags = flags;
-}
-
 static void list_send(struct client *client, struct list_node *node,
                      const char *cmd, const char *path, const char *sep)
 {
@@ -119,10 +105,77 @@ static void list_send(struct client *client, struct list_node *node,
        }
 }
 
+static void list_unsorted(struct client *client,
+                         struct mailbox_list_context *ctx,
+                         const char *cmd, const char *sep)
+{
+       struct mailbox_list *list;
+       const char *name, *str;
+
+       while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
+               t_push();
+               if (strcasecmp(list->name, "INBOX") == 0)
+                       name = "INBOX";
+               else
+                       name = str_escape(list->name);
+               str = t_strdup_printf("* %s (%s) \"%s\" \"%s\"", cmd,
+                                     mailbox_flags2str(list->flags),
+                                     sep, name);
+               client_send_line(client, str);
+               t_pop();
+       }
+}
+
+static void list_and_sort(struct client *client,
+                         struct mailbox_list_context *ctx,
+                         const char *cmd, const char *sep)
+{
+       struct mailbox_list *list;
+       struct list_node *nodes, *node;
+       pool_t pool;
+
+       pool = pool_alloconly_create("list_mailboxes", 10240);
+       nodes = NULL;
+
+       while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
+               node = list_node_get(pool, &nodes, list->name,
+                                    client->storage->hierarchy_sep);
+
+               /* set the flags, this also overrides the
+                  NOSELECT flag set by list_node_get() */
+               node->flags = list->flags;
+       }
+
+       list_send(client, nodes, cmd, NULL, sep);
+       pool_unref(pool);
+}
+
+static int list_mailboxes(struct client *client, const char *mask,
+                         int subscribed, const char *sep)
+{
+       struct mailbox_list_context *ctx;
+       const char *cmd;
+       int sorted;
+
+       ctx = client->storage->
+               list_mailbox_init(client->storage, mask,
+                                 subscribed ? MAILBOX_LIST_SUBSCRIBED : 0,
+                                 &sorted);
+       if (ctx == NULL)
+               return FALSE;
+
+        cmd = subscribed ? "LSUB" : "LIST";
+       if (sorted || (client_workarounds & WORKAROUND_LIST_SORT) == 0)
+               list_unsorted(client, ctx, cmd, sep);
+       else
+               list_and_sort(client, ctx, cmd, sep);
+
+       return client->storage->list_mailbox_deinit(ctx);
+}
+
 int _cmd_list_full(struct client *client, int subscribed)
 {
-       struct list_context ctx;
-       const char *ref, *pattern;
+       const char *ref, *mask;
        char sep_chr, sep[3];
        int failed;
 
@@ -137,50 +190,32 @@ int _cmd_list_full(struct client *client, int subscribed)
        }
 
        /* <reference> <mailbox wildcards> */
-       if (!client_read_string_args(client, 2, &ref, &pattern))
+       if (!client_read_string_args(client, 2, &ref, &mask))
                return FALSE;
 
-       if (*pattern == '\0' && !subscribed) {
+       if (*mask == '\0' && !subscribed) {
                /* special request to return the hierarchy delimiter */
                client_send_line(client, t_strconcat(
                        "* LIST (\\Noselect) \"", sep, "\" \"\"", NULL));
                failed = FALSE;
        } else {
                if (*ref != '\0') {
-                       /* join reference + pattern */
-                       if (*pattern == sep_chr &&
+                       /* join reference + mask */
+                       if (*mask == sep_chr &&
                            ref[strlen(ref)-1] == sep_chr) {
                                /* LIST A. .B -> A.B */
-                               pattern++;
+                               mask++;
                        }
-                       if (*pattern != sep_chr &&
+                       if (*mask != sep_chr &&
                            ref[strlen(ref)-1] != sep_chr) {
                                /* LIST A B -> A.B */
-                               pattern = t_strconcat(ref, sep, pattern, NULL);
+                               mask = t_strconcat(ref, sep, mask, NULL);
                        } else {
-                               pattern = t_strconcat(ref, pattern, NULL);
+                               mask = t_strconcat(ref, mask, NULL);
                        }
                }
 
-               ctx.pool = pool_alloconly_create("list_context", 10240);
-               ctx.nodes = NULL;
-               ctx.storage = client->storage;
-
-               if (!subscribed) {
-                       failed = !client->storage->
-                               find_mailboxes(client->storage,
-                                              pattern, list_cb, &ctx);
-               } else {
-                       failed = !client->storage->
-                               find_subscribed(client->storage,
-                                               pattern, list_cb, &ctx);
-               }
-
-               if (!failed) {
-                       list_send(client, ctx.nodes,
-                                 subscribed ? "LSUB" : "LIST", NULL, sep);
-               }
-               pool_unref(ctx.pool);
+               failed = !list_mailboxes(client, mask, subscribed, sep);
        }
 
        if (failed)
index 0ea4f86edfd9e66acd19a352cf5e0d6ecead53ea..a7f7e292355f5afe8ee220070a6a68d34172641a 100644 (file)
@@ -10,6 +10,8 @@
 #include <ctype.h>
 
 struct imap_match_glob {
+       pool_t pool;
+
        int inboxcase;
        const char *inboxcase_end;
 
@@ -21,15 +23,16 @@ struct imap_match_glob {
 static const char inbox[] = "INBOX";
 #define INBOXLEN (sizeof(inbox) - 1)
 
-struct imap_match_glob *imap_match_init(const char *mask, int inboxcase,
-                                       char separator)
+struct imap_match_glob *
+imap_match_init(pool_t pool, const char *mask, int inboxcase, char separator)
 {
        struct imap_match_glob *glob;
        const char *p, *inboxp;
        char *dst;
 
        /* +1 from struct */
-       glob = t_malloc(sizeof(struct imap_match_glob) + strlen(mask));
+       glob = p_malloc(pool, sizeof(struct imap_match_glob) + strlen(mask));
+       glob->pool = pool;
        glob->sep_char = separator;
 
        /* @UNSAFE: compress the mask */
@@ -79,13 +82,18 @@ struct imap_match_glob *imap_match_init(const char *mask, int inboxcase,
                }
 
                if (glob->inboxcase && inboxp != NULL && *inboxp != '\0' &&
-                   *p != '*' && (p != glob->mask && p[-1] == '%'))
+                   *p != '*' && (p != glob->mask && p[-1] != '%'))
                        glob->inboxcase = FALSE;
        }
 
        return glob;
 }
 
+void imap_match_deinit(struct imap_match_glob *glob)
+{
+       p_free(glob->pool, glob);
+}
+
 static inline int cmp_chr(const struct imap_match_glob *glob,
                          const char *data, char maskchr)
 {
@@ -94,23 +102,24 @@ static inline int cmp_chr(const struct imap_match_glob *glob,
                 i_toupper(*data) == i_toupper(maskchr));
 }
 
-static int match_sub(const struct imap_match_glob *glob, const char **data_p,
-                    const char **mask_p)
+static enum imap_match_result
+match_sub(const struct imap_match_glob *glob, const char **data_p,
+         const char **mask_p)
 {
        const char *mask, *data;
-       int ret, best_ret;
+       enum imap_match_result ret, best_ret;
 
        data = *data_p; mask = *mask_p;
 
        while (*mask != '\0' && *mask != '*' && *mask != '%') {
                if (!cmp_chr(glob, data, *mask)) {
                        return *data == '\0' && *mask == glob->sep_char ?
-                               0 : -1;
+                               IMAP_MATCH_CHILDREN : IMAP_MATCH_NO;
                }
                data++; mask++;
        }
 
-        best_ret = -1;
+        best_ret = IMAP_MATCH_NO;
        while (*mask == '%') {
                mask++;
 
@@ -126,8 +135,8 @@ static int match_sub(const struct imap_match_glob *glob, const char **data_p,
                                if (ret > 0)
                                        break;
 
-                               if (ret == 0)
-                                       best_ret = 0;
+                               if (ret == IMAP_MATCH_CHILDREN)
+                                       best_ret = IMAP_MATCH_CHILDREN;
                        }
 
                        if (*data == glob->sep_char)
@@ -139,18 +148,23 @@ static int match_sub(const struct imap_match_glob *glob, const char **data_p,
 
        if (*mask != '*') {
                if (*data == '\0' && *mask != '\0')
-                       return *mask == glob->sep_char ? 0 : best_ret;
+                       return *mask == glob->sep_char ?
+                               IMAP_MATCH_CHILDREN : best_ret;
 
-               if (*data != '\0')
-                       return best_ret;
+               if (*data != '\0') {
+                       return best_ret != IMAP_MATCH_NO ||
+                               *mask != '\0' || *data != glob->sep_char ?
+                               best_ret : IMAP_MATCH_PARENT;
+               }
        }
 
        *data_p = data;
        *mask_p = mask;
-       return 1;
+       return IMAP_MATCH_YES;
 }
 
-int imap_match(struct imap_match_glob *glob, const char *data)
+enum imap_match_result
+imap_match(struct imap_match_glob *glob, const char *data)
 {
        const char *mask;
        int ret;
@@ -168,14 +182,14 @@ int imap_match(struct imap_match_glob *glob, const char *data)
                        return ret;
 
                if (*mask == '\0')
-                       return 1;
+                       return IMAP_MATCH_YES;
        }
 
        while (*mask == '*') {
                mask++;
 
                if (*mask == '\0')
-                       return 1;
+                       return IMAP_MATCH_YES;
 
                while (*data != '\0') {
                        if (cmp_chr(glob, data, *mask)) {
@@ -187,5 +201,6 @@ int imap_match(struct imap_match_glob *glob, const char *data)
                }
        }
 
-       return *data == '\0' && *mask == '\0' ? 1 : 0;
+       return *data == '\0' && *mask == '\0' ?
+               IMAP_MATCH_YES : IMAP_MATCH_CHILDREN;
 }
index 2d9a4cd37e4214628e304fad666401e2c7eec60b..207fa87a0c22c13b453bc67ca9349942d6e529a1 100644 (file)
@@ -1,15 +1,24 @@
 #ifndef __IMAP_MATCH_H
 #define __IMAP_MATCH_H
 
+enum imap_match_result {
+       IMAP_MATCH_YES = 1, /* match */
+       IMAP_MATCH_NO = -1, /* definite non-match */
+
+       IMAP_MATCH_CHILDREN = 0, /* it's children might match */
+       IMAP_MATCH_PARENT = -2 /* one of it's parents would match */
+};
+
 struct imap_match_glob;
 
 /* If inboxcase is TRUE, the "INBOX" string at the beginning of line is
    compared case-insensitively */
-struct imap_match_glob *imap_match_init(const char *mask, int inboxcase,
-                                       char separator);
+struct imap_match_glob *
+imap_match_init(pool_t pool, const char *mask, int inboxcase, char separator);
+
+void imap_match_deinit(struct imap_match_glob *glob);
 
-/* Returns 1 if matched, 0 if it didn't match, but could match with additional
-   hierarchies, -1 if definitely didn't match */
-int imap_match(struct imap_match_glob *glob, const char *data);
+enum imap_match_result
+imap_match(struct imap_match_glob *glob, const char *data);
 
 #endif
index 8f2abdff885662f2600afc793498763e353b4d50..6b7ce31a2bb907b43046fad9d0c4b4572166ba0c 100644 (file)
 #include <dirent.h>
 #include <sys/stat.h>
 
-struct find_subscribed_context {
-       mailbox_list_callback_t *callback;
-       void *context;
+struct mailbox_list_context {
+       pool_t pool, list_pool;
+
+       struct mail_storage *storage;
+       const char *dir, *prefix;
+        enum mailbox_list_flags flags;
+
+       DIR *dirp;
+       struct imap_match_glob *glob;
+       struct subsfile_list_context *subsfile_ctx;
+
+       struct mailbox_list *(*next)(struct mailbox_list_context *ctx);
+
+       struct mailbox_list list;
+       int found_inbox, failed;
 };
 
+static struct mailbox_list *maildir_list_subs(struct mailbox_list_context *ctx);
+static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx);
+
 static enum mailbox_flags
 maildir_get_marked_flags_from(const char *dir, time_t index_stamp)
 {
@@ -72,50 +87,147 @@ maildir_get_marked_flags(struct mail_storage *storage, const char *dir)
        return maildir_get_marked_flags_from(dir, st.st_mtime);
 }
 
-int maildir_find_mailboxes(struct mail_storage *storage, const char *mask,
-                          mailbox_list_callback_t callback, void *context)
+struct mailbox_list_context *
+maildir_list_mailbox_init(struct mail_storage *storage,
+                         const char *mask, enum mailbox_list_flags flags,
+                         int *sorted)
 {
-        struct imap_match_glob *glob;
-       DIR *dirp;
-       struct dirent *d;
-       struct stat st;
-        enum mailbox_flags flags;
-       const char *dir, *prefix, *p;
-       char path[PATH_MAX];
-       int failed, found_inbox, ret;
+        struct mailbox_list_context *ctx;
+       pool_t pool;
+       const char *dir, *p;
 
+       *sorted = FALSE;
        mail_storage_clear_error(storage);
 
+       pool = pool_alloconly_create("maildir_list", 1024);
+       ctx = p_new(pool, struct mailbox_list_context, 1);
+       ctx->pool = pool;
+       ctx->storage = storage;
+       ctx->flags = flags;
+
+       if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
+               ctx->glob = imap_match_init(pool, mask, TRUE, '.');
+               ctx->subsfile_ctx = subsfile_list_init(storage);
+               ctx->next = maildir_list_subs;
+               if (ctx->subsfile_ctx == NULL) {
+                       pool_unref(pool);
+                       return NULL;
+               }
+               return ctx;
+       }
+
        if (!full_filesystem_access || (p = strrchr(mask, '/')) == NULL) {
-               dir = storage->dir;
-               prefix = "";
+               ctx->dir = storage->dir;
+               ctx->prefix = "";
        } else {
                p = strchr(p, storage->hierarchy_sep);
                if (p == NULL) {
                        /* this isn't going to work */
                        mail_storage_set_error(storage, "Invalid list mask");
+                       pool_unref(pool);
                        return FALSE;
                }
 
                dir = t_strdup_until(mask, p);
-               prefix = t_strdup_until(mask, p+1);
+               ctx->prefix = t_strdup_until(mask, p+1);
 
                if (*mask != '/' && *mask != '~')
                        dir = t_strconcat(storage->dir, "/", dir, NULL);
-               dir = home_expand(dir);
+               ctx->dir = p_strdup(pool, home_expand(dir));
        }
 
-       dirp = opendir(dir);
-       if (dirp == NULL) {
+       ctx->dirp = opendir(ctx->dir);
+       if (ctx->dirp == NULL) {
                mail_storage_set_critical(storage, "opendir(%s) failed: %m",
-                                         dir);
-               return FALSE;
+                                         ctx->dir);
+               pool_unref(pool);
+               return NULL;
+       }
+
+       ctx->list_pool = pool_alloconly_create("maildir_list.list", 4096);
+       ctx->glob = imap_match_init(pool, mask, TRUE, '.');
+       ctx->next = maildir_list_next;
+       return ctx;
+}
+
+int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx)
+{
+       int failed;
+
+       if (ctx->subsfile_ctx != NULL)
+               failed = !subsfile_list_deinit(ctx->subsfile_ctx);
+       else
+               failed = ctx->failed;
+
+       if (ctx->dirp != NULL)
+               (void)closedir(ctx->dirp);
+       if (ctx->list_pool != NULL)
+               pool_unref(ctx->list_pool);
+       imap_match_deinit(ctx->glob);
+       pool_unref(ctx->pool);
+
+       return !failed;
+}
+
+static struct mailbox_list *maildir_list_subs(struct mailbox_list_context *ctx)
+{
+       struct stat st;
+       const char *name, *path, *p;
+       enum imap_match_result match = IMAP_MATCH_NO;
+
+       while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
+               match = imap_match(ctx->glob, name);
+               if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
+                       break;
+       }
+
+       if (name == NULL)
+               return NULL;
+
+       ctx->list.flags = 0;
+       ctx->list.name = name;
+
+       if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) != 0)
+               return &ctx->list;
+
+       if (match == IMAP_MATCH_PARENT) {
+               /* placeholder */
+               ctx->list.flags = MAILBOX_NOSELECT;
+               while ((p = strrchr(name, '.')) != NULL) {
+                       name = t_strdup_until(name, p);
+                       if (imap_match(ctx->glob, name) > 0) {
+                               ctx->list.name = name;
+                               return &ctx->list;
+                       }
+               }
+               i_unreached();
+       }
+
+       t_push();
+       path = maildir_get_path(ctx->storage, ctx->list.name);
+       if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+               ctx->list.flags = maildir_get_marked_flags(ctx->storage, path);
+       else {
+               if (strcasecmp(ctx->list.name, "INBOX") == 0)
+                       ctx->list.flags = 0;
+               else
+                       ctx->list.flags = MAILBOX_NOSELECT;
        }
+       t_pop();
+       return &ctx->list;
+}
 
-       glob = imap_match_init(mask, TRUE, '.');
+static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx)
+{
+       struct dirent *d;
+       struct stat st;
+       char path[PATH_MAX];
+       int ret;
 
-       failed = found_inbox = FALSE;
-       while ((d = readdir(dirp)) != NULL) {
+       if (ctx->dirp == NULL)
+               return NULL;
+
+       while ((d = readdir(ctx->dirp)) != NULL) {
                const char *fname = d->d_name;
 
                if (fname[0] != '.')
@@ -128,23 +240,24 @@ int maildir_find_mailboxes(struct mail_storage *storage, const char *mask,
                /* make sure the mask matches - dirs beginning with ".."
                   should be deleted and we always want to check those. */
                t_push();
-               ret = imap_match(glob, t_strconcat(prefix, fname+1, NULL));
+               ret = imap_match(ctx->glob,
+                                t_strconcat(ctx->prefix, fname+1, NULL));
                t_pop();
                if (fname[1] == '.' || ret <= 0)
                        continue;
 
-               if (str_path(path, sizeof(path), dir, fname) < 0)
+               if (str_path(path, sizeof(path), ctx->dir, fname) < 0)
                        continue;
 
                /* make sure it's a directory */
-               if (stat(path, &st) != 0) {
+               if (stat(path, &st) < 0) {
                        if (errno == ENOENT)
                                continue; /* just deleted, ignore */
 
-                       mail_storage_set_critical(storage,
+                       mail_storage_set_critical(ctx->storage,
                                                  "stat(%s) failed: %m", path);
-                       failed = TRUE;
-                       break;
+                       ctx->failed = TRUE;
+                       return NULL;
                }
 
                if (!S_ISDIR(st.st_mode))
@@ -162,60 +275,43 @@ int maildir_find_mailboxes(struct mail_storage *storage, const char *mask,
                }
 
                if (strcasecmp(fname+1, "INBOX") == 0) {
-                       if (found_inbox) {
+                       if (ctx->found_inbox) {
                                /* another inbox, ignore it */
                                continue;
                        }
-                       found_inbox = TRUE;
+                       ctx->found_inbox = TRUE;
                }
 
-               t_push();
-               flags = maildir_get_marked_flags(storage, path);
-               callback(storage, t_strconcat(prefix, fname+1, NULL),
-                        flags, context);
-               t_pop();
+               p_clear(ctx->list_pool);
+               if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) == 0) {
+                       ctx->list.flags =
+                               maildir_get_marked_flags(ctx->storage, path);
+               }
+               ctx->list.name = p_strconcat(ctx->list_pool,
+                                            ctx->prefix, fname+1, NULL);
+               return &ctx->list;
        }
 
-       if (!failed && !found_inbox && imap_match(glob, "INBOX") > 0) {
-               /* .INBOX directory doesn't exist yet, but INBOX still exists */
-               callback(storage, "INBOX", 0, context);
+       if (closedir(ctx->dirp) < 0) {
+               mail_storage_set_critical(ctx->storage,
+                                         "closedir(%s) failed: %m", ctx->dir);
+               ctx->failed = TRUE;
        }
+       ctx->dirp = NULL;
 
-       (void)closedir(dirp);
-       return !failed;
-}
-
-static int maildir_subs_cb(struct mail_storage *storage, const char *name,
-                          void *context)
-{
-       struct find_subscribed_context *ctx = context;
-       enum mailbox_flags flags;
-       struct stat st;
-       char path[PATH_MAX];
-
-       if (str_ppath(path, sizeof(path), storage->dir, ".", name) < 0)
-               flags = MAILBOX_NOSELECT;
-       else {
-               if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
-                       flags = maildir_get_marked_flags(storage, path);
-               else
-                       flags = MAILBOX_NOSELECT;
+       if (!ctx->found_inbox && imap_match(ctx->glob, "INBOX") > 0) {
+               /* .INBOX directory doesn't exist yet, but INBOX still exists */
+               ctx->list.flags = 0;
+               ctx->list.name = "INBOX";
+               return &ctx->list;
        }
 
-       ctx->callback(storage, name, flags, ctx->context);
-       return TRUE;
+       /* we're finished */
+       return NULL;
 }
 
-int maildir_find_subscribed(struct mail_storage *storage, const char *mask,
-                           mailbox_list_callback_t callback, void *context)
+struct mailbox_list *
+maildir_list_mailbox_next(struct mailbox_list_context *ctx)
 {
-       struct find_subscribed_context ctx;
-
-       ctx.callback = callback;
-       ctx.context = context;
-
-       if (subsfile_foreach(storage, mask, maildir_subs_cb, &ctx) <= 0)
-               return FALSE;
-
-       return TRUE;
+       return ctx->next(ctx);
 }
index 242cad375873fcd420d47631e9b36a949cb8b404..a13caa1bbadfaf56c6af250df071240ff3c27f8f 100644 (file)
@@ -120,8 +120,7 @@ static const char *maildir_get_absolute_path(const char *name)
        return t_strconcat(t_strdup_until(name, p), "/", p, NULL);
 }
 
-static const char *maildir_get_path(struct mail_storage *storage,
-                                   const char *name)
+const char *maildir_get_path(struct mail_storage *storage, const char *name)
 {
        if (full_filesystem_access && (*name == '/' || *name == '~'))
                return maildir_get_absolute_path(name);
@@ -431,43 +430,63 @@ static int rename_indexes(struct mail_storage *storage,
        return TRUE;
 }
 
-static void rename_subfolder(struct mail_storage *storage, const char *name,
-                            enum mailbox_flags flags __attr_unused__,
-                            void *context)
+static int rename_subfolders(struct mail_storage *storage,
+                            const char *oldname, const char *newname)
 {
-       struct rename_context *ctx = context;
-       const char *newname, *oldpath, *newpath;
-
-       i_assert(ctx->oldnamelen <= strlen(name));
-
-       newname = t_strconcat(ctx->newname, ".", name + ctx->oldnamelen, NULL);
-
-       oldpath = maildir_get_path(storage, name);
-       newpath = maildir_get_path(storage, newname);
-
-       /* FIXME: it's possible to merge two folders if either one of them
-          doesn't have existing root folder. We could check this but I'm not
-          sure if it's worth it. It could be even considered as a feature.
+       struct mailbox_list_context *ctx;
+        struct mailbox_list *list;
+       const char *oldpath, *newpath, *new_listname;
+       size_t oldnamelen;
+       int sorted, ret;
+
+       ret = 0;
+       oldnamelen = strlen(oldname);
+
+       ctx = storage->list_mailbox_init(storage,
+                                        t_strconcat(oldname, ".*", NULL),
+                                        MAILBOX_LIST_NO_FLAGS, &sorted);
+       while ((list = maildir_list_mailbox_next(ctx)) != NULL) {
+               i_assert(oldnamelen <= strlen(list->name));
+
+               t_push();
+               new_listname = t_strconcat(newname, ".",
+                                          list->name + oldnamelen, NULL);
+               oldpath = maildir_get_path(storage, list->name);
+               newpath = maildir_get_path(storage, new_listname);
+
+               /* FIXME: it's possible to merge two folders if either one of
+                  them doesn't have existing root folder. We could check this
+                  but I'm not sure if it's worth it. It could be even
+                  considered as a feature.
+
+                  Anyway, the bug with merging is that if both folders have
+                  identically named subfolder they conflict. Just ignore those
+                  and leave them under the old folder. */
+               if (rename(oldpath, newpath) == 0 ||
+                   errno == EEXIST || errno == ENOTEMPTY)
+                       ret = 1;
+               else {
+                       mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
+                                                 oldpath, newpath);
+                       ret = -1;
+                       t_pop();
+                       break;
+               }
 
-          Anyway, the bug with merging is that if both folders have
-          identically named subfolder they conflict. Just ignore those and
-          leave them under the old folder. */
-       if (rename(oldpath, newpath) == 0 || errno == EEXIST)
-               ctx->found = TRUE;
-       else {
-               mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
-                                         oldpath, newpath);
+               (void)rename_indexes(storage, list->name, new_listname);
+               t_pop();
        }
 
-       (void)rename_indexes(storage, name, newname);
+       if (!maildir_list_mailbox_deinit(ctx))
+               return -1;
+       return ret;
 }
 
 static int maildir_rename_mailbox(struct mail_storage *storage,
                                  const char *oldname, const char *newname)
 {
-       struct rename_context ctx;
        const char *oldpath, *newpath;
-       int ret;
+       int ret, found;
 
        mail_storage_clear_error(storage);
 
@@ -495,19 +514,16 @@ static int maildir_rename_mailbox(struct mail_storage *storage,
                if (!rename_indexes(storage, oldname, newname))
                        return FALSE;
 
-               ctx.found = ret == 0;
-               ctx.oldnamelen = strlen(oldname)+1;
-               ctx.newname = newname;
-               if (!maildir_find_mailboxes(storage,
-                                           t_strconcat(oldname, ".*", NULL),
-                                           rename_subfolder, &ctx))
+               found = ret == 0;
+               ret = rename_subfolders(storage, oldname, newname);
+               if (ret < 0)
                        return FALSE;
-
-               if (!ctx.found) {
+               if (!found && ret == 0) {
                        mail_storage_set_error(storage,
                                               "Mailbox doesn't exist");
                        return FALSE;
                }
+
                return TRUE;
        }
 
@@ -581,9 +597,10 @@ struct mail_storage maildir_storage = {
        maildir_create_mailbox,
        maildir_delete_mailbox,
        maildir_rename_mailbox,
-       maildir_find_mailboxes,
+       maildir_list_mailbox_init,
+       maildir_list_mailbox_deinit,
+       maildir_list_mailbox_next,
        subsfile_set_subscribed,
-       maildir_find_subscribed,
        maildir_get_mailbox_name_status,
        mail_storage_get_last_error,
 
index 2e68ff5dfdaf5713d76a21d7bf3118689560559d..4c57f5316d2800b2dbb93926e06739f02bf3589a 100644 (file)
@@ -14,14 +14,19 @@ int maildir_storage_save_next(struct mail_save_context *ctx,
                              time_t received_date, int timezone_offset,
                              struct istream *data);
 
-int maildir_find_mailboxes(struct mail_storage *storage, const char *mask,
-                          mailbox_list_callback_t callback, void *context);
-int maildir_find_subscribed(struct mail_storage *storage, const char *mask,
-                           mailbox_list_callback_t callback, void *context);
+struct mailbox_list_context *
+maildir_list_mailbox_init(struct mail_storage *storage,
+                         const char *mask, enum mailbox_list_flags flags,
+                         int *sorted);
+int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx);
+struct mailbox_list *
+maildir_list_mailbox_next(struct mailbox_list_context *ctx);
 
 int maildir_expunge_locked(struct index_mailbox *ibox, int notify);
 
 /* Return new filename base to save into tmp/ */
 const char *maildir_generate_tmp_filename(void);
 
+const char *maildir_get_path(struct mail_storage *storage, const char *name);
+
 #endif
index df4f8f7e4a4cf6303867dde4e93c37472c2a5a13..00be440c5f4c8fc73b707949f3f212de8af8407d 100644 (file)
 #include <dirent.h>
 #include <sys/stat.h>
 
-struct find_subscribed_context {
-       mailbox_list_callback_t *callback;
-       void *context;
+#define STAT_GET_MARKED(st) \
+       ((st).st_size != 0 && (st).st_atime < (st).st_ctime ? \
+        MAILBOX_MARKED : MAILBOX_UNMARKED)
+
+struct list_dir_context {
+       struct list_dir_context *prev;
+
+       DIR *dirp;
+       char *real_path, *virtual_path;
 };
 
-struct list_context {
+struct mailbox_list_context {
        struct mail_storage *storage;
+       enum mailbox_list_flags flags;
+
        struct imap_match_glob *glob;
-       mailbox_list_callback_t *callback;
-       void *context;
+       struct subsfile_list_context *subsfile_ctx;
 
-       const char *rootdir;
+       int failed;
+
+       struct mailbox_list *(*next)(struct mailbox_list_context *ctx);
+
+       pool_t list_pool;
+       struct mailbox_list list;
+        struct list_dir_context *dir;
 };
 
-static int mbox_find_path(struct list_context *ctx, const char *relative_dir)
+static struct mailbox_list *mbox_list_subs(struct mailbox_list_context *ctx);
+static struct mailbox_list *mbox_list_inbox(struct mailbox_list_context *ctx);
+static struct mailbox_list *mbox_list_path(struct mailbox_list_context *ctx);
+static struct mailbox_list *mbox_list_next(struct mailbox_list_context *ctx);
+
+static const char *mask_get_dir(const char *mask)
 {
-       DIR *dirp;
-       struct dirent *d;
-       struct stat st;
-       const char *dir, *listpath;
-       char fulldir[PATH_MAX], path[PATH_MAX], fullpath[PATH_MAX];
-       int failed, match;
-       size_t len;
+       const char *p, *last_dir;
 
-       t_push();
+       last_dir = NULL;
+       for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
+               if (*p == '/')
+                       last_dir = p;
+       }
 
-       if (relative_dir == NULL)
-               dir = ctx->rootdir;
-       else if (*ctx->rootdir == '\0' && *relative_dir != '\0')
-               dir = relative_dir;
-       else {
-               if (str_path(fulldir, sizeof(fulldir),
-                            ctx->rootdir, relative_dir) < 0) {
-                       mail_storage_set_critical(ctx->storage,
-                                                 "Path too long: %s",
-                                                 relative_dir);
-                       return FALSE;
-               }
+       return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir);
+}
 
-               dir = fulldir;
-       }
+static const char *mbox_get_path(struct mail_storage *storage, const char *name)
+{
+       if (!full_filesystem_access || name == NULL ||
+           (*name != '/' && *name != '~' && *name != '\0'))
+               return t_strconcat(storage->dir, "/", name, NULL);
+       else
+               return home_expand(name);
+}
 
-       dir = home_expand(dir);
-       dirp = opendir(dir);
-       if (dirp == NULL) {
-               t_pop();
+static int list_opendir(struct mail_storage *storage,
+                       const char *path, int root, DIR **dirp)
+{
+       *dirp = opendir(*path == '\0' ? "/" : path);
+       if (*dirp != NULL)
+               return 1;
+
+       if (!root && (errno == ENOENT || errno == ENOTDIR)) {
+               /* probably just race condition with other client
+                  deleting the mailbox. */
+               return 0;
+       }
 
-               if (relative_dir != NULL &&
-                   (errno == ENOENT || errno == ENOTDIR)) {
-                       /* probably just race condition with other client
-                          deleting the mailbox. */
-                       return TRUE;
+       if (errno == EACCES) {
+               if (!root) {
+                       /* subfolder, ignore */
+                       return 0;
                }
+               mail_storage_set_error(storage, "Access denied");
+               return -1;
+       }
 
-               if (errno == EACCES) {
-                       if (relative_dir != NULL) {
-                               /* subfolder, ignore */
-                               return TRUE;
-                       }
-                       mail_storage_set_error(ctx->storage, "Access denied");
-                       return FALSE;
-               }
+       mail_storage_set_critical(storage, "opendir(%s) failed: %m", path);
+       return -1;
+}
 
-               mail_storage_set_critical(ctx->storage,
-                                         "opendir(%s) failed: %m", dir);
-               return FALSE;
-       }
+struct mailbox_list_context *
+mbox_list_mailbox_init(struct mail_storage *storage, const char *mask,
+                      enum mailbox_list_flags flags, int *sorted)
+{
+       struct mailbox_list_context *ctx;
+       const char *path, *virtual_path;
+       DIR *dirp;
 
-       failed = FALSE;
-       while ((d = readdir(dirp)) != NULL) {
-               const char *fname = d->d_name;
+       *sorted = (flags & MAILBOX_LIST_SUBSCRIBED) == 0;
 
-               /* skip all hidden files */
-               if (fname[0] == '.')
-                       continue;
+       /* check that we're not trying to do any "../../" lists */
+       if (!mbox_is_valid_mask(mask)) {
+               mail_storage_set_error(storage, "Invalid mask");
+               return NULL;
+       }
 
-               /* skip all .lock files */
-               len = strlen(fname);
-               if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
-                       continue;
+       mail_storage_clear_error(storage);
 
-               /* check the mask */
-               if (relative_dir == NULL)
-                       listpath = fname;
-               else {
-                       if (str_path(path, sizeof(path),
-                                    relative_dir, fname) < 0) {
-                               mail_storage_set_critical(ctx->storage,
-                                       "Path too long: %s/%s",
-                                       relative_dir, fname);
-                               failed = TRUE;
-                               break;
-                       }
-                       listpath = path;
+       if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
+               ctx = i_new(struct mailbox_list_context, 1);
+               ctx->storage = storage;
+               ctx->flags = flags;
+               ctx->next = mbox_list_subs;
+               ctx->subsfile_ctx = subsfile_list_init(storage);
+               if (ctx->subsfile_ctx == NULL) {
+                       i_free(ctx);
+                       return NULL;
                }
+               ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
+               return ctx;
+       }
 
-               if ((match = imap_match(ctx->glob, listpath)) < 0)
-                       continue;
+       /* if we're matching only subdirectories, don't bother scanning the
+          parent directories */
+       virtual_path = mask_get_dir(mask);
+
+       path = mbox_get_path(storage, virtual_path);
+       if (list_opendir(storage, path, TRUE, &dirp) <= 0)
+               return NULL;
+
+       ctx = i_new(struct mailbox_list_context, 1);
+       ctx->storage = storage;
+       ctx->flags = flags;
+       ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
+       ctx->list_pool = pool_alloconly_create("mbox_list", 1024);
+
+       if (virtual_path == NULL && imap_match(ctx->glob, "INBOX") > 0)
+               ctx->next = mbox_list_inbox;
+       else if (virtual_path != NULL)
+               ctx->next = mbox_list_path;
+       else
+               ctx->next = mbox_list_next;
 
-               /* see if it's a directory */
-               if (str_path(fullpath, sizeof(fullpath), dir, fname) < 0) {
-                       mail_storage_set_critical(ctx->storage,
-                                                 "Path too long: %s/%s",
-                                                 dir, fname);
-                       failed = TRUE;
-                       break;
-               }
+       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;
+}
 
-               if (stat(fullpath, &st) < 0) {
-                       if (errno == ENOENT)
-                               continue; /* just deleted, ignore */
+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 mbox_list_mailbox_deinit(struct mailbox_list_context *ctx)
+{
+       int failed = ctx->failed;
 
-                       mail_storage_set_critical(ctx->storage,
-                                                 "stat(%s) failed: %m",
-                                                 fullpath);
+       if (ctx->subsfile_ctx != NULL) {
+               if (!subsfile_list_deinit(ctx->subsfile_ctx))
                        failed = TRUE;
-                       break;
-               }
+       }
 
-               if (S_ISDIR(st.st_mode)) {
-                       /* subdirectory, scan it too */
-                       t_push();
-                       ctx->callback(ctx->storage, listpath, MAILBOX_NOSELECT,
-                                     ctx->context);
-                       t_pop();
+       while (ctx->dir != NULL) {
+               struct list_dir_context *dir = ctx->dir;
 
-                       if (!mbox_find_path(ctx, listpath)) {
-                               failed = TRUE;
-                               break;
-                       }
-               } else if (match > 0 &&
-                          strcmp(fullpath, ctx->storage->inbox_file) != 0 &&
-                          strcasecmp(listpath, "INBOX") != 0) {
-                       /* don't match any INBOX here, it's added later.
-                          we might also have ~/mail/inbox, ~/mail/Inbox etc.
-                          Just ignore them for now. */
-                       t_push();
-                       ctx->callback(ctx->storage, listpath,
-                                     MAILBOX_NOINFERIORS, ctx->context);
-                       t_pop();
-               }
+               ctx->dir = dir->prev;
+                list_dir_context_free(dir);
        }
 
-       t_pop();
+       if (ctx->list_pool != NULL)
+               pool_unref(ctx->list_pool);
+       imap_match_deinit(ctx->glob);
+       i_free(ctx);
 
-       (void)closedir(dirp);
        return !failed;
 }
 
-static const char *mask_get_dir(const char *mask)
+struct mailbox_list *mbox_list_mailbox_next(struct mailbox_list_context *ctx)
 {
-       const char *p, *last_dir;
+       return ctx->next(ctx);
+}
 
-       last_dir = NULL;
-       for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
-               if (*p == '/')
-                       last_dir = p;
+static int list_file(struct mailbox_list_context *ctx, const char *fname)
+{
+        struct list_dir_context *dir;
+       const char *list_path, *real_path, *path;
+       struct stat st;
+       DIR *dirp;
+       size_t len;
+       enum imap_match_result match, match2;
+       int ret;
+
+       /* skip all hidden files */
+       if (fname[0] == '.')
+               return 0;
+
+       /* skip all .lock files */
+       len = strlen(fname);
+       if (len > 5 && strcmp(fname+len-5, ".lock") == 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);
        }
 
-       return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir);
-}
+       if ((match = imap_match(ctx->glob, list_path)) < 0)
+               return 0;
+
+       /* see if it's a directory */
+       real_path = t_strconcat(ctx->dir->real_path, "/", fname, NULL);
+       if (stat(real_path, &st) < 0) {
+               if (errno == ENOENT)
+                       return 0; /* just deleted, ignore */
+               mail_storage_set_critical(ctx->storage, "stat(%s) failed: %m",
+                                         real_path);
+               return -1;
+       }
 
-int mbox_find_mailboxes(struct mail_storage *storage, const char *mask,
-                       mailbox_list_callback_t callback, void *context)
-{
-        struct list_context ctx;
-       struct imap_match_glob *glob;
-       const char *relative_dir;
+       if (S_ISDIR(st.st_mode)) {
+               /* subdirectory. scan inside it. */
+               path = t_strconcat(list_path, "/", NULL);
+               match2 = imap_match(ctx->glob, path);
+
+               if (match > 0) {
+                       ctx->list.flags = MAILBOX_NOSELECT;
+                       ctx->list.name = p_strdup(ctx->list_pool, list_path);
+               } else if (match2 > 0) {
+                       ctx->list.flags = MAILBOX_NOSELECT;
+                       ctx->list.name = p_strdup(ctx->list_pool, path);
+               }
 
-       /* check that we're not trying to do any "../../" lists */
-       if (!mbox_is_valid_mask(mask)) {
-               mail_storage_set_error(storage, "Invalid mask");
-               return FALSE;
+               ret = match2 < 0 ? 0 :
+                       list_opendir(ctx->storage, 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 &&
+                  strcmp(real_path, ctx->storage->inbox_file) != 0 &&
+                  strcasecmp(list_path, "INBOX") != 0) {
+               /* don't match any INBOX here, it's added separately.
+                  we might also have ~/mail/inbox, ~/mail/Inbox etc.
+                  Just ignore them for now. */
+               ctx->list.flags = MAILBOX_NOINFERIORS | STAT_GET_MARKED(st);
+               ctx->list.name = p_strdup(ctx->list_pool, list_path);
+               return 1;
        }
 
-       mail_storage_clear_error(storage);
+       return 0;
+}
 
-       /* if we're matching only subdirectories, don't bother scanning the
-          parent directories */
-       relative_dir = mask_get_dir(mask);
+static struct mailbox_list *mbox_list_subs(struct mailbox_list_context *ctx)
+{
+       struct stat st;
+       const char *name, *path, *p;
+       enum imap_match_result match = IMAP_MATCH_NO;
 
-       glob = imap_match_init(mask, TRUE, '/');
-       if (relative_dir == NULL && imap_match(glob, "INBOX") > 0) {
-               /* INBOX exists always, even if the file doesn't. */
-               callback(storage, "INBOX", MAILBOX_NOINFERIORS, context);
+       while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
+               match = imap_match(ctx->glob, name);
+               if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
+                       break;
        }
 
-       memset(&ctx, 0, sizeof(ctx));
-       ctx.storage = storage;
-       ctx.glob = glob;
-       ctx.callback = callback;
-       ctx.context = context;
+       if (name == NULL)
+               return NULL;
 
-       if (!full_filesystem_access || relative_dir == NULL ||
-           (*relative_dir != '/' && *relative_dir != '~' &&
-            *relative_dir != '\0'))
-               ctx.rootdir = storage->dir;
-       else
-               ctx.rootdir = "";
+       ctx->list.flags = 0;
+       ctx->list.name = name;
 
-       if (relative_dir != NULL) {
-               const char *matchdir = t_strconcat(relative_dir, "/", NULL);
+       if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) != 0)
+               return &ctx->list;
 
-               if (imap_match(ctx.glob, matchdir) > 0) {
-                       t_push();
-                       ctx.callback(ctx.storage, matchdir, MAILBOX_NOSELECT,
-                                    ctx.context);
-                       t_pop();
+       if (match == IMAP_MATCH_PARENT) {
+               /* placeholder */
+               ctx->list.flags = MAILBOX_NOSELECT;
+               while ((p = strrchr(name, '/')) != NULL) {
+                       name = t_strdup_until(name, p);
+                       if (imap_match(ctx->glob, name) > 0) {
+                               ctx->list.name = name;
+                               return &ctx->list;
+                       }
                }
+               i_unreached();
        }
 
-       if (!mbox_find_path(&ctx, relative_dir))
-               return FALSE;
-
-       return TRUE;
+       t_push();
+       path = mbox_get_path(ctx->storage, ctx->list.name);
+       if (stat(path, &st) == 0) {
+               if (S_ISDIR(st.st_mode))
+                       ctx->list.flags = MAILBOX_NOSELECT;
+               else {
+                       ctx->list.flags = MAILBOX_NOINFERIORS |
+                               STAT_GET_MARKED(st);
+               }
+       } else {
+               if (strcasecmp(ctx->list.name, "INBOX") == 0)
+                       ctx->list.flags = MAILBOX_UNMARKED;
+               else
+                       ctx->list.flags = MAILBOX_NOSELECT;
+       }
+       t_pop();
+       return &ctx->list;
 }
 
-static int mbox_subs_cb(struct mail_storage *storage, const char *name,
-                       void *context)
+static struct mailbox_list *mbox_list_inbox(struct mailbox_list_context *ctx)
 {
-       struct find_subscribed_context *ctx = context;
-       enum mailbox_flags flags;
        struct stat st;
-       char path[PATH_MAX];
 
-       /* see if the mailbox exists, don't bother with the marked flags */
-       if (strcasecmp(name, "INBOX") == 0) {
-               /* inbox always exists */
-               flags = 0;
-       } else {
-               flags = str_path(path, sizeof(path), storage->dir, name) == 0 &&
-                       stat(path, &st) == 0 && !S_ISDIR(st.st_mode) ?
-                       0 : MAILBOX_NOSELECT;
+       if (ctx->dir->virtual_path != NULL)
+               ctx->next = mbox_list_path;
+       else
+               ctx->next = mbox_list_next;
+
+       /* INBOX exists always, even if the file doesn't. */
+       ctx->list.flags = MAILBOX_NOINFERIORS;
+       if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) == 0) {
+               if (stat(ctx->storage->inbox_file, &st) < 0)
+                       ctx->list.flags |= MAILBOX_UNMARKED;
+               else
+                       ctx->list.flags |= STAT_GET_MARKED(st);
        }
 
-       ctx->callback(storage, name, flags, ctx->context);
-       return TRUE;
+       ctx->list.name = "INBOX";
+       return &ctx->list;
+}
+
+static struct mailbox_list *mbox_list_path(struct mailbox_list_context *ctx)
+{
+       ctx->next = mbox_list_next;
+
+       ctx->list.flags = MAILBOX_NOSELECT;
+       ctx->list.name = p_strconcat(ctx->list_pool,
+                                    ctx->dir->virtual_path, "/", NULL);
+
+       if (imap_match(ctx->glob, ctx->list.name) > 0)
+               return &ctx->list;
+       else
+               return ctx->next(ctx);
 }
 
-int mbox_find_subscribed(struct mail_storage *storage, const char *mask,
-                        mailbox_list_callback_t callback, void *context)
+static struct mailbox_list *mbox_list_next(struct mailbox_list_context *ctx)
 {
-       struct find_subscribed_context ctx;
+       struct list_dir_context *dir;
+       struct dirent *d;
+       int ret;
 
-       ctx.callback = callback;
-       ctx.context = context;
+       p_clear(ctx->list_pool);
 
-       if (subsfile_foreach(storage, mask, mbox_subs_cb, &ctx) <= 0)
-               return FALSE;
+       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->d_name);
+                       t_pop();
+
+                       if (ret > 0)
+                               return &ctx->list;
+                       if (ret < 0) {
+                               ctx->failed = TRUE;
+                               return NULL;
+                       }
+               }
+
+               dir = ctx->dir;
+               ctx->dir = dir->prev;
+               list_dir_context_free(dir);
+       }
 
-       return TRUE;
+       /* finished */
+       return NULL;
 }
index b4491b4b7a2ad5137d04f6b7efc53f8ad98aa0e5..47155c70a83b0d24d33e3e6ab88d750bd429f90b 100644 (file)
@@ -645,9 +645,10 @@ struct mail_storage mbox_storage = {
        mbox_create_mailbox,
        mbox_delete_mailbox,
        mbox_rename_mailbox,
-       mbox_find_mailboxes,
+       mbox_list_mailbox_init,
+       mbox_list_mailbox_deinit,
+       mbox_list_mailbox_next,
        subsfile_set_subscribed,
-       mbox_find_subscribed,
        mbox_get_mailbox_name_status,
        mail_storage_get_last_error,
 
index 48c02d2e5cf8c575b7de3b084d68acd3e146a122..427c6282fad47be301651e176f49c099380d18b1 100644 (file)
@@ -14,10 +14,11 @@ int mbox_storage_save_next(struct mail_save_context *ctx,
                           time_t received_date, int timezone_offset,
                           struct istream *data);
 
-int mbox_find_mailboxes(struct mail_storage *storage, const char *mask,
-                       mailbox_list_callback_t callback, void *context);
-int mbox_find_subscribed(struct mail_storage *storage, const char *mask,
-                        mailbox_list_callback_t callback, void *context);
+struct mailbox_list_context *
+mbox_list_mailbox_init(struct mail_storage *storage, const char *mask,
+                      enum mailbox_list_flags flags, int *sorted);
+int mbox_list_mailbox_deinit(struct mailbox_list_context *ctx);
+struct mailbox_list *mbox_list_mailbox_next(struct mailbox_list_context *ctx);
 
 int mbox_expunge_locked(struct index_mailbox *ibox, int notify);
 
index 8de8bbb84928bcba6e4d902cb85f8aa8df98fbeb..58c008d8115d742199dcc8bc9fec646ca46c1da7 100644 (file)
@@ -23,6 +23,7 @@ struct client_workaround_list {
 
 struct client_workaround_list client_workaround_list[] = {
        { "oe6-fetch-no-newmail", WORKAROUND_OE6_FETCH_NO_NEWMAIL },
+       { "list-sort", WORKAROUND_LIST_SORT },
        { NULL, 0 }
 };
 
@@ -48,11 +49,13 @@ void mail_storage_init(void)
 
                list = client_workaround_list;
                for (; list->name != NULL; list++) {
-                       if (strcasecmp(*str, list->name) == 0)
+                       if (strcasecmp(*str, list->name) == 0) {
                                client_workarounds |= list->num;
-                       else
-                               i_fatal("Unknown client workaround: %s", *str);
+                               break;
+                       }
                }
+               if (list->name == NULL)
+                       i_fatal("Unknown client workaround: %s", *str);
        }
 }
 
index 62e16d319a76aa30d818a9344d74d1a4e86a4e86..af969ff8d7a17d6bbc91e1629552d93a28c57a15 100644 (file)
@@ -5,6 +5,11 @@ struct message_size;
 
 #include "imap-util.h"
 
+enum mailbox_list_flags {
+       MAILBOX_LIST_SUBSCRIBED = 0x01, /* show only subscribed */
+       MAILBOX_LIST_NO_FLAGS   = 0x02  /* don't set mailbox_flags */
+};
+
 enum mailbox_flags {
        MAILBOX_NOSELECT        = 0x01,
        MAILBOX_CHILDREN        = 0x02,
@@ -86,7 +91,8 @@ enum mail_fetch_field {
 };
 
 enum client_workarounds {
-       WORKAROUND_OE6_FETCH_NO_NEWMAIL = 0x01
+       WORKAROUND_OE6_FETCH_NO_NEWMAIL = 0x01,
+       WORKAROUND_LIST_SORT            = 0x02
 };
 
 struct mail_full_flags {
@@ -98,15 +104,12 @@ struct mail_full_flags {
 
 struct mail_storage;
 struct mail_storage_callbacks;
+struct mailbox_list;
 struct mailbox_status;
 struct mail_search_arg;
 struct fetch_context;
 struct search_context;
 
-typedef void mailbox_list_callback_t(struct mail_storage *storage,
-                                    const char *name, enum mailbox_flags flags,
-                                    void *context);
-
 /* All methods returning int return either TRUE or FALSE. */
 struct mail_storage {
        char *name;
@@ -156,10 +159,22 @@ struct mail_storage {
        int (*rename_mailbox)(struct mail_storage *storage, const char *oldname,
                              const char *newname);
 
-       /* Execute specified function for all mailboxes matching given
-          mask. The mask is in RFC2060 LIST format. */
-       int (*find_mailboxes)(struct mail_storage *storage, const char *mask,
-                             mailbox_list_callback_t *callback, void *context);
+       /* Initialize new mailbox list request. mask may contain '%' and '*'
+          wildcards as defined in RFC2060. Matching against "INBOX" is
+          case-insensitive, but anything else is not. *sorted is set to TRUE
+          if the output will contain parent mailboxes always before their
+          children. */
+       struct mailbox_list_context *
+               (*list_mailbox_init)(struct mail_storage *storage,
+                                    const char *mask,
+                                    enum mailbox_list_flags flags,
+                                    int *sorted);
+       /* Deinitialize mailbox list request. Returns FALSE if some error
+          occured while listing. */
+       int (*list_mailbox_deinit)(struct mailbox_list_context *ctx);
+       /* Get next mailbox. Returns the mailbox name */
+       struct mailbox_list *
+               (*list_mailbox_next)(struct mailbox_list_context *ctx);
 
        /* Subscribe/unsubscribe mailbox. There should be no error when
           subscribing to already subscribed mailbox. Subscribing to
@@ -167,11 +182,6 @@ struct mail_storage {
        int (*set_subscribed)(struct mail_storage *storage,
                              const char *name, int set);
 
-       /* Exactly like find_mailboxes(), but list only subscribed mailboxes. */
-       int (*find_subscribed)(struct mail_storage *storage, const char *mask,
-                              mailbox_list_callback_t *callback,
-                              void *context);
-
        /* Returns mailbox name status */
        int (*get_mailbox_name_status)(struct mail_storage *storage,
                                       const char *name,
@@ -356,6 +366,11 @@ struct mail {
                                   enum mail_fetch_field field);
 };
 
+struct mailbox_list {
+       const char *name;
+        enum mailbox_flags flags;
+};
+
 struct mailbox_status {
        unsigned int messages;
        unsigned int recent;
index 9646d5aa200a196b360fbc24f7b720086e0c41b7..d1faaeee3e0fc8dcf9e55ef46bd976e5da4bc8d4 100644 (file)
@@ -1,12 +1,10 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-/* ugly code here - text files are annoying to manage */
+/* Copyright (C) 2002-2003 Timo Sirainen */
 
 #include "lib.h"
+#include "istream.h"
+#include "ostream.h"
 #include "file-lock.h"
-#include "mmap-util.h"
 #include "write-full.h"
-#include "imap-match.h"
 #include "mail-storage.h"
 #include "subscription-file.h"
 
 #include <fcntl.h>
 
 #define SUBSCRIPTION_FILE_NAME ".subscriptions"
+#define MAX_MAILBOX_LENGTH PATH_MAX
+
+struct subsfile_list_context {
+       pool_t pool;
+
+       struct mail_storage *storage;
+       struct istream *input;
+       const char *path;
+
+       int failed;
+};
 
 static int subsfile_set_syscall_error(struct mail_storage *storage,
                                      const char *path, const char *function)
@@ -27,8 +36,7 @@ static int subsfile_set_syscall_error(struct mail_storage *storage,
 }
 
 static int subscription_open(struct mail_storage *storage, int update,
-                            const char **path, void **mmap_base,
-                            size_t *mmap_length)
+                            const char **path)
 {
        int fd;
 
@@ -42,7 +50,7 @@ static int subscription_open(struct mail_storage *storage, int update,
                        return -1;
                }
 
-               return -2;
+               return -1;
        }
 
        /* FIXME: we should work without locking, rename() would be easiest
@@ -52,115 +60,108 @@ static int subscription_open(struct mail_storage *storage, int update,
                (void)close(fd);
                return -1;
        }
+       return fd;
+}
 
-       *mmap_base = update ? mmap_rw_file(fd, mmap_length) :
-               mmap_ro_file(fd, mmap_length);
-       if (*mmap_base == MAP_FAILED) {
-               *mmap_base = NULL;
-               subsfile_set_syscall_error(storage, "mmap()", *path);
-               (void)close(fd);
-               return -1;
+static const char *next_line(struct mail_storage *storage, const char *path,
+                            struct istream *input, int *failed)
+{
+       const char *line;
+
+       while ((line = i_stream_next_line(input)) == NULL) {
+               switch (i_stream_read(input)) {
+               case -1:
+                       *failed = FALSE;
+                       return NULL;
+               case -2:
+                       /* mailbox name too large */
+                       mail_storage_set_critical(storage,
+                               "Subscription file %s contains lines longer "
+                               "than %u characters", path,
+                               MAX_MAILBOX_LENGTH);
+                       *failed = TRUE;
+                       return NULL;
+               }
        }
 
-       (void)madvise(*mmap_base, *mmap_length, MADV_SEQUENTIAL);
-       return fd;
+       *failed = FALSE;
+       return line;
 }
 
-static int subscription_append(struct mail_storage *storage, int fd,
-                              const char *name, size_t len, int prefix_lf,
-                              const char *path)
+static int stream_cut(struct mail_storage *storage, const char *path,
+                     struct istream *input, uoff_t count)
 {
-       char *buf;
-
-       if (lseek(fd, 0, SEEK_END) < 0)
-               return subsfile_set_syscall_error(storage, "lseek()", path);
-
-       /* @UNSAFE */
-       buf = t_buffer_get(len+2);
-       buf[0] = '\n';
-       memcpy(buf+1, name, len);
-       buf[len+1] = '\n';
-
-       if (prefix_lf)
-               len += 2;
-       else {
-               buf++;
-               len++;
+       struct ostream *output;
+       int fd, failed;
+
+       fd = i_stream_get_fd(input);
+       i_assert(fd != -1);
+
+       output = o_stream_create_file(fd, default_pool, 4096, 0, 0);
+       if (o_stream_seek(output, input->start_offset + input->v_offset) < 0) {
+               failed = TRUE;
+               errno = output->stream_errno;
+               subsfile_set_syscall_error(storage, "o_stream_seek()", path);
+       } else {
+               i_stream_skip(input, count);
+               failed = o_stream_send_istream(output, input) < 0;
+               if (failed) {
+                       errno = output->stream_errno;
+                       subsfile_set_syscall_error(storage,
+                                                  "o_stream_send_istream()",
+                                                  path);
+               }
        }
 
-       if (write_full(fd, buf, len) < 0) {
-               subsfile_set_syscall_error(storage, "write_full()", path);
-               return FALSE;
+       if (!failed) {
+               if (ftruncate(fd, output->offset) < 0) {
+                       subsfile_set_syscall_error(storage, "ftruncate()",
+                                                  path);
+                       failed = TRUE;
+               }
        }
 
-       return TRUE;
+       o_stream_unref(output);
+       return !failed;
 }
 
 int subsfile_set_subscribed(struct mail_storage *storage,
                            const char *name, int set)
 {
-       void *mmap_base;
-       size_t mmap_length;
-       const char *path;
-       char *subscriptions, *end, *p;
-       size_t namelen, afterlen, removelen;
-       int fd,  failed, prefix_lf;
+       const char *path, *line;
+       struct istream *input;
+       uoff_t offset;
+       int fd, failed;
 
        if (strcasecmp(name, "INBOX") == 0)
                name = "INBOX";
 
-       fd = subscription_open(storage, TRUE, &path, &mmap_base, &mmap_length);
+       fd = subscription_open(storage, TRUE, &path);
        if (fd == -1)
                return FALSE;
 
-       namelen = strlen(name);
-
-       subscriptions = mmap_base;
-       if (subscriptions == NULL)
-               p = NULL;
-       else {
-               end = subscriptions + mmap_length;
-               for (p = subscriptions; p != end; p++) {
-                       if (*p == *name && p+namelen <= end &&
-                           strncmp(p, name, namelen) == 0) {
-                               /* make sure beginning and end matches too */
-                               if ((p == subscriptions || p[-1] == '\n') &&
-                                   (p+namelen == end || p[namelen] == '\n'))
-                                       break;
-                       }
+       input = i_stream_create_file(fd, default_pool,
+                                    MAX_MAILBOX_LENGTH, FALSE);
+       do {
+               offset = input->v_offset;
+                line = next_line(storage, path, input, &failed);
+       } while (line != NULL && strcmp(line, name) != 0);
+
+       if (!failed) {
+               if (set && line == NULL) {
+                       /* add subscription. we're at EOF so just write it */
+                       write_full(fd, t_strconcat(name, "\n", NULL),
+                                  strlen(name)+1);
+               } else if (!set && line != NULL) {
+                       /* remove subcription. */
+                       uoff_t size = input->v_offset - offset;
+                       i_stream_seek(input, offset);
+                       if (!stream_cut(storage, path, input, size))
+                               failed = TRUE;
                }
-
-               if (p == end)
-                       p = NULL;
        }
 
-       failed = FALSE;
-       if (p != NULL && !set) {
-               /* remove it */
-               afterlen = mmap_length - (size_t) (p - subscriptions);
-               removelen = namelen < afterlen ? namelen+1 : namelen;
-
-               if (removelen < afterlen)
-                       memmove(p, p+removelen, afterlen-removelen);
-
-               if (ftruncate(fd, (off_t) (mmap_length - removelen)) == -1) {
-                       subsfile_set_syscall_error(storage, "ftruncate()",
-                                                  path);
-                       failed = TRUE;
-               }
-       } else if (p == NULL && set) {
-               /* append it */
-               prefix_lf = mmap_length > 0 &&
-                       subscriptions[mmap_length-1] != '\n';
-               if (!subscription_append(storage, fd, name, namelen,
-                                        prefix_lf, path))
-                       failed = TRUE;
-       }
-
-       if (mmap_base != NULL && munmap(mmap_base, mmap_length) < 0) {
-               subsfile_set_syscall_error(storage, "munmap()", path);
-               failed = TRUE;
-       }
+       i_stream_unref(input);
 
        if (close(fd) < 0) {
                subsfile_set_syscall_error(storage, "close()", path);
@@ -169,45 +170,45 @@ int subsfile_set_subscribed(struct mail_storage *storage,
        return !failed;
 }
 
-int subsfile_foreach(struct mail_storage *storage, const char *mask,
-                    subsfile_foreach_callback_t *callback, void *context)
+struct subsfile_list_context *
+subsfile_list_init(struct mail_storage *storage)
 {
-        struct imap_match_glob *glob;
-       const char *path, *start, *end, *p, *line;
-       void *mmap_base;
-       size_t mmap_length;
-       int fd, ret;
-
-       fd = subscription_open(storage, FALSE, &path, &mmap_base, &mmap_length);
-       if (fd < 0) {
-               /* -2 = no subscription file, ignore */
-               return fd == -1 ? -1 : 1;
-       }
+       struct subsfile_list_context *ctx;
+       pool_t pool;
+       const char *path;
+       int fd;
 
-       glob = imap_match_init(mask, TRUE, storage->hierarchy_sep);
+       fd = subscription_open(storage, FALSE, &path);
+       if (fd == -1 && errno != ENOENT)
+               return NULL;
 
-       start = mmap_base; end = start + mmap_length; ret = 1;
-       while (ret) {
-               t_push();
+       pool = pool_alloconly_create("subsfile_list", MAX_MAILBOX_LENGTH+1024);
 
-               for (p = start; p != end; p++) {
-                       if (*p == '\n')
-                               break;
-               }
+       ctx = p_new(pool, struct subsfile_list_context, 1);
+       ctx->pool = pool;
+       ctx->storage = storage;
+       ctx->input = fd == -1 ? NULL :
+               i_stream_create_file(fd, pool, MAX_MAILBOX_LENGTH, TRUE);
+       ctx->path = p_strdup(pool, path);
+       return ctx;
+}
 
-               line = t_strdup_until(start, p);
-               if (line != NULL && *line != '\0' && imap_match(glob, line) > 0)
-                       ret = callback(storage, line, context);
-               t_pop();
+int subsfile_list_deinit(struct subsfile_list_context *ctx)
+{
+       int failed;
 
-               if (p == end)
-                       break;
-               start = p+1;
-       }
+       failed = ctx->failed;
+       if (ctx->input != NULL)
+               i_stream_unref(ctx->input);
+       pool_unref(ctx->pool);
 
-       if (mmap_base != NULL && munmap(mmap_base, mmap_length) < 0)
-               subsfile_set_syscall_error(storage, "munmap()", path);
-       if (close(fd) < 0)
-               subsfile_set_syscall_error(storage, "close()", path);
-       return ret;
+       return !failed;
+}
+
+const char *subsfile_list_next(struct subsfile_list_context *ctx)
+{
+       if (ctx->failed || ctx->input == NULL)
+               return NULL;
+
+       return next_line(ctx->storage, ctx->path, ctx->input, &ctx->failed);
 }
index 2add661bfa608776673ee0bed348b05c931fae36..40c7e025178a31c4cf5199147218f64e628693cf 100644 (file)
@@ -3,15 +3,17 @@
 
 #include "mail-storage.h"
 
-/* Returns FALSE if foreach should be aborted */
-typedef int subsfile_foreach_callback_t(struct mail_storage *storage,
-                                       const char *name, void *context);
+/* Initialize new subscription file listing. Returns NULL if failed. */
+struct subsfile_list_context *
+subsfile_list_init(struct mail_storage *storage);
+
+/* Deinitialize subscription file listing. Returns FALSE if some error occured
+   while listing. */
+int subsfile_list_deinit(struct subsfile_list_context *ctx);
+/* Returns the next subscribed mailbox, or NULL. */
+const char *subsfile_list_next(struct subsfile_list_context *ctx);
 
 int subsfile_set_subscribed(struct mail_storage *storage,
                            const char *name, int set);
 
-/* Returns -1 if error, 0 if foreach function returned FALSE or 1 if all ok */
-int subsfile_foreach(struct mail_storage *storage, const char *mask,
-                    subsfile_foreach_callback_t *callback, void *context);
-
 #endif