Mercurial > dovecot > core-2.2
changeset 14899:f9d0ea98157f
imap: Implemented NOTIFY extension.
Requires mailbox_list_index=yes to work (and to show up in capabilities).
SubscriptionChange event is still unimplemented.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 13 Aug 2012 15:23:32 +0300 |
parents | a16d77a075bb |
children | 87e14707d210 |
files | src/imap/Makefile.am src/imap/cmd-cancelupdate.c src/imap/cmd-fetch.c src/imap/cmd-idle.c src/imap/cmd-list.c src/imap/cmd-notify.c src/imap/cmd-search.c src/imap/cmd-select.c src/imap/cmd-sort.c src/imap/imap-client.c src/imap/imap-client.h src/imap/imap-commands.c src/imap/imap-commands.h src/imap/imap-fetch.c src/imap/imap-fetch.h src/imap/imap-list.c src/imap/imap-list.h src/imap/imap-notify.c src/imap/imap-notify.h src/imap/imap-search.c src/imap/imap-search.h src/imap/imap-sync.c src/imap/main.c |
diffstat | 23 files changed, 1644 insertions(+), 178 deletions(-) [+] |
line wrap: on
line diff
--- a/src/imap/Makefile.am Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/Makefile.am Mon Aug 13 15:23:32 2012 +0300 @@ -43,6 +43,7 @@ cmd-lsub.c \ cmd-namespace.c \ cmd-noop.c \ + cmd-notify.c \ cmd-rename.c \ cmd-search.c \ cmd-select.c \ @@ -64,6 +65,8 @@ imap-expunge.c \ imap-fetch.c \ imap-fetch-body.c \ + imap-list.c \ + imap-notify.c \ imap-search.c \ imap-search-args.c \ imap-settings.c \ @@ -79,6 +82,8 @@ imap-common.h \ imap-expunge.h \ imap-fetch.h \ + imap-list.h \ + imap-notify.h \ imap-search.h \ imap-search-args.h \ imap-settings.h \
--- a/src/imap/cmd-cancelupdate.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-cancelupdate.c Mon Aug 13 15:23:32 2012 +0300 @@ -1,6 +1,7 @@ /* Copyright (c) 2008-2012 Dovecot authors, see the included COPYING file */ #include "imap-common.h" +#include "imap-search.h" #include "imap-commands.h" static bool client_search_update_cancel(struct client *client, const char *tag) @@ -12,8 +13,7 @@ if (update == NULL) return FALSE; - i_free(update->tag); - mailbox_search_result_free(&update->result); + imap_search_update_free(update); array_delete(&client->search_updates, idx, 1); return TRUE; }
--- a/src/imap/cmd-fetch.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-fetch.c Mon Aug 13 15:23:32 2012 +0300 @@ -103,6 +103,7 @@ static bool fetch_parse_modifier(struct imap_fetch_context *ctx, struct client_command_context *cmd, + struct mail_search_args *search_args, const char *name, const struct imap_arg **args, bool *send_vanished) { @@ -117,7 +118,7 @@ return FALSE; } *args += 1; - imap_fetch_add_changed_since(ctx, modseq); + imap_fetch_add_changed_since(ctx, search_args, modseq); return TRUE; } if (strcmp(name, "VANISHED") == 0 && cmd->uid) { @@ -137,6 +138,7 @@ static bool fetch_parse_modifiers(struct imap_fetch_context *ctx, struct client_command_context *cmd, + struct mail_search_args *search_args, const struct imap_arg *args, bool *send_vanished_r) { const char *name; @@ -150,13 +152,14 @@ return FALSE; } args++; - if (!fetch_parse_modifier(ctx, cmd, t_str_ucase(name), + if (!fetch_parse_modifier(ctx, cmd, search_args, + t_str_ucase(name), &args, send_vanished_r)) return FALSE; } if (*send_vanished_r && - (ctx->search_args->args->next == NULL || - ctx->search_args->args->next->type != SEARCH_MODSEQ)) { + (search_args->args->next == NULL || + search_args->args->next->type != SEARCH_MODSEQ)) { client_send_command_error(cmd, "VANISHED used without CHANGEDSINCE"); return FALSE; @@ -250,23 +253,27 @@ return ret < 0; ctx = imap_fetch_alloc(client, cmd->pool); - ctx->search_args = search_args; if (!fetch_parse_args(ctx, cmd, &args[1], &next_arg) || (imap_arg_get_list(next_arg, &list_arg) && - !fetch_parse_modifiers(ctx, cmd, list_arg, &send_vanished))) { + !fetch_parse_modifiers(ctx, cmd, search_args, list_arg, + &send_vanished))) { imap_fetch_free(&ctx); + mail_search_args_unref(&search_args); return TRUE; } if (send_vanished) { memset(&qresync_args, 0, sizeof(qresync_args)); if (imap_fetch_send_vanished(client, client->mailbox, - search_args, &qresync_args) < 0) + search_args, &qresync_args) < 0) { + mail_search_args_unref(&search_args); return cmd_fetch_finish(ctx, cmd); + } } - imap_fetch_begin_once(ctx, client->mailbox); + imap_fetch_begin(ctx, client->mailbox, search_args); + mail_search_args_unref(&search_args); if (imap_fetch_more(ctx, cmd) == 0) { /* unfinished */
--- a/src/imap/cmd-idle.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-idle.c Mon Aug 13 15:23:32 2012 +0300 @@ -115,7 +115,7 @@ static void keepalive_timeout(struct cmd_idle_context *ctx) { - if (ctx->client->output_lock != NULL) { + if (ctx->client->output_locked) { /* it's busy sending output */ return; }
--- a/src/imap/cmd-list.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-list.c Mon Aug 13 15:23:32 2012 +0300 @@ -9,6 +9,7 @@ #include "imap-match.h" #include "imap-status.h" #include "imap-commands.h" +#include "imap-list.h" #include "mail-namespace.h" struct cmd_list_context { @@ -49,40 +50,22 @@ if ((ctx->list_flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) flags &= ~(MAILBOX_CHILDREN|MAILBOX_NOCHILDREN); - if ((flags & MAILBOX_SUBSCRIBED) != 0 && - (ctx->list_flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) - str_append(str, "\\Subscribed "); + if ((ctx->list_flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) == 0) + flags &= ~MAILBOX_SUBSCRIBED; if ((flags & MAILBOX_CHILD_SUBSCRIBED) != 0 && (flags & MAILBOX_SUBSCRIBED) == 0 && !ctx->used_listext) { /* LSUB uses \Noselect for this */ flags |= MAILBOX_NOSELECT; } - - if ((flags & MAILBOX_NOSELECT) != 0) - str_append(str, "\\Noselect "); - if ((flags & MAILBOX_NONEXISTENT) != 0) - str_append(str, "\\NonExistent "); - - if ((flags & MAILBOX_CHILDREN) != 0) - str_append(str, "\\HasChildren "); - else if ((flags & MAILBOX_NOINFERIORS) != 0) - str_append(str, "\\NoInferiors "); - else if ((flags & MAILBOX_NOCHILDREN) != 0) - str_append(str, "\\HasNoChildren "); - - if ((flags & MAILBOX_MARKED) != 0) - str_append(str, "\\Marked "); - if ((flags & MAILBOX_UNMARKED) != 0) - str_append(str, "\\UnMarked "); + imap_mailbox_flags2str(str, flags); if ((ctx->list_flags & MAILBOX_LIST_ITER_RETURN_SPECIALUSE) != 0 && special_use != NULL) { + if (str_len(str) != orig_len) + str_append_c(str, ' '); str_append(str, special_use); - str_append_c(str, ' '); } - if (str_len(str) != orig_len) - str_truncate(str, str_len(str)-1); } static void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/cmd-notify.c Mon Aug 13 15:23:32 2012 +0300 @@ -0,0 +1,542 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "imap-quote.h" +#include "imap-commands.h" +#include "imap-fetch.h" +#include "imap-list.h" +#include "imap-status.h" +#include "imap-notify.h" + +#define IMAP_NOTIFY_MAX_NAMES_PER_NS 100 + +static const char *imap_notify_event_names[] = { + "MessageNew", "MessageExpunge", "FlagChange", "AnnotationChange", + "MailboxName", "SubscriptionChange", "MailboxMetadataChange", + "ServerMetadataChange" +}; + +static int +cmd_notify_parse_event(const struct imap_arg *arg, + enum imap_notify_event *event_r) +{ + const char *str; + unsigned int i; + + if (!imap_arg_get_atom(arg, &str)) + return -1; + + for (i = 0; i < N_ELEMENTS(imap_notify_event_names); i++) { + if (strcasecmp(str, imap_notify_event_names[i]) == 0) { + *event_r = (enum imap_notify_event)(1 << i); + return 0; + } + } + return -1; +} + +static int +cmd_notify_parse_fetch(struct imap_notify_context *ctx, + const struct imap_arg *list) +{ + return imap_fetch_att_list_parse(ctx->client, ctx->pool, list, + &ctx->fetch_ctx, &ctx->error); +} + +static int +cmd_notify_set_selected(struct imap_notify_context *ctx, + const struct imap_arg *events) +{ +#define EV_NEW_OR_EXPUNGE \ + (IMAP_NOTIFY_EVENT_MESSAGE_NEW | IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) + const struct imap_arg *list, *fetch_att_list; + const char *str; + enum imap_notify_event event; + + if (imap_arg_get_atom(events, &str) && + strcasecmp(str, "NONE") == 0) { + /* no events for selected mailbox. this is also the default + when NOTIFY command doesn't specify it explicitly */ + return 0; + } + + if (!imap_arg_get_list(events, &list)) + return -1; + + for (; list->type != IMAP_ARG_EOL; list++) { + if (cmd_notify_parse_event(list, &event) < 0) + return -1; + ctx->selected_events |= event; + ctx->global_used_events |= event; + + if (event == IMAP_NOTIFY_EVENT_MESSAGE_NEW && + imap_arg_get_list(&list[1], &fetch_att_list)) { + /* MessageNew: list of fetch-att */ + if (cmd_notify_parse_fetch(ctx, fetch_att_list) < 0) + return -1; + list++; + } + } + + /* if MessageNew or MessageExpunge is specified, both of them must */ + if ((ctx->selected_events & EV_NEW_OR_EXPUNGE) != 0 && + (ctx->selected_events & EV_NEW_OR_EXPUNGE) != EV_NEW_OR_EXPUNGE) { + ctx->error = "MessageNew and MessageExpunge must be together"; + return -1; + } + + /* if FlagChange or AnnotationChange is specified, + MessageNew and MessageExpunge must also be specified */ + if ((ctx->selected_events & + (IMAP_NOTIFY_EVENT_FLAG_CHANGE | + IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE)) != 0 && + (ctx->selected_events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) == 0) { + ctx->error = "FlagChange requires MessageNew and MessageExpunge"; + return -1; + } + return 0; +} + +static struct imap_notify_namespace * +imap_notify_namespace_get(struct imap_notify_context *ctx, + struct mail_namespace *ns) +{ + struct imap_notify_namespace *notify_ns; + + array_foreach_modifiable(&ctx->namespaces, notify_ns) { + if (notify_ns->ns == ns) + return notify_ns; + } + notify_ns = array_append_space(&ctx->namespaces); + notify_ns->ctx = ctx; + notify_ns->ns = ns; + p_array_init(¬ify_ns->mailboxes, ctx->pool, 4); + return notify_ns; +} + +static struct imap_notify_mailboxes * +imap_notify_mailboxes_get(struct imap_notify_namespace *notify_ns, + enum imap_notify_type type, + enum imap_notify_event events) +{ + struct imap_notify_mailboxes *notify_boxes; + + array_foreach_modifiable(¬ify_ns->mailboxes, notify_boxes) { + if (notify_boxes->type == type && + notify_boxes->events == events) + return notify_boxes; + } + notify_boxes = array_append_space(¬ify_ns->mailboxes); + notify_boxes->type = type; + notify_boxes->events = events; + p_array_init(¬ify_boxes->names, notify_ns->ctx->pool, 4); + return notify_boxes; +} + +static void +cmd_notify_add_mailbox(struct imap_notify_context *ctx, + struct mail_namespace *ns, const char *name, + enum imap_notify_type type, + enum imap_notify_event events) +{ + struct imap_notify_namespace *notify_ns; + struct imap_notify_mailboxes *notify_boxes; + const char *const *names; + unsigned int i, count, cur_len, name_len = strlen(name); + char ns_sep = mail_namespace_get_sep(ns); + + if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 && + strncmp(name, "INBOX", 5) != 0 && + strncasecmp(name, "INBOX", 5) == 0 && + (name[5] == '\0' || name[5] == ns_sep)) { + /* we'll do only case-sensitive comparisons later, + so sanitize INBOX to be uppercase */ + name = t_strconcat("INBOX", name + 5, NULL); + } + + notify_ns = imap_notify_namespace_get(ctx, ns); + notify_boxes = imap_notify_mailboxes_get(notify_ns, type, events); + + names = array_get(¬ify_boxes->names, &count); + for (i = 0; i < count; ) { + if (strcmp(names[i], name) == 0) { + /* exact duplicate, already added */ + return; + } + if (type != IMAP_NOTIFY_TYPE_SUBTREE) + i++; + else { + /* see if one is a subtree of the other */ + cur_len = strlen(names[i]); + if (strncmp(names[i], name, cur_len) == 0 && + names[i][cur_len] == ns_sep) { + /* already matched in this subtree */ + return; + } + if (strncmp(names[i], name, name_len) == 0 && + names[i][name_len] == ns_sep) { + /* we're adding a parent, remove the child */ + array_delete(¬ify_boxes->names, i, 1); + names = array_get(¬ify_boxes->names, &count); + } else { + i++; + } + } + } + name = p_strdup(ctx->pool, name); + array_append(¬ify_boxes->names, &name, 1); + + ctx->global_max_mailbox_names = + I_MAX(ctx->global_max_mailbox_names, + array_count(¬ify_boxes->names)); +} + +static void cmd_notify_add_personal(struct imap_notify_context *ctx, + enum imap_notify_event events) +{ + struct mail_namespace *ns; + + for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) { + if (ns->type == NAMESPACE_PRIVATE) { + cmd_notify_add_mailbox(ctx, ns, "", + IMAP_NOTIFY_TYPE_SUBTREE, events); + } + } +} + +static void cmd_notify_add_subscribed(struct imap_notify_context *ctx, + enum imap_notify_event events) +{ + struct mail_namespace *ns; + + for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) { + cmd_notify_add_mailbox(ctx, ns, "", + IMAP_NOTIFY_TYPE_SUBSCRIBED, events); + } +} + +static void +cmd_notify_add_mailbox_namespaces(struct imap_notify_context *ctx, + const char *name, + enum imap_notify_type type, + enum imap_notify_event events) +{ + struct mail_namespace *ns; + + /* add to all matching namespaces */ + for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) { + if (mail_namespace_find(ns, name) == ns) + cmd_notify_add_mailbox(ctx, ns, name, type, events); + } +} + +static int +cmd_notify_add_mailboxes(struct imap_notify_context *ctx, + const struct imap_arg *arg, + enum imap_notify_type type, + enum imap_notify_event events) +{ + const struct imap_arg *list; + const char *name; + + if (imap_arg_get_astring(arg, &name)) { + cmd_notify_add_mailbox_namespaces(ctx, name, type, events); + return 0; + } + if (!imap_arg_get_list(arg, &list)) + return -1; + + for (; list->type != IMAP_ARG_EOL; list++) { + if (!imap_arg_get_astring(list, &name)) + return -1; + + cmd_notify_add_mailbox_namespaces(ctx, name, type, events); + } + return 0; +} + +static int +cmd_notify_set(struct imap_notify_context *ctx, const struct imap_arg *args) +{ + const struct imap_arg *event_group, *mailboxes, *list; + const char *str, *filter_mailboxes; + enum imap_notify_event event, event_mask; + + if (imap_arg_get_atom(args, &str) && + strcasecmp(str, "STATUS") == 0) { + /* send STATUS replies for all matched mailboxes before + NOTIFY's OK reply */ + ctx->send_immediate_status = TRUE; + args++; + } + for (; args->type != IMAP_ARG_EOL; args++) { + if (!imap_arg_get_list(args, &event_group)) + return -1; + + /* filter-mailboxes */ + if (!imap_arg_get_atom(event_group, &filter_mailboxes)) + return -1; + event_group++; + + if (strcasecmp(filter_mailboxes, "selected") == 0 || + strcasecmp(filter_mailboxes, "selected-delayed") == 0) { + /* setting events for selected mailbox. + handle specially. */ + if (ctx->selected_set) { + ctx->error = "Duplicate selected filter"; + return -1; + } + ctx->selected_set = TRUE; + if (strcasecmp(filter_mailboxes, "selected") == 0) + ctx->selected_immediate_expunges = TRUE; + if (cmd_notify_set_selected(ctx, event_group) < 0) + return -1; + continue; + } + + if (strcasecmp(filter_mailboxes, "subtree") == 0 || + strcasecmp(filter_mailboxes, "mailboxes") == 0) { + mailboxes = event_group++; + } else { + mailboxes = NULL; + } + + /* parse events */ + if (imap_arg_get_atom(event_group, &str) && + strcasecmp(str, "NONE") == 0) { + /* NONE is the default, ignore this */ + continue; + } + if (!imap_arg_get_list(event_group, &list) || + list[0].type == IMAP_ARG_EOL) + return -1; + + event_mask = 0; + for (; list->type != IMAP_ARG_EOL; list++) { + if (cmd_notify_parse_event(list, &event) < 0) + return -1; + event_mask |= event; + ctx->global_used_events |= event; + } + + /* we can't currently know inboxes, so treat it the + same as personal */ + if (strcasecmp(filter_mailboxes, "inboxes") == 0 || + strcasecmp(filter_mailboxes, "personal") == 0) + cmd_notify_add_personal(ctx, event_mask); + else if (strcasecmp(filter_mailboxes, "subscribed") == 0) + cmd_notify_add_subscribed(ctx, event_mask); + else if (strcasecmp(filter_mailboxes, "subtree") == 0) { + if (cmd_notify_add_mailboxes(ctx, mailboxes, + IMAP_NOTIFY_TYPE_SUBTREE, + event_mask) < 0) + return -1; + } else if (strcasecmp(filter_mailboxes, "mailboxes") == 0) { + if (cmd_notify_add_mailboxes(ctx, mailboxes, + IMAP_NOTIFY_TYPE_MAILBOX, + event_mask) < 0) + return -1; + } else { + return -1; + } + } + return 0; +} + +static void +imap_notify_box_list_noperm(struct client *client, struct mailbox *box) +{ + string_t *str = t_str_new(128); + char ns_sep = mail_namespace_get_sep(mailbox_get_namespace(box)); + enum mailbox_info_flags mailbox_flags; + + if (mailbox_list_mailbox(mailbox_get_namespace(box)->list, + mailbox_get_name(box), &mailbox_flags) < 0) + mailbox_flags = 0; + + str_append(str, "* LIST ("); + if (imap_mailbox_flags2str(str, mailbox_flags)) + str_append_c(str, ' '); + str_append(str, "\\NoAccess) \""); + if (ns_sep == '\\') + str_append_c(str, '\\'); + str_append_c(str, ns_sep); + str_append(str, "\" "); + + imap_quote_append_string(str, mailbox_get_vname(box), FALSE); + client_send_line(client, str_c(str)); +} + +static void +imap_notify_box_send_status(struct client_command_context *cmd, + struct imap_notify_context *ctx, + const struct mailbox_info *info) +{ + struct mailbox *box; + struct imap_status_items items; + struct imap_status_result result; + + if ((info->flags & (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) != 0) + return; + + /* don't send STATUS to selected mailbox */ + if (cmd->client->mailbox != NULL && + mailbox_equals(cmd->client->mailbox, info->ns, info->vname)) + return; + + memset(&items, 0, sizeof(items)); + memset(&result, 0, sizeof(result)); + + items.status = STATUS_UIDVALIDITY | STATUS_UIDNEXT | + STATUS_MESSAGES | STATUS_UNSEEN; + + box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY); + if (ctx->client->enabled_features != 0) + (void)mailbox_enable(box, ctx->client->enabled_features); + + if (imap_status_get(cmd, info->ns, info->vname, &items, &result) < 0) { + if (result.error == MAIL_ERROR_PERM) + imap_notify_box_list_noperm(ctx->client, box); + else if (result.error != MAIL_ERROR_NOTFOUND) { + client_send_line(ctx->client, + t_strconcat("* ", result.errstr, NULL)); + } + } else { + imap_status_send(ctx->client, info->vname, &items, &result); + } + mailbox_free(&box); +} + +static bool imap_notify_ns_want_status(struct imap_notify_namespace *notify_ns) +{ + const struct imap_notify_mailboxes *notify_boxes; + + array_foreach(¬ify_ns->mailboxes, notify_boxes) { + if ((notify_boxes->events & + (IMAP_NOTIFY_EVENT_MESSAGE_NEW | + IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE | + IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE | + IMAP_NOTIFY_EVENT_FLAG_CHANGE)) != 0) + return TRUE; + } + return FALSE; +} + +static void +imap_notify_ns_send_status(struct client_command_context *cmd, + struct imap_notify_context *ctx, + struct imap_notify_namespace *notify_ns) +{ + struct mailbox_list_iterate_context *iter; + const struct imap_notify_mailboxes *notify_boxes; + const struct mailbox_info *info; + + if (!imap_notify_ns_want_status(notify_ns)) + return; + + iter = mailbox_list_iter_init(notify_ns->ns->list, "*", + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + array_foreach(¬ify_ns->mailboxes, notify_boxes) { + if (imap_notify_match_mailbox(notify_ns, notify_boxes, + info->vname)) { + imap_notify_box_send_status(cmd, ctx, info); + break; + } + } + } + if (mailbox_list_iter_deinit(&iter) < 0) { + client_send_line(notify_ns->ctx->client, + "* NO Mailbox listing failed"); + } +} + +static void cmd_notify_send_status(struct client_command_context *cmd, + struct imap_notify_context *ctx) +{ + struct imap_notify_namespace *notify_ns; + + array_foreach_modifiable(&ctx->namespaces, notify_ns) + imap_notify_ns_send_status(cmd, ctx, notify_ns); +} + +bool cmd_notify(struct client_command_context *cmd) +{ + struct imap_notify_context *ctx; + const struct imap_arg *args; + const char *str; + int ret = 0; + pool_t pool; + + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; + + pool = pool_alloconly_create("imap notify context", 1024); + ctx = p_new(pool, struct imap_notify_context, 1); + ctx->pool = pool; + ctx->client = cmd->client; + p_array_init(&ctx->namespaces, pool, 4); + + if (!imap_arg_get_atom(&args[0], &str)) + ret = -1; + else if (strcasecmp(str, "NONE") == 0) + ; + else if (strcasecmp(str, "SET") == 0) + ret = cmd_notify_set(ctx, args+1); + else + ret = -1; + + if (ret < 0) { + client_send_command_error(cmd, ctx->error != NULL ? ctx->error : + "Invalid arguments."); + pool_unref(&pool); + return TRUE; + } + + if ((ctx->global_used_events & UNSUPPORTED_EVENTS) != 0) { + string_t *str = t_str_new(128); + unsigned int i; + + str_append(str, "NO [BADEVENT"); + for (i = 0; i < N_ELEMENTS(imap_notify_event_names); i++) { + if ((ctx->global_used_events & (1 << i)) != 0 && + ((1 << i) & UNSUPPORTED_EVENTS) != 0) { + str_append_c(str, ' '); + str_append(str, imap_notify_event_names[i]); + } + } + str_append(str, "] Unsupported NOTIFY events."); + client_send_tagline(cmd, str_c(str)); + pool_unref(&pool); + return TRUE; + } + + if (array_count(&ctx->namespaces) == 0) { + /* selected mailbox only */ + } else if (ctx->global_max_mailbox_names > IMAP_NOTIFY_MAX_NAMES_PER_NS) { + client_send_tagline(cmd, + "NO [NOTIFICATIONOVERFLOW] Too many mailbox names"); + pool_unref(&pool); + return TRUE; + } else if (imap_notify_begin(ctx) < 0) { + client_send_tagline(cmd, + "NO [NOTIFICATIONOVERFLOW] NOTIFY not supported for these mailboxes."); + pool_unref(&pool); + return TRUE; + } + if (cmd->client->notify_ctx != NULL) + imap_notify_deinit(&cmd->client->notify_ctx); + + if (ctx->send_immediate_status) + cmd_notify_send_status(cmd, ctx); + cmd->client->notify_immediate_expunges = + ctx->selected_immediate_expunges; + cmd->client->notify_count_changes = + (ctx->selected_events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0; + cmd->client->notify_flag_changes = + (ctx->selected_events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0; + + cmd->client->notify_ctx = ctx; + return cmd_sync(cmd, 0, IMAP_SYNC_FLAG_SAFE, "OK NOTIFY completed."); +}
--- a/src/imap/cmd-search.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-search.c Mon Aug 13 15:23:32 2012 +0300 @@ -31,6 +31,7 @@ if (!imap_arg_get_astring(&args[1], &charset)) { client_send_command_error(cmd, "Invalid charset argument."); + imap_search_context_free(ctx); return TRUE; } args += 2; @@ -39,8 +40,10 @@ } ret = imap_search_args_build(cmd, args, charset, &sargs); - if (ret <= 0) + if (ret <= 0) { + imap_search_context_free(ctx); return ret < 0; + } return imap_search_start(ctx, sargs, NULL); }
--- a/src/imap/cmd-select.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-select.c Mon Aug 13 15:23:32 2012 +0300 @@ -251,14 +251,16 @@ } fetch_ctx = imap_fetch_alloc(ctx->cmd->client, ctx->cmd->pool); - fetch_ctx->search_args = search_args; - imap_fetch_add_changed_since(fetch_ctx, ctx->qresync_modseq); + imap_fetch_add_changed_since(fetch_ctx, search_args, + ctx->qresync_modseq); imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init); imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init); imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_modseq_init); - imap_fetch_begin_once(fetch_ctx, ctx->box); + imap_fetch_begin(fetch_ctx, ctx->box, search_args); + mail_search_args_unref(&search_args); + if (imap_fetch_more(fetch_ctx, ctx->cmd) == 0) { /* unfinished */ ctx->fetch_ctx = fetch_ctx; @@ -312,6 +314,7 @@ client->messages_count = status.messages; client->recent_count = status.recent; client->uidvalidity = status.uidvalidity; + client->notify_uidnext = status.uidnext; client_update_mailbox_flags(client, status.keywords); client_send_mailbox_flags(client, TRUE);
--- a/src/imap/cmd-sort.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/cmd-sort.c Mon Aug 13 15:23:32 2012 +0300 @@ -114,23 +114,29 @@ /* sort program */ if (!imap_arg_get_list(args, &list_args)) { client_send_command_error(cmd, "Invalid sort argument."); + imap_search_context_free(ctx); return TRUE; } - if (get_sort_program(cmd, list_args, sort_program) < 0) + if (get_sort_program(cmd, list_args, sort_program) < 0) { + imap_search_context_free(ctx); return TRUE; + } args++; /* charset */ if (!imap_arg_get_astring(args, &charset)) { client_send_command_error(cmd, "Invalid charset argument."); + imap_search_context_free(ctx); return TRUE; } args++; ret = imap_search_args_build(cmd, args, charset, &sargs); - if (ret <= 0) + if (ret <= 0) { + imap_search_context_free(ctx); return ret < 0; + } return imap_search_start(ctx, sargs, sort_program); }
--- a/src/imap/imap-client.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-client.c Mon Aug 13 15:23:32 2012 +0300 @@ -14,6 +14,8 @@ #include "imap-util.h" #include "mail-namespace.h" #include "mail-storage-service.h" +#include "imap-search.h" +#include "imap-notify.h" #include "imap-commands.h" #include <stdlib.h> @@ -29,7 +31,7 @@ static void client_idle_timeout(struct client *client) { - if (client->output_lock == NULL) + if (!client->output_locked) client_send_line(client, "* BYE Disconnected for inactivity."); client_destroy(client, "Disconnected for inactivity"); } @@ -39,6 +41,7 @@ struct mail_storage_service_user *service_user, const struct imap_settings *set) { + const struct mail_storage_settings *mail_set; struct client *client; const char *ident; pool_t pool; @@ -73,6 +76,8 @@ client->command_pool = pool_alloconly_create(MEMPOOL_GROWING"client command", 1024*2); client->user = user; + client->notify_count_changes = TRUE; + client->notify_flag_changes = TRUE; mail_namespaces_set_storage_callbacks(user->namespaces, &mail_storage_callbacks, client); @@ -96,6 +101,14 @@ str_append(client->capability_string, " SEARCH=FUZZY"); } + mail_set = mail_user_set_get_storage_set(user); + if (mail_set->mailbox_list_index && !explicit_capability) { + /* NOTIFY is enabled only when mailbox list indexes are + enabled, although even that doesn't necessarily guarantee + it always */ + str_append(client->capability_string, " NOTIFY"); + } + ident = mail_user_get_anvil_userip_ident(client->user); if (ident != NULL) { master_service_anvil_send(master_service, t_strconcat( @@ -204,8 +217,8 @@ o_stream_close(client->output); /* finish off all the queued commands. */ - if (client->output_lock != NULL) - client_command_cancel(&client->output_lock); + if (client->output_cmd_lock != NULL) + client_command_cancel(&client->output_cmd_lock); while (client->command_queue != NULL) { cmd = client->command_queue; client_command_cancel(&cmd); @@ -221,6 +234,9 @@ client_search_updates_free(client); mailbox_free(&client->mailbox); } + if (client->notify_ctx != NULL) + imap_notify_deinit(&client->notify_ctx); + if (client->anvil_sent) { master_service_anvil_send(master_service, t_strconcat( "DISCONNECT\t", my_pid, "\timap/", @@ -520,8 +536,7 @@ return TRUE; } -static struct client_command_context * -client_command_new(struct client *client) +struct client_command_context *client_command_alloc(struct client *client) { struct client_command_context *cmd; @@ -530,6 +545,17 @@ cmd->pool = client->command_pool; p_array_init(&cmd->module_contexts, cmd->pool, 5); + DLLIST_PREPEND(&client->command_queue, cmd); + client->command_queue_size++; + return cmd; +} + +static struct client_command_context * +client_command_new(struct client *client) +{ + struct client_command_context *cmd; + + cmd = client_command_alloc(client); if (client->free_parser != NULL) { cmd->parser = client->free_parser; client->free_parser = NULL; @@ -538,10 +564,6 @@ imap_parser_create(client->input, client->output, client->set->imap_max_line_length); } - - DLLIST_PREPEND(&client->command_queue, cmd); - client->command_queue_size++; - return cmd; } @@ -552,6 +574,8 @@ *_cmd = NULL; + i_assert(client->output_cmd_lock != cmd); + /* reset input idle time because command output might have taken a long time and we don't want to disconnect client immediately then */ client->last_input = ioloop_time; @@ -567,16 +591,14 @@ if (client->input_lock == cmd) client->input_lock = NULL; - if (client->output_lock == cmd) - client->output_lock = NULL; if (client->mailbox_change_lock == cmd) client->mailbox_change_lock = NULL; - if (client->free_parser != NULL) - imap_parser_unref(&cmd->parser); - else { + if (client->free_parser == NULL) { imap_parser_reset(cmd->parser); client->free_parser = cmd->parser; + } else if (cmd->parser != NULL) { + imap_parser_unref(&cmd->parser); } client->command_queue_size--; @@ -589,6 +611,7 @@ if (client->to_idle_output != NULL) timeout_remove(&client->to_idle_output); } + imap_client_notify_command_freed(client); imap_refresh_proctitle(); } @@ -775,7 +798,7 @@ /* beginning a new command */ if (client->command_queue_size >= CLIENT_COMMAND_QUEUE_MAX_SIZE || - client->output_lock != NULL) { + client->output_locked) { /* wait for some of the commands to finish */ *remove_io_r = TRUE; return FALSE; @@ -869,33 +892,19 @@ } } -int client_output(struct client *client) +static void client_output_commands(struct client *client) { struct client_command_context *cmd; - int ret; - - i_assert(!client->destroyed); - - client->last_output = ioloop_time; - timeout_reset(client->to_idle); - if (client->to_idle_output != NULL) - timeout_reset(client->to_idle_output); - - o_stream_cork(client->output); - if ((ret = o_stream_flush(client->output)) < 0) { - client_destroy(client, NULL); - return 1; - } /* mark all commands non-executed */ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) cmd->temp_executed = FALSE; - if (client->output_lock != NULL) { - client->output_lock->temp_executed = TRUE; - client_output_cmd(client->output_lock); + if (client->output_cmd_lock != NULL) { + client->output_cmd_lock->temp_executed = TRUE; + client_output_cmd(client->output_cmd_lock); } - while (client->output_lock == NULL) { + while (!client->output_locked) { /* go through the entire commands list every time in case multiple commands were freed. temp_executed keeps track of which messages we've called so far */ @@ -913,8 +922,28 @@ break; } } +} - cmd_sync_delayed(client); +int client_output(struct client *client) +{ + int ret; + + i_assert(!client->destroyed); + + client->last_output = ioloop_time; + timeout_reset(client->to_idle); + if (client->to_idle_output != NULL) + timeout_reset(client->to_idle_output); + + o_stream_cork(client->output); + if ((ret = o_stream_flush(client->output)) < 0) { + client_destroy(client, NULL); + return 1; + } + + client_output_commands(client); + (void)cmd_sync_delayed(client); + o_stream_uncork(client->output); if (client->disconnected) client_destroy(client, NULL); @@ -1003,10 +1032,8 @@ if (!array_is_created(&client->search_updates)) return; - array_foreach_modifiable(&client->search_updates, update) { - i_free(update->tag); - mailbox_search_result_free(&update->result); - } + array_foreach_modifiable(&client->search_updates, update) + imap_search_update_free(update); array_clear(&client->search_updates); }
--- a/src/imap/imap-client.h Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-client.h Mon Aug 13 15:23:32 2012 +0300 @@ -29,6 +29,9 @@ char *tag; struct mail_search_result *result; bool return_uids; + + pool_t fetch_pool; + struct imap_fetch_context *fetch_ctx; }; enum client_command_state { @@ -124,10 +127,13 @@ ARRAY_TYPE(seq_range) search_saved_uidset; /* SEARCH=CONTEXT extension: Searches that get updated */ ARRAY_DEFINE(search_updates, struct imap_search_update); + /* NOTIFY extension */ + struct imap_notify_context *notify_ctx; + uint32_t notify_uidnext; /* client input/output is locked by this command */ struct client_command_context *input_lock; - struct client_command_context *output_lock; + struct client_command_context *output_cmd_lock; /* command changing the mailbox */ struct client_command_context *mailbox_change_lock; @@ -146,9 +152,13 @@ unsigned int mailbox_examined:1; unsigned int anvil_sent:1; unsigned int tls_compression:1; + unsigned int output_locked:1; unsigned int input_skip_line:1; /* skip all the data until we've found a new line */ unsigned int modseqs_sent_since_sync:1; + unsigned int notify_immediate_expunges:1; + unsigned int notify_count_changes:1; + unsigned int notify_flag_changes:1; }; struct imap_module_register { @@ -212,6 +222,7 @@ unsigned int *idx_r); void client_search_updates_free(struct client *client); +struct client_command_context *client_command_alloc(struct client *client); void client_command_cancel(struct client_command_context **cmd); void client_command_free(struct client_command_context **cmd);
--- a/src/imap/imap-commands.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-commands.c Mon Aug 13 15:23:32 2012 +0300 @@ -53,6 +53,7 @@ { "IDLE", cmd_idle, COMMAND_FLAG_BREAKS_SEQS | COMMAND_FLAG_REQUIRES_SYNC }, { "NAMESPACE", cmd_namespace, 0 }, + { "NOTIFY", cmd_notify, COMMAND_FLAG_BREAKS_SEQS }, { "SORT", cmd_sort, COMMAND_FLAG_USES_SEQS }, { "THREAD", cmd_thread, COMMAND_FLAG_USES_SEQS }, { "UID EXPUNGE", cmd_uid_expunge, COMMAND_FLAG_BREAKS_SEQS },
--- a/src/imap/imap-commands.h Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-commands.h Mon Aug 13 15:23:32 2012 +0300 @@ -107,6 +107,7 @@ bool cmd_id(struct client_command_context *cmd); bool cmd_idle(struct client_command_context *cmd); bool cmd_namespace(struct client_command_context *cmd); +bool cmd_notify(struct client_command_context *cmd); bool cmd_sort(struct client_command_context *cmd); bool cmd_thread(struct client_command_context *cmd); bool cmd_uid_expunge(struct client_command_context *cmd);
--- a/src/imap/imap-fetch.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-fetch.c Mon Aug 13 15:23:32 2012 +0300 @@ -80,6 +80,38 @@ i_unreached(); } +int imap_fetch_att_list_parse(struct client *client, pool_t pool, + const struct imap_arg *list, + struct imap_fetch_context **fetch_ctx_r, + const char **error_r) +{ + struct imap_fetch_init_context init_ctx; + const char *str; + + memset(&init_ctx, 0, sizeof(init_ctx)); + init_ctx.fetch_ctx = imap_fetch_alloc(client, pool); + init_ctx.pool = pool; + init_ctx.args = list; + + while (imap_arg_get_atom(init_ctx.args, &str)) { + init_ctx.name = t_str_ucase(str); + init_ctx.args++; + if (!imap_fetch_init_handler(&init_ctx)) { + *error_r = t_strconcat("Invalid fetch-att list: ", + init_ctx.error, NULL); + imap_fetch_free(&init_ctx.fetch_ctx); + return -1; + } + } + if (!IMAP_ARG_IS_EOL(init_ctx.args)) { + *error_r = "fetch-att list contains non-atoms."; + imap_fetch_free(&init_ctx.fetch_ctx); + return -1; + } + *fetch_ctx_r = init_ctx.fetch_ctx; + return 0; +} + struct imap_fetch_context * imap_fetch_alloc(struct client *client, pool_t pool) { @@ -98,18 +130,19 @@ } void imap_fetch_add_changed_since(struct imap_fetch_context *ctx, + struct mail_search_args *search_args, uint64_t modseq) { struct mail_search_arg *search_arg; - search_arg = p_new(ctx->search_args->pool, struct mail_search_arg, 1); + search_arg = p_new(search_args->pool, struct mail_search_arg, 1); search_arg->type = SEARCH_MODSEQ; search_arg->value.modseq = - p_new(ctx->search_args->pool, struct mail_search_modseq, 1); + p_new(search_args->pool, struct mail_search_modseq, 1); search_arg->value.modseq->modseq = modseq + 1; - search_arg->next = ctx->search_args->args->next; - ctx->search_args->args->next = search_arg; + search_arg->next = search_args->args->next; + search_args->args->next = search_arg; imap_fetch_init_nofail_handler(ctx, imap_fetch_modseq_init); } @@ -333,12 +366,10 @@ ctx->fetch_data |= MAIL_FETCH_NUL_STATE; } -static void -imap_fetch_begin_full(struct imap_fetch_context *ctx, - struct mailbox *box, bool once) +void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box, + struct mail_search_args *search_args) { struct mailbox_header_lookup_ctx *wanted_headers = NULL; - struct mail_search_args *search_args; const char *const *headers; i_assert(!ctx->state.fetching); @@ -361,14 +392,6 @@ MAILBOX_TRANSACTION_FLAG_HIDE | MAILBOX_TRANSACTION_FLAG_REFRESH); - if (!once) { - /* fetch can be executed multiple times. - clone the search args for this fetch */ - search_args = mail_search_args_dup(ctx->search_args); - } else { - search_args = ctx->search_args; - ctx->search_args = NULL; - } mail_search_args_init(search_args, box, TRUE, &ctx->client->search_saved_uidset); ctx->state.search_ctx = @@ -380,17 +403,6 @@ if (wanted_headers != NULL) mailbox_header_lookup_unref(&wanted_headers); - mail_search_args_unref(&search_args); -} - -void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box) -{ - imap_fetch_begin_full(ctx, box, FALSE); -} - -void imap_fetch_begin_once(struct imap_fetch_context *ctx, struct mailbox *box) -{ - imap_fetch_begin_full(ctx, box, TRUE); } static int imap_fetch_flush_buffer(struct imap_fetch_context *ctx) @@ -557,15 +569,38 @@ { int ret; - i_assert(ctx->client->output_lock == NULL || - ctx->client->output_lock == cmd); + i_assert(!ctx->client->output_locked || + ctx->client->output_cmd_lock == cmd); ret = imap_fetch_more_int(ctx, cmd->cancel); if (ret < 0) ctx->state.failed = TRUE; if (ctx->state.line_partial) { /* nothing can be sent until FETCH is finished */ - ctx->client->output_lock = cmd; + ctx->client->output_cmd_lock = cmd; + } + if (cmd->cancel && ctx->client->output_cmd_lock != NULL) { + /* canceling didn't really work. we must not output + anything anymore. */ + if (!ctx->client->destroyed) + client_disconnect(ctx->client, "Failed to cancel FETCH"); + ctx->client->output_cmd_lock = NULL; + } + ctx->client->output_locked = ctx->client->output_cmd_lock != NULL; + return ret; +} + +int imap_fetch_more_no_lock_update(struct imap_fetch_context *ctx) +{ + int ret; + + ret = imap_fetch_more_int(ctx, FALSE); + if (ret < 0) { + ctx->state.failed = TRUE; + if (!ctx->state.line_finished) { + client_disconnect(ctx->client, + "NOTIFY failed in the middle of FETCH reply"); + } } return ret; } @@ -617,8 +652,6 @@ if (handler->want_deinit) handler->handler(ctx, NULL, handler->context); } - if (ctx->search_args != NULL) - mail_search_args_unref(&ctx->search_args); pool_unref(&ctx->ctx_pool); }
--- a/src/imap/imap-fetch.h Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-fetch.h Mon Aug 13 15:23:32 2012 +0300 @@ -73,8 +73,6 @@ struct client *client; pool_t ctx_pool; - struct mail_search_args *search_args; - enum mail_fetch_field fetch_data; ARRAY_TYPE(const_string) all_headers; @@ -111,6 +109,11 @@ (imap_fetch_handler_t *)handler, context) #endif +int imap_fetch_att_list_parse(struct client *client, pool_t pool, + const struct imap_arg *list, + struct imap_fetch_context **fetch_ctx_r, + const char **error_r); + struct imap_fetch_context * imap_fetch_alloc(struct client *client, pool_t pool); void imap_fetch_free(struct imap_fetch_context **ctx); @@ -119,10 +122,11 @@ bool (*init)(struct imap_fetch_init_context *)); void imap_fetch_add_changed_since(struct imap_fetch_context *ctx, + struct mail_search_args *search_args, uint64_t modseq); -void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box); -void imap_fetch_begin_once(struct imap_fetch_context *ctx, struct mailbox *box); +void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box, + struct mail_search_args *search_args); int imap_fetch_send_vanished(struct client *client, struct mailbox *box, const struct mail_search_args *search_args, const struct imap_fetch_qresync_args *qresync_args);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-list.c Mon Aug 13 15:23:32 2012 +0300 @@ -0,0 +1,35 @@ +/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "imap-list.h" + +bool imap_mailbox_flags2str(string_t *str, enum mailbox_info_flags flags) +{ + unsigned int orig_len = str_len(str); + + if ((flags & MAILBOX_SUBSCRIBED) != 0) + str_append(str, "\\Subscribed "); + + if ((flags & MAILBOX_NOSELECT) != 0) + str_append(str, "\\Noselect "); + if ((flags & MAILBOX_NONEXISTENT) != 0) + str_append(str, "\\NonExistent "); + + if ((flags & MAILBOX_CHILDREN) != 0) + str_append(str, "\\HasChildren "); + else if ((flags & MAILBOX_NOINFERIORS) != 0) + str_append(str, "\\NoInferiors "); + else if ((flags & MAILBOX_NOCHILDREN) != 0) + str_append(str, "\\HasNoChildren "); + + if ((flags & MAILBOX_MARKED) != 0) + str_append(str, "\\Marked "); + if ((flags & MAILBOX_UNMARKED) != 0) + str_append(str, "\\UnMarked "); + + if (str_len(str) == orig_len) + return FALSE; + str_truncate(str, str_len(str)-1); + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-list.h Mon Aug 13 15:23:32 2012 +0300 @@ -0,0 +1,7 @@ +#ifndef IMAP_LIST_H +#define IMAP_LIST_H + +/* Returns TRUE if anything was added to the string. */ +bool imap_mailbox_flags2str(string_t *str, enum mailbox_info_flags flags); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-notify.c Mon Aug 13 15:23:32 2012 +0300 @@ -0,0 +1,518 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "ostream.h" +#include "imap-quote.h" +#include "mailbox-list-notify.h" +#include "mail-search.h" +#include "mail-search-build.h" +#include "imap-commands.h" +#include "imap-fetch.h" +#include "imap-list.h" +#include "imap-status.h" +#include "imap-notify.h" + +#define IMAP_NOTIFY_WATCH_ADD_DELAY_MSECS 1000 + +static bool notify_hook_registered; + +static int imap_notify_list(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec, + enum mailbox_info_flags flags) +{ + string_t *str = t_str_new(128); + char ns_sep = mail_namespace_get_sep(notify_ns->ns); + + str_append(str, "* LIST ("); + imap_mailbox_flags2str(str, flags); + str_append(str, ") \""); + if (ns_sep == '\\') + str_append_c(str, '\\'); + str_append_c(str, ns_sep); + str_append(str, "\" "); + + imap_quote_append_string(str, rec->vname, FALSE); + if (rec->old_vname != NULL) { + str_append(str, " (\"OLDNAME\" ("); + imap_quote_append_string(str, rec->old_vname, FALSE); + str_append(str, "))"); + } + return client_send_line_next(notify_ns->ctx->client, str_c(str)); +} + +static int imap_notify_status(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec) +{ + struct client *client = notify_ns->ctx->client; + struct mailbox *box; + struct imap_status_items items; + struct imap_status_result result; + enum mail_error error; + int ret = 1; + + memset(&items, 0, sizeof(items)); + if ((client->enabled_features & MAILBOX_FEATURE_CONDSTORE) != 0) + items.status |= STATUS_HIGHESTMODSEQ; + + box = mailbox_alloc(notify_ns->ns->list, rec->vname, 0); + switch (rec->event) { + case MAILBOX_LIST_NOTIFY_UIDVALIDITY: + items.status |= STATUS_UIDVALIDITY | STATUS_UIDNEXT | + STATUS_MESSAGES | STATUS_UNSEEN; + break; + case MAILBOX_LIST_NOTIFY_APPENDS: + case MAILBOX_LIST_NOTIFY_EXPUNGES: + items.status |= STATUS_UIDNEXT | STATUS_MESSAGES | STATUS_UNSEEN; + break; + case MAILBOX_LIST_NOTIFY_SEEN_CHANGES: + items.status |= STATUS_UNSEEN; + break; + case MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES: + /* if HIGHESTMODSEQ isn't being sent, don't send anything */ + break; + case MAILBOX_LIST_NOTIFY_CREATE: + case MAILBOX_LIST_NOTIFY_DELETE: + case MAILBOX_LIST_NOTIFY_RENAME: + case MAILBOX_LIST_NOTIFY_SUBSCRIPTION_CHANGE: + i_unreached(); + } + if (items.status == 0) { + /* don't send anything */ + } else if (mailbox_get_status(box, items.status, &result.status) < 0) { + /* hide permission errors from client. we don't want to leak + information about existence of mailboxes where user doesn't + have access to */ + (void)mailbox_get_last_error(box, &error); + if (error != MAIL_ERROR_PERM) + ret = -1; + } else { + ret = imap_status_send(client, rec->vname, &items, &result); + } + mailbox_free(&box); + return ret; +} + +static int +imap_notify_next(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec) +{ + enum mailbox_info_flags mailbox_flags; + int ret = 1; + + switch (rec->event) { + case MAILBOX_LIST_NOTIFY_CREATE: + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + ret = imap_notify_list(notify_ns, rec, mailbox_flags); + break; + case MAILBOX_LIST_NOTIFY_DELETE: + ret = imap_notify_list(notify_ns, rec, MAILBOX_NONEXISTENT); + break; + case MAILBOX_LIST_NOTIFY_RENAME: + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + ret = imap_notify_list(notify_ns, rec, mailbox_flags); + break; + case MAILBOX_LIST_NOTIFY_SUBSCRIPTION_CHANGE: + /* FIXME: set \subscribed when needed */ + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + ret = imap_notify_list(notify_ns, rec, mailbox_flags); + break; + case MAILBOX_LIST_NOTIFY_UIDVALIDITY: + case MAILBOX_LIST_NOTIFY_APPENDS: + case MAILBOX_LIST_NOTIFY_EXPUNGES: + case MAILBOX_LIST_NOTIFY_SEEN_CHANGES: + case MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES: + ret = imap_notify_status(notify_ns, rec); + break; + } + return ret; +} + +static bool +imap_notify_match_event(struct imap_notify_namespace *notify_ns, + const struct imap_notify_mailboxes *notify_boxes, + const struct mailbox_list_notify_rec *rec) +{ + enum imap_notify_event wanted_events = notify_boxes->events; + struct mailbox *box; + bool mailbox_event = FALSE; + + switch (rec->event) { + case MAILBOX_LIST_NOTIFY_CREATE: + case MAILBOX_LIST_NOTIFY_DELETE: + case MAILBOX_LIST_NOTIFY_RENAME: + if ((wanted_events & IMAP_NOTIFY_EVENT_MAILBOX_NAME) == 0) + return FALSE; + break; + case MAILBOX_LIST_NOTIFY_SUBSCRIPTION_CHANGE: + if ((wanted_events & IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE) == 0) + return FALSE; + break; + case MAILBOX_LIST_NOTIFY_UIDVALIDITY: + if ((wanted_events & (IMAP_NOTIFY_EVENT_MESSAGE_NEW | + IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE | + IMAP_NOTIFY_EVENT_FLAG_CHANGE)) == 0) + return FALSE; + mailbox_event = TRUE; + break; + case MAILBOX_LIST_NOTIFY_APPENDS: + if ((wanted_events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) == 0) + return FALSE; + mailbox_event = TRUE; + break; + case MAILBOX_LIST_NOTIFY_EXPUNGES: + if ((wanted_events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) == 0) + return FALSE; + mailbox_event = TRUE; + break; + case MAILBOX_LIST_NOTIFY_SEEN_CHANGES: + case MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES: + if ((wanted_events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) == 0) + return FALSE; + mailbox_event = TRUE; + break; + } + + if (mailbox_event) { + /* if this is an even for selected mailbox, ignore it */ + box = notify_ns->ctx->client->mailbox; + if (box != NULL && + mailbox_equals(box, notify_ns->ns, rec->vname)) + return FALSE; + } + return TRUE; +} + +bool imap_notify_match_mailbox(struct imap_notify_namespace *notify_ns, + const struct imap_notify_mailboxes *notify_boxes, + const char *vname) +{ + const char *const *namep; + unsigned int name_len; + char ns_sep; + + switch (notify_boxes->type) { + case IMAP_NOTIFY_TYPE_SUBSCRIBED: + // FIXME + return TRUE; + case IMAP_NOTIFY_TYPE_SUBTREE: + ns_sep = mail_namespace_get_sep(notify_ns->ns); + array_foreach(¬ify_boxes->names, namep) { + name_len = strlen(*namep); + if (name_len == 0) { + /* everything under root. NOTIFY spec itself + doesn't define this, but we use it for + implementing "personal" */ + return TRUE; + } + if (strncmp(*namep, vname, name_len) == 0 && + (vname[name_len] == '\0' || + vname[name_len] == ns_sep)) + return TRUE; + } + break; + case IMAP_NOTIFY_TYPE_MAILBOX: + array_foreach(¬ify_boxes->names, namep) { + if (strcmp(*namep, vname) == 0) + return TRUE; + } + break; + } + return FALSE; +} + +static bool +imap_notify_match(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec) +{ + const struct imap_notify_mailboxes *notify_boxes; + + array_foreach(¬ify_ns->mailboxes, notify_boxes) { + if (imap_notify_match_event(notify_ns, notify_boxes, rec) && + imap_notify_match_mailbox(notify_ns, notify_boxes, rec->vname)) + return TRUE; + } + return FALSE; +} + +static int imap_client_notify_ns(struct imap_notify_namespace *notify_ns) +{ + const struct mailbox_list_notify_rec *rec; + int ret, ret2 = 1; + + while ((ret = mailbox_list_notify_next(notify_ns->notify, &rec)) > 0) { + if (imap_notify_match(notify_ns, rec)) T_BEGIN { + ret2 = imap_notify_next(notify_ns, rec); + } T_END; + if (ret2 <= 0) + break; + } + if (ret < 0) { + /* failed to get some notifications */ + return -1; + } + return ret2; +} + +static int +imap_client_notify_selected(struct client *client) +{ + struct imap_fetch_context *fetch_ctx = client->notify_ctx->fetch_ctx; + int ret; + + if (!fetch_ctx->state.fetching) + return 1; + + if ((ret = imap_fetch_more_no_lock_update(fetch_ctx)) <= 0) + return ret; + /* finished the FETCH */ + if (imap_fetch_end(fetch_ctx) < 0) + return -1; + return 1; +} + +static int imap_client_notify_more(struct client *client) +{ + struct imap_notify_namespace *notify_ns; + int ret = 1; + + /* send notifications for selected mailbox first. note that it may + leave the client's output stream in the middle of a FETCH reply. */ + if (client->notify_ctx->fetch_ctx != NULL) { + if ((ret = imap_client_notify_selected(client)) < 0) { + client->notify_ctx->fetch_ctx->state.failed = FALSE; + ret = -1; + } + } + + /* send notifications for non-selected mailboxes */ + array_foreach_modifiable(&client->notify_ctx->namespaces, notify_ns) { + if (ret == 0) + break; + if (imap_client_notify_ns(notify_ns) < 0) + ret = -1; + } + + if (ret < 0) { + client_send_line(notify_ns->ctx->client, + "* NO NOTIFY error, some events may have got lost"); + } + return ret; +} + +int imap_client_notify_newmails(struct client *client) +{ + struct imap_fetch_context *fetch_ctx = client->notify_ctx->fetch_ctx; + struct mailbox_status status; + struct mail_search_args *search_args; + struct mail_search_arg *arg; + + i_assert(client->mailbox != NULL); + + if (fetch_ctx == NULL) { + /* FETCH notifications not enabled in this session */ + return 1; + } + if (client->notify_ctx->notifying) + return imap_client_notify_more(client); + client->notify_ctx->notifying = TRUE; + + i_assert(!fetch_ctx->state.fetching); + + mailbox_get_open_status(client->mailbox, STATUS_UIDNEXT, &status); + + search_args = mail_search_build_init(); + arg = mail_search_build_add(search_args, SEARCH_UIDSET); + p_array_init(&arg->value.seqset, search_args->pool, 1); + seq_range_array_add_range(&arg->value.seqset, + client->notify_uidnext, status.uidnext-1); + client->notify_uidnext = status.uidnext; + + imap_fetch_begin(fetch_ctx, client->mailbox, search_args); + mail_search_args_unref(&search_args); + + return imap_client_notify_more(client); +} + +void imap_client_notify_finished(struct client *client) +{ + if (client->notify_ctx != NULL) + client->notify_ctx->notifying = FALSE; +} + +static void notify_callback(void *context) +{ + struct imap_notify_namespace *notify_ns = context; + + o_stream_cork(notify_ns->ctx->client->output); + imap_client_notify_ns(notify_ns); + o_stream_uncork(notify_ns->ctx->client->output); +} + +static enum mailbox_list_notify_event +imap_events_to_notify(enum imap_notify_event events) +{ + enum mailbox_list_notify_event ret = 0; + + if ((events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0) { + ret |= MAILBOX_LIST_NOTIFY_APPENDS | + MAILBOX_LIST_NOTIFY_UIDVALIDITY; + } + if ((events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) != 0) { + ret |= MAILBOX_LIST_NOTIFY_EXPUNGES | + MAILBOX_LIST_NOTIFY_UIDVALIDITY; + } + if ((events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0) { + ret |= MAILBOX_LIST_NOTIFY_SEEN_CHANGES | + MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES | + MAILBOX_LIST_NOTIFY_UIDVALIDITY; + } + if ((events & IMAP_NOTIFY_EVENT_MAILBOX_NAME) != 0) { + ret |= MAILBOX_LIST_NOTIFY_CREATE | + MAILBOX_LIST_NOTIFY_DELETE | + MAILBOX_LIST_NOTIFY_RENAME; + } + if ((events & IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE) != 0) + ret |= MAILBOX_LIST_NOTIFY_SUBSCRIPTION_CHANGE; + return ret; +} + +static void imap_notify_callback(struct mailbox *box, struct client *client) +{ + struct client_command_context *cmd; + enum mailbox_sync_flags sync_flags = 0; + + i_assert(client->command_queue_size == 0); + i_assert(box == client->mailbox); + + /* create a fake command to handle this */ + cmd = client_command_alloc(client); + cmd->tag = "*"; + cmd->name = "NOTIFY-CALLBACK"; + + if (!client->notify_ctx->selected_immediate_expunges) + sync_flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES; + if (cmd_sync(cmd, sync_flags, 0, NULL)) + i_unreached(); + (void)cmd_sync_delayed(client); +} + +static void imap_notify_watch_selected_mailbox(struct client *client) +{ + if (client->command_queue_size > 0) { + /* don't add it until all commands are finished */ + return; + } + if (client->mailbox == NULL) { + /* mailbox not selected */ + return; + } + if (client->notify_ctx == NULL || !client->notify_ctx->selected_set) { + /* client doesn't want selected mailbox notifications */ + return; + + } + mailbox_notify_changes(client->mailbox, imap_notify_callback, client); + client->notify_ctx->watching_mailbox = TRUE; +} + +static void imap_notify_watch_timeout(struct client *client) +{ + timeout_remove(&client->notify_ctx->to_watch); + imap_notify_watch_selected_mailbox(client); +} + +void imap_client_notify_command_freed(struct client *client) +{ + imap_notify_watch_selected_mailbox(client); +} + +static void imap_notify_cmd_hook_pre(struct client_command_context *cmd) +{ + struct imap_notify_context *ctx = cmd->client->notify_ctx; + + if (ctx == NULL) + return; + + /* remove mailbox watcher before starting any commands */ + if (ctx->watching_mailbox) { + mailbox_notify_changes_stop(cmd->client->mailbox); + ctx->watching_mailbox = FALSE; + } + if (ctx->to_watch != NULL) + timeout_remove(&ctx->to_watch); +} + +static void imap_notify_cmd_hook_post(struct client_command_context *cmd) +{ + struct imap_notify_context *ctx = cmd->client->notify_ctx; + + if (ctx == NULL) + return; + + /* add mailbox watched back after a small delay */ + if (ctx->to_watch != NULL) + timeout_reset(ctx->to_watch); + else { + ctx->to_watch = timeout_add(IMAP_NOTIFY_WATCH_ADD_DELAY_MSECS, + imap_notify_watch_timeout, + cmd->client); + } +} + +int imap_notify_begin(struct imap_notify_context *ctx) +{ + struct imap_notify_namespace *notify_ns; + const struct imap_notify_mailboxes *notify_boxes; + enum mailbox_list_notify_event notify_events; + int ret = -1; + + if (!notify_hook_registered) { + notify_hook_registered = TRUE; + command_hook_register(imap_notify_cmd_hook_pre, + imap_notify_cmd_hook_post); + } + + array_foreach_modifiable(&ctx->namespaces, notify_ns) { + notify_events = 0; + array_foreach(¬ify_ns->mailboxes, notify_boxes) { + notify_events |= + imap_events_to_notify(notify_boxes->events); + } + if (mailbox_list_notify_init(notify_ns->ns->list, notify_events, + ¬ify_ns->notify) < 0) { + /* notifications not supported */ + } else { + ret = 0; + mailbox_list_notify_wait(notify_ns->notify, + notify_callback, notify_ns); + } + } + /* enable NOTIFY as long as even one namespace supports it, + ignore the rest */ + return ret; +} + +void imap_notify_deinit(struct imap_notify_context **_ctx) +{ + struct imap_notify_context *ctx = *_ctx; + struct imap_notify_namespace *notify_ns; + + *_ctx = NULL; + + array_foreach_modifiable(&ctx->namespaces, notify_ns) { + if (notify_ns->notify != NULL) + mailbox_list_notify_deinit(¬ify_ns->notify); + } + if (ctx->to_watch != NULL) + timeout_remove(&ctx->to_watch); + if (ctx->fetch_ctx != NULL) + imap_fetch_free(&ctx->fetch_ctx); + pool_unref(&ctx->pool); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-notify.h Mon Aug 13 15:23:32 2012 +0300 @@ -0,0 +1,71 @@ +#ifndef IMAP_NOTIFY_H +#define IMAP_NOTIFY_H + +enum imap_notify_type { + IMAP_NOTIFY_TYPE_SUBSCRIBED, + IMAP_NOTIFY_TYPE_SUBTREE, + IMAP_NOTIFY_TYPE_MAILBOX +}; + +enum imap_notify_event { + IMAP_NOTIFY_EVENT_MESSAGE_NEW = 0x01, + IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE = 0x02, + IMAP_NOTIFY_EVENT_FLAG_CHANGE = 0x04, + IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE = 0x08, + IMAP_NOTIFY_EVENT_MAILBOX_NAME = 0x10, + IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE = 0x20, + IMAP_NOTIFY_EVENT_MAILBOX_METADATA_CHANGE = 0x40, + IMAP_NOTIFY_EVENT_SERVER_METADATA_CHANGE = 0x80 +}; +#define UNSUPPORTED_EVENTS \ + (IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE | \ + IMAP_NOTIFY_EVENT_MAILBOX_METADATA_CHANGE | \ + IMAP_NOTIFY_EVENT_SERVER_METADATA_CHANGE) + +struct imap_notify_mailboxes { + enum imap_notify_event events; + enum imap_notify_type type; + ARRAY_TYPE(const_string) names; +}; + +struct imap_notify_namespace { + struct imap_notify_context *ctx; + struct mail_namespace *ns; + + struct mailbox_list_notify *notify; + ARRAY_DEFINE(mailboxes, struct imap_notify_mailboxes); +}; + +struct imap_notify_context { + pool_t pool; + struct client *client; + const char *error; + + ARRAY_DEFINE(namespaces, struct imap_notify_namespace); + enum imap_notify_event selected_events; + enum imap_notify_event global_used_events; + unsigned int global_max_mailbox_names; + + struct imap_fetch_context *fetch_ctx; + struct timeout *to_watch; + + unsigned int selected_set:1; + unsigned int selected_immediate_expunges:1; + unsigned int send_immediate_status:1; + unsigned int watching_mailbox:1; + unsigned int notifying:1; +}; + +bool imap_notify_match_mailbox(struct imap_notify_namespace *notify_ns, + const struct imap_notify_mailboxes *notify_boxes, + const char *vname); + +int imap_client_notify_newmails(struct client *client); +void imap_client_notify_finished(struct client *client); + +void imap_client_notify_command_freed(struct client *client); + +int imap_notify_begin(struct imap_notify_context *ctx); +void imap_notify_deinit(struct imap_notify_context **ctx); + +#endif
--- a/src/imap/imap-search.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-search.c Mon Aug 13 15:23:32 2012 +0300 @@ -10,6 +10,7 @@ #include "imap-seqset.h" #include "imap-util.h" #include "mail-search-build.h" +#include "imap-fetch.h" #include "imap-commands.h" #include "imap-search-args.h" #include "imap-search.h" @@ -42,10 +43,28 @@ } static bool +search_parse_fetch_att(struct imap_search_context *ctx, + const struct imap_arg *update_args) +{ + const char *error; + + ctx->fetch_pool = pool_alloconly_create("search update fetch", 512); + if (imap_fetch_att_list_parse(ctx->cmd->client, ctx->fetch_pool, + update_args, &ctx->fetch_ctx, &error) < 0) { + client_send_command_error(ctx->cmd, t_strconcat( + "SEARCH UPDATE fetch-att: ", error, NULL)); + pool_unref(&ctx->fetch_pool); + return FALSE; + } + return TRUE; +} + +static bool search_parse_return_options(struct imap_search_context *ctx, const struct imap_arg *args) { struct client_command_context *cmd = ctx->cmd; + const struct imap_arg *update_args; const char *name, *str; unsigned int idx; @@ -69,9 +88,19 @@ ctx->return_options |= SEARCH_RETURN_SAVE; else if (strcmp(name, "CONTEXT") == 0) { /* no-op */ - } else if (strcmp(name, "UPDATE") == 0) + } else if (strcmp(name, "UPDATE") == 0) { + if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0) { + client_send_command_error(cmd, + "SEARCH return options have duplicate UPDATE."); + return FALSE; + } ctx->return_options |= SEARCH_RETURN_UPDATE; - else if (strcmp(name, "RELEVANCY") == 0) + if (imap_arg_get_list(args, &update_args)) { + if (!search_parse_fetch_att(ctx, update_args)) + return FALSE; + args++; + } + } else if (strcmp(name, "RELEVANCY") == 0) ctx->return_options |= SEARCH_RETURN_RELEVANCY; else if (strcmp(name, "PARTIAL") == 0) { if (ctx->partial1 != 0) { @@ -153,6 +182,7 @@ str_append_c(str, ']'); client_send_line(client, str_c(str)); ctx->return_options &= ~SEARCH_RETURN_UPDATE; + imap_search_context_free(ctx); return; } result = mailbox_search_result_save(ctx->search_ctx, @@ -163,6 +193,10 @@ update->tag = i_strdup(ctx->cmd->tag); update->result = result; update->return_uids = ctx->cmd->uid; + update->fetch_pool = ctx->fetch_pool; + update->fetch_ctx = ctx->fetch_ctx; + ctx->fetch_pool = NULL; + ctx->fetch_ctx = NULL; } static void imap_search_send_result_standard(struct imap_search_context *ctx) @@ -510,20 +544,24 @@ return 1; } - if (!search_parse_return_options(ctx, list_args)) + if (!search_parse_return_options(ctx, list_args)) { + imap_search_context_free(ctx); return -1; + } if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) { /* wait if there is another SEARCH SAVE command running. */ - cmd->search_save_result = TRUE; - if (client_handle_search_save_ambiguity(cmd)) + if (client_handle_search_save_ambiguity(cmd)) { + imap_search_context_free(ctx); return 0; + } /* make sure the search result gets cleared if SEARCH fails */ if (array_is_created(&cmd->client->search_saved_uidset)) array_clear(&cmd->client->search_saved_uidset); else i_array_init(&cmd->client->search_saved_uidset, 128); + cmd->search_save_result = TRUE; } *_args = args + 2; @@ -553,6 +591,9 @@ i_array_init(&ctx->result, 128); if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0) imap_search_result_save(ctx); + else { + i_assert(ctx->fetch_ctx == NULL); + } if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0) i_array_init(&ctx->relevancy_scores, 128); @@ -592,7 +633,26 @@ array_free(&ctx->result); mail_search_args_deinit(ctx->sargs); mail_search_args_unref(&ctx->sargs); + imap_search_context_free(ctx); ctx->cmd->context = NULL; return ret; } + +void imap_search_context_free(struct imap_search_context *ctx) +{ + if (ctx->fetch_ctx != NULL) { + imap_fetch_free(&ctx->fetch_ctx); + pool_unref(&ctx->fetch_pool); + } +} + +void imap_search_update_free(struct imap_search_update *update) +{ + if (update->fetch_ctx != NULL) { + imap_fetch_free(&update->fetch_ctx); + pool_unref(&update->fetch_pool); + } + mailbox_search_result_free(&update->result); + i_free(update->tag); +}
--- a/src/imap/imap-search.h Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-search.h Mon Aug 13 15:23:32 2012 +0300 @@ -26,6 +26,9 @@ struct mailbox_transaction_context *trans; struct mail_search_context *search_ctx; + pool_t fetch_pool; + struct imap_fetch_context *fetch_ctx; + struct mail_search_args *sargs; enum search_return_options return_options; uint32_t partial1, partial2; @@ -47,9 +50,11 @@ int cmd_search_parse_return_if_found(struct imap_search_context *ctx, const struct imap_arg **args); +void imap_search_context_free(struct imap_search_context *ctx); bool imap_search_start(struct imap_search_context *ctx, struct mail_search_args *sargs, const enum mail_sort_type *sort_program) ATTR_NULL(3); +void imap_search_update_free(struct imap_search_update *update); #endif
--- a/src/imap/imap-sync.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/imap-sync.c Mon Aug 13 15:23:32 2012 +0300 @@ -3,12 +3,15 @@ #include "imap-common.h" #include "str.h" #include "ostream.h" +#include "mail-user.h" #include "mail-storage.h" -#include "mail-user.h" +#include "mail-search-build.h" #include "imap-quote.h" #include "imap-util.h" +#include "imap-fetch.h" +#include "imap-notify.h" +#include "imap-commands.h" #include "imap-sync.h" -#include "imap-commands.h" struct client_sync_context { /* if multiple commands are in progress, we may need to wait for them @@ -29,17 +32,24 @@ struct mailbox_sync_context *sync_ctx; struct mail *mail; + struct mailbox_status status; + struct mailbox_sync_status sync_status; + struct mailbox_sync_rec sync_rec; ARRAY_TYPE(keywords) tmp_keywords; ARRAY_TYPE(seq_range) expunges; uint32_t seq; ARRAY_TYPE(seq_range) search_adds, search_removes; + unsigned int search_update_idx; unsigned int messages_count; unsigned int failed:1; + unsigned int finished:1; unsigned int no_newmail:1; + unsigned int have_new_mails:1; + unsigned int search_update_notifying:1; }; static void uids_to_seqs(struct mailbox *box, ARRAY_TYPE(seq_range) *uids) @@ -67,17 +77,78 @@ } T_END; } -static void +static int search_update_fetch_more(const struct imap_search_update *update) +{ + int ret; + + if ((ret = imap_fetch_more_no_lock_update(update->fetch_ctx)) <= 0) + return ret; + /* finished the FETCH */ + if (imap_fetch_end(update->fetch_ctx) < 0) + return -1; + return 1; +} + +static int +imap_sync_send_fetch_to_search_update(struct imap_sync_context *ctx, + const struct imap_search_update *update) +{ + struct mail_search_args *search_args; + struct mail_search_arg *arg; + ARRAY_TYPE(seq_range) seqs; + + if (ctx->search_update_notifying) + return search_update_fetch_more(update); + + i_assert(!update->fetch_ctx->state.fetching); + + if (array_count(&ctx->search_adds) == 0 || !ctx->have_new_mails) + return 1; + + search_args = mail_search_build_init(); + arg = mail_search_build_add(search_args, SEARCH_UIDSET); + p_array_init(&arg->value.seqset, search_args->pool, 1); + + /* find the newly appended messages: ctx->messages_count is the message + count before new messages found by sync, client->messages_count is + the number of messages after. */ + t_array_init(&seqs, 1); + seq_range_array_add_range(&seqs, ctx->messages_count+1, + ctx->client->messages_count); + mailbox_get_uid_range(ctx->client->mailbox, &seqs, &arg->value.seqset); + /* remove messages not in the search_adds list */ + seq_range_array_intersect(&arg->value.seqset, &ctx->search_adds); + + imap_fetch_begin(update->fetch_ctx, ctx->client->mailbox, search_args); + mail_search_args_unref(&search_args); + return search_update_fetch_more(update); +} + +static int imap_sync_send_search_update(struct imap_sync_context *ctx, - const struct imap_search_update *update) + const struct imap_search_update *update, + bool removes_only) { string_t *cmd; + int ret = 1; - mailbox_search_result_sync(update->result, &ctx->search_removes, - &ctx->search_adds); + if (!ctx->search_update_notifying) { + mailbox_search_result_sync(update->result, &ctx->search_removes, + &ctx->search_adds); + } if (array_count(&ctx->search_adds) == 0 && array_count(&ctx->search_removes) == 0) - return; + return 1; + + i_assert(array_count(&ctx->search_adds) == 0 || !removes_only); + if (update->fetch_ctx != NULL) { + ret = imap_sync_send_fetch_to_search_update(ctx, update); + if (ret == 0) { + ctx->search_update_notifying = TRUE; + return 0; + } + } + ctx->search_update_notifying = FALSE; cmd = t_str_new(256); str_append(cmd, "* ESEARCH (TAG "); @@ -103,23 +174,35 @@ } str_append(cmd, "\r\n"); o_stream_nsend(ctx->client->output, str_data(cmd), str_len(cmd)); + return ret; } -static void imap_sync_send_search_updates(struct imap_sync_context *ctx) +static int +imap_sync_send_search_updates(struct imap_sync_context *ctx, bool removes_only) { - const struct imap_search_update *update; + const struct imap_search_update *updates; + unsigned int i, count; + int ret = 1; if (!array_is_created(&ctx->client->search_updates)) - return; + return 1; if (!array_is_created(&ctx->search_removes)) { i_array_init(&ctx->search_removes, 64); i_array_init(&ctx->search_adds, 128); } - array_foreach(&ctx->client->search_updates, update) T_BEGIN { - imap_sync_send_search_update(ctx, update); - } T_END; + updates = array_get(&ctx->client->search_updates, &count); + for (i = ctx->search_update_idx; i < count; i++) { + T_BEGIN { + ret = imap_sync_send_search_update(ctx, &updates[i], + removes_only); + } T_END; + if (ret <= 0) + break; + } + ctx->search_update_idx = i; + return ret; } struct imap_sync_context * @@ -130,6 +213,11 @@ i_assert(client->mailbox == box); + if (client->notify_immediate_expunges) { + /* NOTIFY enabled without SELECTED-DELAYED */ + flags &= ~MAILBOX_SYNC_FLAG_NO_EXPUNGES; + } + ctx = i_new(struct imap_sync_context, 1); ctx->client = client; ctx->box = box; @@ -155,37 +243,38 @@ /* send search updates the first time after sync is initialized. it now contains expunged messages that must be sent before EXPUNGE replies. */ - imap_sync_send_search_updates(ctx); + if (imap_sync_send_search_updates(ctx, TRUE) == 0) + i_unreached(); + ctx->search_update_idx = 0; return ctx; } static void imap_sync_send_highestmodseq(struct imap_sync_context *ctx, - const struct mailbox_status *status, - const struct mailbox_sync_status *sync_status, struct client_command_context *sync_cmd) { struct client *client = ctx->client; uint64_t send_modseq = 0; - if (sync_status->sync_delayed_expunges && + if (ctx->sync_status.sync_delayed_expunges && client->highest_fetch_modseq > client->sync_last_full_modseq) { /* if client updates highest-modseq using returned MODSEQs it loses expunges. try to avoid this by sending it a lower pre-expunge HIGHESTMODSEQ reply. */ send_modseq = client->sync_last_full_modseq; - } else if (!sync_status->sync_delayed_expunges && - status->highest_modseq > client->sync_last_full_modseq && - status->highest_modseq > client->highest_fetch_modseq) { + } else if (!ctx->sync_status.sync_delayed_expunges && + ctx->status.highest_modseq > client->sync_last_full_modseq && + ctx->status.highest_modseq > client->highest_fetch_modseq) { /* we've probably sent some VANISHED or EXISTS replies which increased the highest-modseq. notify the client about this. */ - send_modseq = status->highest_modseq; + send_modseq = ctx->status.highest_modseq; } if (send_modseq == 0) { /* no sending */ } else if (sync_cmd->sync != NULL && /* IDLE doesn't have ->sync */ + sync_cmd->sync->tagline != NULL && /* NOTIFY doesn't have tagline */ strncmp(sync_cmd->sync->tagline, "OK ", 3) == 0 && sync_cmd->sync->tagline[3] != '[') { /* modify the tagged reply directly */ @@ -200,66 +289,95 @@ (unsigned long long)send_modseq)); } - if (!sync_status->sync_delayed_expunges) { + if (!ctx->sync_status.sync_delayed_expunges) { /* no delayed expunges, remember this for future */ - client->sync_last_full_modseq = status->highest_modseq; + client->sync_last_full_modseq = ctx->status.highest_modseq; } client->highest_fetch_modseq = 0; } -int imap_sync_deinit(struct imap_sync_context *ctx, - struct client_command_context *sync_cmd) +static int imap_sync_finish(struct imap_sync_context *ctx) { struct client *client = ctx->client; - struct mailbox_status status; - struct mailbox_sync_status sync_status; - int ret; + int ret = ctx->failed ? -1 : 0; + + if (ctx->finished) + return ret; + ctx->finished = TRUE; mail_free(&ctx->mail); if (array_is_created(&ctx->expunges)) array_free(&ctx->expunges); - if (mailbox_sync_deinit(&ctx->sync_ctx, &sync_status) < 0 || + if (mailbox_sync_deinit(&ctx->sync_ctx, &ctx->sync_status) < 0 || ctx->failed) { mailbox_transaction_rollback(&ctx->t); - array_free(&ctx->tmp_keywords); - i_free(ctx); + ctx->failed = TRUE; return -1; } mailbox_get_open_status(ctx->box, STATUS_UIDVALIDITY | STATUS_MESSAGES | STATUS_RECENT | - STATUS_HIGHESTMODSEQ, &status); + STATUS_HIGHESTMODSEQ, &ctx->status); - ret = mailbox_transaction_commit(&ctx->t); + if (mailbox_transaction_commit(&ctx->t) < 0) { + ctx->failed = TRUE; + ret = -1; + } - if (status.uidvalidity != client->uidvalidity) { + if (ctx->status.uidvalidity != client->uidvalidity) { /* most clients would get confused by this. disconnect them. */ client_disconnect_with_error(client, "Mailbox UIDVALIDITY changed"); } if (!ctx->no_newmail) { - if (status.messages < ctx->messages_count) + if (ctx->status.messages < ctx->messages_count) i_panic("Message count decreased"); - client->messages_count = status.messages; - if (status.messages != ctx->messages_count) { + if (ctx->status.messages != ctx->messages_count && + client->notify_count_changes) { client_send_line(client, - t_strdup_printf("* %u EXISTS", status.messages)); + t_strdup_printf("* %u EXISTS", ctx->status.messages)); + ctx->have_new_mails = TRUE; + } + if (ctx->status.recent != client->recent_count && + client->notify_count_changes) { + client_send_line(client, + t_strdup_printf("* %u RECENT", ctx->status.recent)); } - if (status.recent != client->recent_count && - !ctx->no_newmail) { - client->recent_count = status.recent; - client_send_line(client, - t_strdup_printf("* %u RECENT", status.recent)); - } + client->messages_count = ctx->status.messages; + client->recent_count = ctx->status.recent; } + return ret; +} + +static int imap_sync_notify_more(struct imap_sync_context *ctx) +{ + int ret = 1; + + if (ctx->have_new_mails && ctx->client->notify_ctx != NULL) { + /* send FETCH replies for the new mails */ + if ((ret = imap_client_notify_newmails(ctx->client)) == 0) + return 0; + if (ret < 0) + ctx->failed = TRUE; + } + /* send search updates the second time after syncing in done. now it contains added/removed messages. */ - imap_sync_send_search_updates(ctx); + if ((ret = imap_sync_send_search_updates(ctx, FALSE)) < 0) + ctx->failed = TRUE; + return ret; +} - if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0) { - imap_sync_send_highestmodseq(ctx, &status, &sync_status, - sync_cmd); - } +int imap_sync_deinit(struct imap_sync_context *ctx, + struct client_command_context *sync_cmd) +{ + int ret; + + ret = imap_sync_finish(ctx); + imap_client_notify_finished(ctx->client); + + if ((ctx->client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0) + imap_sync_send_highestmodseq(ctx, sync_cmd); if (array_is_created(&ctx->search_removes)) { array_free(&ctx->search_removes); @@ -373,6 +491,11 @@ { int ret = 1; + if (!ctx->client->notify_count_changes) { + /* NOTIFY: MessageEvent not specified for selected mailbox */ + return 1; + } + if (array_is_created(&ctx->expunges)) { /* Use a single VANISHED line */ seq_range_array_add_range(&ctx->expunges, @@ -400,6 +523,9 @@ string_t *str; int ret = 1; + if (ctx->finished) + return imap_sync_notify_more(ctx); + /* finish syncing even when client has disconnected. otherwise our internal state (ctx->messages_count) can get messed up and unless we immediately stop handling all commands and syncs we could end up @@ -431,6 +557,11 @@ ctx->sync_rec.type == MAILBOX_SYNC_TYPE_EXPUNGE); switch (ctx->sync_rec.type) { case MAILBOX_SYNC_TYPE_FLAGS: + if (!ctx->client->notify_flag_changes) { + /* NOTIFY: FlagChange not specified for + selected mailbox */ + break; + } if (ctx->seq == 0) ctx->seq = ctx->sync_rec.seq1; @@ -458,6 +589,13 @@ if ((ctx->client->enabled_features & MAILBOX_FEATURE_CONDSTORE) == 0) break; + if (!ctx->client->notify_flag_changes) { + /* NOTIFY: FlagChange not specified for + selected mailbox. The RFC doesn't explicitly + specify MODSEQ changes, but they're close + enough to flag changes. */ + break; + } if (ctx->seq == 0) ctx->seq = ctx->sync_rec.seq1; @@ -480,6 +618,11 @@ } if (array_is_created(&ctx->expunges)) imap_sync_vanished(ctx); + if (ret > 0) { + if (imap_sync_finish(ctx) < 0) + return -1; + return imap_sync_more(ctx); + } return ret; } @@ -499,10 +642,10 @@ { if (cmd->sync->callback != NULL) return cmd->sync->callback(cmd); - else { + + if (cmd->sync->tagline != NULL) client_send_tagline(cmd, cmd->sync->tagline); - return TRUE; - } + return TRUE; } static bool cmd_sync_continue(struct client_command_context *sync_cmd) @@ -586,6 +729,7 @@ client->sync_counter++; no_newmail = (client->set->parsed_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 && + client->notify_ctx == NULL && /* always disabled with NOTIFY */ (imap_flags & IMAP_SYNC_FLAG_SAFE) == 0; if (no_newmail) { /* expunges might break the client just as badly as new mail @@ -620,7 +764,7 @@ { struct client *client = cmd->client; - i_assert(client->output_lock == cmd || client->output_lock == NULL); + i_assert(!client->output_locked); if (cmd->cancel) return TRUE; @@ -628,7 +772,8 @@ if (client->mailbox == NULL) { /* no mailbox selected, no point in delaying the sync */ i_assert(callback == NULL); - client_send_tagline(cmd, tagline); + if (tagline != NULL) + client_send_tagline(cmd, tagline); return TRUE; } @@ -643,7 +788,6 @@ cmd->func = NULL; cmd->context = NULL; - client->output_lock = NULL; if (client->input_lock == cmd) client->input_lock = NULL; return FALSE; @@ -693,7 +837,7 @@ { struct client_command_context *cmd, *first_expunge, *first_nonexpunge; - if (client->output_lock != NULL) { + if (client->output_locked) { /* wait until we can send output to client */ return FALSE; }
--- a/src/imap/main.c Mon Aug 13 15:20:33 2012 +0300 +++ b/src/imap/main.c Mon Aug 13 15:23:32 2012 +0300 @@ -85,7 +85,7 @@ static void client_kill_idle(struct client *client) { - if (client->output_lock != NULL) + if (client->output_locked) return; client_send_line(client, "* BYE Server shutting down.");