Mercurial > dovecot > original-hg > dovecot-1.2
changeset 5811:e0b451e0c190 HEAD
Handle listing flags correctly for all namespace prefixes.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 27 Jun 2007 20:21:00 +0300 |
parents | f56a71347378 |
children | 71176467310e |
files | src/imap/cmd-list.c |
diffstat | 1 files changed, 180 insertions(+), 73 deletions(-) [+] |
line wrap: on
line diff
--- a/src/imap/cmd-list.c Wed Jun 27 20:19:49 2007 +0300 +++ b/src/imap/cmd-list.c Wed Jun 27 20:21:00 2007 +0300 @@ -1,6 +1,7 @@ -/* Copyright (C) 2002-2004 Timo Sirainen */ +/* Copyright (C) 2002-2007 Timo Sirainen */ #include "common.h" +#include "array.h" #include "str.h" #include "strescape.h" #include "imap-quote.h" @@ -14,6 +15,7 @@ }; struct cmd_list_context { + struct client_command_context *cmd; const char *ref; const char *mask; enum mailbox_list_flags list_flags; @@ -22,9 +24,14 @@ struct mailbox_list_iterate_context *list_iter; struct imap_match_glob *glob; + ARRAY_DEFINE(ns_prefixes_listed, struct mail_namespace *); + unsigned int lsub:1; unsigned int inbox_found:1; - unsigned int match_inbox:1; + unsigned int seen_inbox_namespace:1; + unsigned int cur_ns_match_inbox:1; + unsigned int cur_ns_send_prefix:1; + unsigned int cur_ns_skip_trailing_sep:1; }; static void @@ -48,10 +55,12 @@ str_append(str, "\\NonExistent "); if ((flags & MAILBOX_CHILDREN) != 0) str_append(str, "\\HasChildren "); - if ((flags & MAILBOX_NOCHILDREN) != 0) - str_append(str, "\\HasNoChildren "); - if ((flags & MAILBOX_NOINFERIORS) != 0) - str_append(str, "\\NoInferiors "); + else { + if ((flags & MAILBOX_NOCHILDREN) != 0) + str_append(str, "\\HasNoChildren "); + if ((flags & MAILBOX_NOINFERIORS) != 0) + str_append(str, "\\NoInferiors "); + } if ((flags & MAILBOX_MARKED) != 0) str_append(str, "\\Marked "); if ((flags & MAILBOX_UNMARKED) != 0) @@ -90,19 +99,102 @@ return TRUE; } -static void -list_namespace_inbox(struct client *client, struct cmd_list_context *ctx) +static enum mailbox_info_flags +list_get_inbox_flags(struct cmd_list_context *ctx) { - const char *str; + struct mail_namespace *ns; + struct mailbox_list_iterate_context *list_iter; + const struct mailbox_info *info; + enum mailbox_info_flags flags = MAILBOX_UNMARKED; + + if (ctx->seen_inbox_namespace && + (ctx->ns->flags & NAMESPACE_FLAG_INBOX) == 0) { + /* INBOX doesn't exist. use the default INBOX flags */ + return flags; + } + + /* find the INBOX flags */ + ns = mail_namespace_find_inbox(ctx->cmd->client->namespaces); + list_iter = mailbox_list_iter_init(ns->list, "INBOX", 0); + info = mailbox_list_iter_next(list_iter); + if (info != NULL) { + i_assert(strcasecmp(info->name, "INBOX") == 0); + flags = info->flags; + } + (void)mailbox_list_iter_deinit(&list_iter); + return flags; +} + +static bool list_namespace_has_children(struct cmd_list_context *ctx) +{ + struct mailbox_list_iterate_context *list_iter; + const struct mailbox_info *info; + bool ret = FALSE; + + list_iter = mailbox_list_iter_init(ctx->ns->list, "%", + MAILBOX_LIST_ITER_FAST_FLAGS); + info = mailbox_list_iter_next(list_iter); + if (info != NULL) + ret = TRUE; + if (mailbox_list_iter_deinit(&list_iter) < 0) { + /* safer to answer TRUE in error conditions */ + ret = TRUE; + } + return ret; +} - if (!ctx->inbox_found && ctx->match_inbox && - (ctx->ns->flags & NAMESPACE_FLAG_INBOX) != 0 && - (ctx->list_flags & MAILBOX_LIST_ITER_SUBSCRIBED) == 0) { - /* INBOX always exists */ - str = t_strdup_printf("* LIST (\\Unmarked) \"%s\" \"INBOX\"", - ctx->ns->sep_str); - client_send_line(client, str); +static void +list_namespace_send_prefix(struct cmd_list_context *ctx, bool have_children) +{ + struct mail_namespace *const *listed; + unsigned int i, count, len; + enum mailbox_info_flags flags; + const char *name; + string_t *str; + + ctx->cur_ns_send_prefix = FALSE; + + /* see if we already listed this as a valid mailbox in another + namespace */ + listed = array_get(&ctx->ns_prefixes_listed, &count); + for (i = 0; i < count; i++) { + if (listed[i] == ctx->ns) + return; } + + len = strlen(ctx->ns->prefix); + if (len == 6 && strncasecmp(ctx->ns->prefix, "INBOX", len-1) == 0 && + ctx->ns->prefix[len-1] == ctx->ns->sep) { + /* INBOX namespace needs to be handled specially. */ + if (ctx->inbox_found) { + /* we're just now going to send it */ + return; + } + + ctx->inbox_found = TRUE; + flags = list_get_inbox_flags(ctx); + } else { + flags = MAILBOX_NONEXISTENT; + } + + if ((flags & MAILBOX_CHILDREN) == 0) { + if (have_children || list_namespace_has_children(ctx)) { + flags |= MAILBOX_CHILDREN; + flags &= ~MAILBOX_NOCHILDREN; + } else { + flags |= MAILBOX_NOCHILDREN; + } + } + + str = t_str_new(128); + str_append(str, "* LIST ("); + mailbox_flags2str(str, flags, ctx->list_flags); + str_printfa(str, ") \"%s\" ", ctx->ns->sep_str); + + name = ctx->cur_ns_skip_trailing_sep ? + t_strndup(ctx->ns->prefix, len-1) : ctx->ns->prefix; + imap_quote_append_string(str, name, FALSE); + client_send_line(ctx->cmd->client, str_c(str)); } static bool @@ -117,7 +209,7 @@ /* no namespace prefix, we can't list this */ return FALSE; } - } else if (!ctx->match_inbox) { + } else if (!ctx->cur_ns_match_inbox) { /* The mask doesn't match INBOX (eg. prefix.%). We still want to list prefix.INBOX if it has children. Otherwise we don't want to list @@ -134,18 +226,15 @@ } static int -list_namespace_mailboxes(struct client *client, struct cmd_list_context *ctx) +list_namespace_mailboxes(struct cmd_list_context *ctx) { const struct mailbox_info *info; + struct mail_namespace *ns; + enum mailbox_info_flags flags; + string_t *str, *name_str; const char *name; - string_t *str, *name_str; int ret = 0; - if (ctx->list_iter == NULL) { - list_namespace_inbox(client, ctx); - return 1; - } - t_push(); str = t_str_new(256); name_str = t_str_new(256); @@ -164,6 +253,7 @@ } } name = str_c(name_str); + flags = info->flags; if (*ctx->ns->prefix != '\0') { /* With masks containing '*' we do the checks here @@ -173,19 +263,32 @@ continue; } if (strcasecmp(name, "INBOX") == 0) { - if ((ctx->ns->flags & NAMESPACE_FLAG_INBOX) == 0) + i_assert((ctx->ns->flags & NAMESPACE_FLAG_INBOX) != 0); + if (ctx->inbox_found) { + /* we already listed this at the beginning + of handling INBOX/ namespace */ continue; + } + ctx->inbox_found = TRUE; + } + if (ctx->cur_ns_send_prefix) + list_namespace_send_prefix(ctx, TRUE); - name = "INBOX"; - ctx->inbox_found = TRUE; + /* if there's a namespace with this name, list it as + having children */ + ns = mail_namespace_find_prefix_nosep(ctx->ns, name); + if (ns != NULL) { + flags |= MAILBOX_CHILDREN; + flags &= ~MAILBOX_NOCHILDREN; + array_append(&ctx->ns_prefixes_listed, &ns, 1); } str_truncate(str, 0); str_printfa(str, "* %s (", ctx->lsub ? "LSUB" : "LIST"); - mailbox_flags2str(str, info->flags, ctx->list_flags); + mailbox_flags2str(str, flags, ctx->list_flags); str_printfa(str, ") \"%s\" ", ctx->ns->sep_str); imap_quote_append_string(str, name, FALSE); - if (client_send_line(client, str_c(str)) == 0) { + if (client_send_line(ctx->cmd->client, str_c(str)) == 0) { /* buffer is full, continue later */ t_pop(); return 0; @@ -195,9 +298,6 @@ if (mailbox_list_iter_deinit(&ctx->list_iter) < 0) ret = -1; - if (ret == 0) - list_namespace_inbox(client, ctx); - t_pop(); return ret < 0 ? -1 : 1; } @@ -297,8 +397,7 @@ } static enum imap_match_result -list_use_inboxcase(struct client_command_context *cmd, - struct cmd_list_context *ctx) +list_use_inboxcase(struct cmd_list_context *ctx) { struct imap_match_glob *inbox_glob; @@ -308,7 +407,7 @@ /* if the original reference and mask combined produces something that matches INBOX, the INBOX casing is on. */ - inbox_glob = imap_match_init(cmd->pool, + inbox_glob = imap_match_init(ctx->cmd->pool, t_strconcat(ctx->ref, ctx->mask, NULL), TRUE, ctx->ns->sep); return imap_match(inbox_glob, "INBOX"); @@ -349,29 +448,31 @@ *cur_mask_p = cur_mask; } -static void -list_namespace_init(struct client_command_context *cmd, - struct cmd_list_context *ctx) +static void list_namespace_init(struct cmd_list_context *ctx) { - struct client *client = cmd->client; struct mail_namespace *ns = ctx->ns; const char *cur_ns_prefix, *cur_ref, *cur_mask; enum imap_match_result match; enum imap_match_result inbox_match; size_t len; - cur_ns_prefix = ctx->ns->prefix; + cur_ns_prefix = ns->prefix; cur_ref = ctx->ref; cur_mask = ctx->mask; + ctx->cur_ns_skip_trailing_sep = FALSE; + + if ((ns->flags & NAMESPACE_FLAG_INBOX) != 0) + ctx->seen_inbox_namespace = TRUE; + if (!skip_namespace_prefix_refmask(ctx, &cur_ns_prefix, &cur_ref, &cur_mask)) return; - inbox_match = list_use_inboxcase(cmd, ctx); - ctx->match_inbox = inbox_match == IMAP_MATCH_YES; + inbox_match = list_use_inboxcase(ctx); + ctx->cur_ns_match_inbox = inbox_match == IMAP_MATCH_YES; - ctx->glob = imap_match_init(cmd->pool, ctx->mask, + ctx->glob = imap_match_init(ctx->cmd->pool, ctx->mask, (inbox_match == IMAP_MATCH_YES || inbox_match == IMAP_MATCH_PARENT) && cur_mask == ctx->mask, ns->sep); @@ -380,7 +481,6 @@ /* namespace prefix still wasn't completely skipped over. for example cur_ns_prefix=INBOX/, mask=%/% or mask=IN%. Check that mask matches namespace prefix. */ - bool skip_trailing_sep = FALSE; i_assert(*cur_ref == '\0'); /* drop the trailing separator in namespace prefix. @@ -389,7 +489,7 @@ len = strlen(cur_ns_prefix); if (cur_ns_prefix[len-1] == ns->sep && strcmp(cur_mask, cur_ns_prefix) != 0) { - skip_trailing_sep = TRUE; + ctx->cur_ns_skip_trailing_sep = TRUE; cur_ns_prefix = t_strndup(cur_ns_prefix, len-1); } @@ -401,26 +501,10 @@ if (match < 0) return; - len = strlen(ns->prefix); if (match == IMAP_MATCH_YES && - (ctx->ns->flags & NAMESPACE_FLAG_LIST) != 0 && - (ctx->list_flags & MAILBOX_LIST_ITER_SUBSCRIBED) == 0 && - (!ctx->match_inbox || - strncmp(ns->prefix, "INBOX", len-1) != 0)) { - /* The prefix itself matches. Because we want to know - INBOX flags, it's handled elsewhere. */ - enum mailbox_info_flags flags; - string_t *str = t_str_new(128); - - flags = MAILBOX_NONEXISTENT | MAILBOX_CHILDREN; - str_append(str, "* LIST ("); - mailbox_flags2str(str, flags, ctx->list_flags); - str_printfa(str, ") \"%s\" ", ns->sep_str); - imap_quote_append_string(str, skip_trailing_sep ? - t_strndup(ns->prefix, len-1) : ns->prefix, - FALSE); - client_send_line(client, str_c(str)); - } + (ns->flags & NAMESPACE_FLAG_LIST) != 0 && + (ctx->list_flags & MAILBOX_LIST_ITER_SUBSCRIBED) == 0) + ctx->cur_ns_send_prefix = TRUE; } @@ -434,11 +518,10 @@ i_assert(*cur_ref == '\0'); skip_mask_wildcard_prefix(cur_ns_prefix, ns->sep, &cur_mask); - if (*cur_mask == '\0' && ctx->match_inbox) { - /* oh what a horrible hack. ns_prefix="INBOX/" and we - wanted to list "%". INBOX should match and we want - to know its flags. for non-INBOX prefixes this is - handled elsewhere because it doesn't need flags. */ + if (*cur_mask == '\0' && ctx->cur_ns_match_inbox) { + /* ns_prefix="INBOX/" and we wanted to list "%". + This is an optimization to avoid doing an empty + listing followed by another INBOX listing later. */ cur_mask = "INBOX"; } } @@ -459,9 +542,22 @@ ctx->list_flags); } +static void list_inbox(struct cmd_list_context *ctx) +{ + const char *str; + + /* INBOX always exists */ + if (!ctx->inbox_found && ctx->cur_ns_match_inbox && + (ctx->ns->flags & NAMESPACE_FLAG_INBOX) != 0 && + (ctx->list_flags & MAILBOX_LIST_ITER_SUBSCRIBED) == 0) { + str = t_strdup_printf("* LIST (\\Unmarked) \"%s\" \"INBOX\"", + ctx->ns->sep_str); + client_send_line(ctx->cmd->client, str); + } +} + static bool cmd_list_continue(struct client_command_context *cmd) { - struct client *client = cmd->client; struct cmd_list_context *ctx = cmd->context; int ret; @@ -471,15 +567,24 @@ return TRUE; } for (; ctx->ns != NULL; ctx->ns = ctx->ns->next) { - if (ctx->list_iter == NULL) - list_namespace_init(cmd, ctx); + if (ctx->list_iter == NULL) { + list_namespace_init(ctx); + if (ctx->list_iter == NULL) + continue; + } - if ((ret = list_namespace_mailboxes(client, ctx)) < 0) { + if ((ret = list_namespace_mailboxes(ctx)) < 0) { client_send_list_error(cmd, ctx->ns->list); return TRUE; } if (ret == 0) return FALSE; + + if (ctx->cur_ns_send_prefix) { + /* no mailboxes in this namespace */ + list_namespace_send_prefix(ctx, FALSE); + } + list_inbox(ctx); } client_send_tagline(cmd, !ctx->lsub ? @@ -579,11 +684,13 @@ client_send_tagline(cmd, "OK List completed."); } else { ctx = p_new(cmd->pool, struct cmd_list_context, 1); + ctx->cmd = cmd; ctx->ref = ref; ctx->mask = mask; ctx->list_flags = list_flags; ctx->lsub = lsub; ctx->ns = client->namespaces; + p_array_init(&ctx->ns_prefixes_listed, cmd->pool, 8); cmd->context = ctx; if (!cmd_list_continue(cmd)) {