]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Implemented SEARCHRES extension.
authorTimo Sirainen <tss@iki.fi>
Sun, 16 Mar 2008 09:05:53 +0000 (11:05 +0200)
committerTimo Sirainen <tss@iki.fi>
Sun, 16 Mar 2008 09:05:53 +0000 (11:05 +0200)
--HG--
branch : HEAD

15 files changed:
configure.in
src/imap/client.c
src/imap/client.h
src/imap/cmd-copy.c
src/imap/cmd-expunge.c
src/imap/cmd-fetch.c
src/imap/cmd-search.c
src/imap/cmd-sort.c
src/imap/cmd-store.c
src/imap/cmd-thread.c
src/imap/imap-fetch.c
src/imap/imap-search.c
src/imap/imap-search.h
src/lib-storage/mail-search-build.c
src/lib-storage/mail-search-build.h

index a6370a98009bdfa19cfe4268f5be762b2cf4f9be..5614bc5828fc266b7f2185ccb0925e5c41c52c8e 100644 (file)
@@ -2119,7 +2119,7 @@ dnl **
 dnl ** capabilities
 dnl **
 
-capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH"
+capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH SEARCHRES"
 AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities)
 
 CFLAGS="$CFLAGS $EXTRA_CFLAGS"
index 622531b1f211ca548c5e9fdb62731c13ea0626a1..248b6c5f3c4fc616b829d2b477d3e9cdab61f9fd 100644 (file)
@@ -155,6 +155,8 @@ void client_destroy(struct client *client, const char *reason)
                        i_error("close(client out) failed: %m");
        }
 
+       if (array_is_created(&client->search_saved_uidset))
+               array_free(&client->search_saved_uidset);
        pool_unref(&client->command_pool);
        i_free(client);
 
@@ -780,6 +782,27 @@ int client_output(struct client *client)
        }
 }
 
+bool client_handle_search_save_ambiguity(struct client_command_context *cmd)
+{
+       struct client_command_context *old_cmd = cmd->next;
+
+       /* search only commands that were added before this command
+          (commands are prepended to the queue, so they're after ourself) */
+       for (; old_cmd != NULL; old_cmd = old_cmd->next) {
+               if (old_cmd->search_save_result)
+                       break;
+       }
+       if (old_cmd == NULL)
+               return FALSE;
+
+       /* ambiguity, wait until it's over */
+       i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
+       cmd->client->input_lock = cmd;
+       cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+       io_remove(&cmd->client->io);
+       return TRUE;
+}
+
 void client_enable(struct client *client, enum mailbox_feature features)
 {
        struct mailbox_status status;
index 8d13e3860664947df6f44015fca14b3ce86928a1..e24ea8162f592ddc70b9ece9a2061c10785f838e 100644 (file)
@@ -53,6 +53,7 @@ struct client_command_context {
        unsigned int uid:1; /* used UID command */
        unsigned int cancel:1; /* command is wanted to be cancelled */
        unsigned int param_error:1;
+       unsigned int search_save_result:1; /* search result is being updated */
        unsigned int temp_executed:1; /* temporary execution state tracking */
 };
 
@@ -78,9 +79,13 @@ struct client {
        struct imap_parser *free_parser;
        /* command_pool is cleared when the command queue gets empty */
        pool_t command_pool;
+       /* New commands are always prepended to the queue */
        struct client_command_context *command_queue;
        unsigned int command_queue_size;
 
+       /* SEARCHRES extension: Last saved SEARCH result */
+       ARRAY_TYPE(seq_range) search_saved_uidset;
+
        /* client input/output is locked by this command */
        struct client_command_context *input_lock;
        struct client_command_context *output_lock;
@@ -127,6 +132,10 @@ bool client_read_args(struct client_command_context *cmd, unsigned int count,
 bool client_read_string_args(struct client_command_context *cmd,
                             unsigned int count, ...);
 
+/* SEARCHRES extension: Call if $ is being used/updated, returns TRUE if we
+   have to wait for an existing SEARCH SAVE to finish. */
+bool client_handle_search_save_ambiguity(struct client_command_context *cmd);
+
 void client_enable(struct client *client, enum mailbox_feature features);
 
 void clients_init(void);
index 858f8bea9b9f479e2dea0ee4f4c4b396c9e15f81..9818cf926d3b25a173769bfa95dc8f96a264883f 100644 (file)
@@ -109,9 +109,9 @@ bool cmd_copy(struct client_command_context *cmd)
        if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE))
                return TRUE;
 
-       search_arg = imap_search_get_seqset(cmd, messageset, cmd->uid);
-       if (search_arg == NULL)
-               return TRUE;
+       ret = imap_search_get_seqset(cmd, messageset, cmd->uid, &search_arg);
+       if (ret <= 0)
+               return ret < 0;
 
        storage = client_find_storage(cmd, &mailbox);
        if (storage == NULL)
index 1502a7c1ed235734d756760720a6b45cdca64f92..6b99143d066f90635fdc534b2192783fcb277b5d 100644 (file)
@@ -62,6 +62,7 @@ bool cmd_uid_expunge(struct client_command_context *cmd)
        const struct imap_arg *args;
        struct mail_search_arg *search_arg;
        const char *uidset;
+       int ret;
 
        if (!client_read_args(cmd, 1, 0, &args))
                return FALSE;
@@ -75,9 +76,10 @@ bool cmd_uid_expunge(struct client_command_context *cmd)
                return TRUE;
        }
 
-       search_arg = imap_search_get_seqset(cmd, uidset, TRUE);
-       return search_arg == NULL ? TRUE :
-               cmd_expunge_finish(cmd, search_arg);
+       ret = imap_search_get_seqset(cmd, uidset, TRUE, &search_arg);
+       if (ret <= 0)
+               return ret < 0;
+       return cmd_expunge_finish(cmd, search_arg);
 }
 
 bool cmd_expunge(struct client_command_context *cmd)
index ca05dc1fbf557c0dc2ba6155a74898656af078d9..14ed32fc55dcc5f843c2b4771bf9cb84abadc771 100644 (file)
@@ -172,10 +172,12 @@ static bool cmd_fetch_continue(struct client_command_context *cmd)
 
 bool cmd_fetch(struct client_command_context *cmd)
 {
+       struct client *client = cmd->client;
        struct imap_fetch_context *ctx;
        const struct imap_arg *args;
        struct mail_search_arg *search_arg;
        const char *messageset;
+       int ret;
 
        if (!client_read_args(cmd, 0, 0, &args))
                return FALSE;
@@ -183,7 +185,7 @@ bool cmd_fetch(struct client_command_context *cmd)
        if (!client_verify_open_mailbox(cmd))
                return TRUE;
 
-       /* <messageset> <field(s)> [(CHANGEDSINCE <modseq>)] */
+       /* <messageset> <field(s)> [(modifiers)] */
        messageset = imap_arg_string(&args[0]);
        if (messageset == NULL ||
            (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM) ||
@@ -192,11 +194,13 @@ bool cmd_fetch(struct client_command_context *cmd)
                return TRUE;
        }
 
-       search_arg = imap_search_get_anyset(cmd, messageset, cmd->uid);
-       if (search_arg == NULL)
-               return TRUE;
+       /* UID FETCH VANISHED needs the uidset, so convert it to
+          sequence set later */
+       ret = imap_search_get_anyset(cmd, messageset, cmd->uid, &search_arg);
+       if (ret <= 0)
+               return ret < 0;
 
-       ctx = imap_fetch_init(cmd, cmd->client->mailbox);
+       ctx = imap_fetch_init(cmd, client->mailbox);
        if (ctx == NULL)
                return TRUE;
        ctx->search_args = search_arg;
index 7b3ef45df1bea249a3eadce19086ab489d8f37bc..6df9379ddb8482696ab0b6db7d7efca86957a916 100644 (file)
@@ -17,8 +17,10 @@ enum search_return_options {
        SEARCH_RETURN_MAX               = 0x04,
        SEARCH_RETURN_ALL               = 0x08,
        SEARCH_RETURN_COUNT             = 0x10,
-       SEARCH_RETURN_MODSEQ            = 0x20
-#define SEARCH_RETURN_EXTRAS (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_MODSEQ)
+       SEARCH_RETURN_MODSEQ            = 0x20,
+       SEARCH_RETURN_SAVE              = 0x40
+#define SEARCH_RETURN_EXTRAS \
+       (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE)
 };
 
 struct imap_search_context {
@@ -38,36 +40,42 @@ struct imap_search_context {
        struct timeval start_time;
 };
 
-static bool search_parse_return_options(struct imap_search_context *ctx,
-                                       const struct imap_arg *args)
+static bool
+search_parse_return_options(struct client_command_context *cmd,
+                           const struct imap_arg *args,
+                           enum search_return_options *return_options_r)
 {
        const char *name;
 
+       *return_options_r = 0;
+
        while (args->type != IMAP_ARG_EOL) {
                if (args->type != IMAP_ARG_ATOM) {
-                       client_send_command_error(ctx->cmd,
+                       client_send_command_error(cmd,
                                "SEARCH return options contain non-atoms.");
                        return FALSE;
                }
                name = t_str_ucase(IMAP_ARG_STR(args));
                args++;
                if (strcmp(name, "MIN") == 0)
-                       ctx->return_options |= SEARCH_RETURN_MIN;
+                       *return_options_r |= SEARCH_RETURN_MIN;
                else if (strcmp(name, "MAX") == 0)
-                       ctx->return_options |= SEARCH_RETURN_MAX;
+                       *return_options_r |= SEARCH_RETURN_MAX;
                else if (strcmp(name, "ALL") == 0)
-                       ctx->return_options |= SEARCH_RETURN_ALL;
+                       *return_options_r |= SEARCH_RETURN_ALL;
                else if (strcmp(name, "COUNT") == 0)
-                       ctx->return_options |= SEARCH_RETURN_COUNT;
+                       *return_options_r |= SEARCH_RETURN_COUNT;
+               else if (strcmp(name, "SAVE") == 0)
+                       *return_options_r |= SEARCH_RETURN_SAVE;
                else {
-                       client_send_command_error(ctx->cmd,
+                       client_send_command_error(cmd,
                                "Unknown SEARCH return option");
                        return FALSE;
                }
        }
-       if (ctx->return_options == 0)
-               ctx->return_options = SEARCH_RETURN_ALL;
-       ctx->return_options |= SEARCH_RETURN_ESEARCH;
+       if (*return_options_r == 0)
+               *return_options_r = SEARCH_RETURN_ALL;
+       *return_options_r |= SEARCH_RETURN_ESEARCH;
        return TRUE;
 }
 
@@ -139,6 +147,7 @@ static void imap_search_send_result_standard(struct imap_search_context *ctx)
 
 static void imap_search_send_result(struct imap_search_context *ctx)
 {
+       struct client *client = ctx->cmd->client;
        const struct seq_range *range;
        unsigned int count;
        string_t *str;
@@ -148,6 +157,13 @@ static void imap_search_send_result(struct imap_search_context *ctx)
                return;
        }
 
+       if (ctx->return_options ==
+           (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_SAVE)) {
+               /* we only wanted to save the result, don't return
+                  ESEARCH result. */
+               return;
+       }
+
        str = str_new(default_pool, 1024);
        str_append(str, "* ESEARCH (TAG ");
        imap_quote_append_string(str, ctx->cmd->tag, FALSE);
@@ -172,8 +188,7 @@ static void imap_search_send_result(struct imap_search_context *ctx)
                            (unsigned long long)ctx->highest_seen_modseq);
        }
        str_append(str, "\r\n");
-       o_stream_send(ctx->cmd->client->output,
-                     str_data(str), str_len(str));
+       o_stream_send(client->output, str_data(str), str_len(str));
 }
 
 static int imap_search_deinit(struct imap_search_context *ctx)
@@ -183,8 +198,14 @@ static int imap_search_deinit(struct imap_search_context *ctx)
        mail_free(&ctx->mail);
        if (mailbox_search_deinit(&ctx->search_ctx) < 0)
                ret = -1;
-       else if (!ctx->cmd->cancel)
+
+       if (ret == 0 && !ctx->cmd->cancel)
                imap_search_send_result(ctx);
+       else {
+               /* search failed */
+               if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0)
+                       array_clear(&ctx->cmd->client->search_saved_uidset);
+       }
 
        (void)mailbox_transaction_commit(&ctx->trans);
 
@@ -197,6 +218,21 @@ static int imap_search_deinit(struct imap_search_context *ctx)
        return ret;
 }
 
+static void search_update_mail(struct imap_search_context *ctx)
+{
+       uint64_t modseq;
+
+       if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
+               modseq = mail_get_modseq(ctx->mail);
+               if (ctx->highest_seen_modseq < modseq)
+                       ctx->highest_seen_modseq = modseq;
+       }
+       if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
+               seq_range_array_add(&ctx->cmd->client->search_saved_uidset,
+                                   0, ctx->mail->uid);
+       }
+}
+
 static bool cmd_search_more(struct client_command_context *cmd)
 {
        struct imap_search_context *ctx = cmd->context;
@@ -204,9 +240,8 @@ static bool cmd_search_more(struct client_command_context *cmd)
        struct timeval end_time;
        const struct seq_range *range;
        unsigned int count;
-       uint64_t modseq;
        uint32_t id, id_min, id_max;
-       bool tryagain;
+       bool tryagain, minmax;
 
        if (cmd->cancel) {
                (void)imap_search_deinit(ctx);
@@ -222,32 +257,29 @@ static bool cmd_search_more(struct client_command_context *cmd)
                id_max = range[count-1].seq2;
        }
 
+       minmax = (opts & (SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) != 0 &&
+               (opts & ~(SEARCH_RETURN_EXTRAS |
+                         SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) == 0;
        while (mailbox_search_next_nonblock(ctx->search_ctx, ctx->mail,
                                            &tryagain) > 0) {
                id = cmd->uid ? ctx->mail->uid : ctx->mail->seq;
                ctx->result_count++;
 
-               if ((opts & ~(SEARCH_RETURN_EXTRAS |
-                             SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) == 0) {
+               if (minmax) {
                        /* we only care about min/max */
                        if (id < id_min && (opts & SEARCH_RETURN_MIN) != 0)
                                id_min = id;
                        if (id > id_max && (opts & SEARCH_RETURN_MAX) != 0)
                                id_max = id;
                        if (id == id_min || id == id_max) {
-                               /* modseq is looked up when we know the
-                                  actual min/max values */
+                               /* return option updates are delayed until
+                                  we know the actual min/max values */
                                seq_range_array_add(&ctx->result, 0, id);
                        }
                        continue;
                }
 
-               if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
-                       modseq = mail_get_modseq(ctx->mail);
-                       if (ctx->highest_seen_modseq < modseq)
-                               ctx->highest_seen_modseq = modseq;
-               }
-
+               search_update_mail(ctx);
                if ((opts & ~(SEARCH_RETURN_EXTRAS |
                              SEARCH_RETURN_COUNT)) == 0) {
                        /* we only want to count (and get modseqs) */
@@ -258,10 +290,9 @@ static bool cmd_search_more(struct client_command_context *cmd)
        if (tryagain)
                return FALSE;
 
-       if (ctx->highest_seen_modseq == 0 &&
-           (ctx->return_options & SEARCH_RETURN_MODSEQ) != 0 &&
-           array_count(&ctx->result) > 0) {
-               /* get highest modseq only from MIN/MAX messages */
+       if (minmax && array_count(&ctx->result) > 0 &&
+           (opts & (SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE)) != 0) {
+               /* handle MIN/MAX modseq/save updates */
                if ((opts & SEARCH_RETURN_MIN) != 0) {
                        i_assert(id_min != (uint32_t)-1);
                        if (cmd->uid) {
@@ -270,7 +301,7 @@ static bool cmd_search_more(struct client_command_context *cmd)
                        } else {
                                mail_set_seq(ctx->mail, id_min);
                        }
-                       ctx->highest_seen_modseq = mail_get_modseq(ctx->mail);
+                       search_update_mail(ctx);
                }
                if ((opts & SEARCH_RETURN_MAX) != 0) {
                        i_assert(id_max != 0);
@@ -280,9 +311,7 @@ static bool cmd_search_more(struct client_command_context *cmd)
                        } else {
                                mail_set_seq(ctx->mail, id_max);
                        }
-                       modseq = mail_get_modseq(ctx->mail);
-                       if (ctx->highest_seen_modseq < modseq)
-                               ctx->highest_seen_modseq = modseq;
+                       search_update_mail(ctx);
                }
        }
 
@@ -297,7 +326,7 @@ static bool cmd_search_more(struct client_command_context *cmd)
        end_time.tv_sec -= ctx->start_time.tv_sec;
        end_time.tv_usec -= ctx->start_time.tv_usec;
        if (end_time.tv_usec < 0) {
-               end_time.tv_sec++;
+               end_time.tv_sec--;
                end_time.tv_usec += 1000000;
        }
 
@@ -327,11 +356,13 @@ static void cmd_search_more_callback(struct client_command_context *cmd)
 
 bool cmd_search(struct client_command_context *cmd)
 {
+       struct client *client = cmd->client;
        struct imap_search_context *ctx;
        struct mail_search_arg *sargs;
        const struct imap_arg *args;
-       int args_count;
-       const char *error, *charset;
+       enum search_return_options return_options;
+       int ret, args_count;
+       const char *charset;
 
        args_count = imap_parser_read_args(cmd->parser, 0, 0, &args);
        if (args_count < 1) {
@@ -342,23 +373,42 @@ bool cmd_search(struct client_command_context *cmd)
                                          "Missing SEARCH arguments.");
                return TRUE;
        }
-       cmd->client->input_lock = NULL;
+       client->input_lock = NULL;
 
        if (!client_verify_open_mailbox(cmd))
                return TRUE;
 
-       ctx = p_new(cmd->pool, struct imap_search_context, 1);
-       ctx->cmd = cmd;
-
        if (args->type == IMAP_ARG_ATOM && args[1].type == IMAP_ARG_LIST &&
            strcasecmp(IMAP_ARG_STR_NONULL(args), "RETURN") == 0) {
                args++;
-               if (!search_parse_return_options(ctx, IMAP_ARG_LIST_ARGS(args)))
+               if (!search_parse_return_options(cmd, IMAP_ARG_LIST_ARGS(args),
+                                                &return_options))
                        return TRUE;
                args++;
+
+               if ((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))
+                               return FALSE;
+               }
        } else {
-               ctx->return_options = SEARCH_RETURN_ALL;
+               return_options = SEARCH_RETURN_ALL;
+       }
+
+       if ((return_options & SEARCH_RETURN_SAVE) != 0) {
+               /* make sure the search result gets cleared if SEARCH fails */
+               if (array_is_created(&client->search_saved_uidset))
+                       array_clear(&client->search_saved_uidset);
+               else
+                       i_array_init(&client->search_saved_uidset, 128);
        }
+
+       ctx = p_new(cmd->pool, struct imap_search_context, 1);
+       ctx->cmd = cmd;
+       ctx->return_options = return_options;
+
        if (args->type == IMAP_ARG_ATOM &&
            strcasecmp(IMAP_ARG_STR_NONULL(args), "CHARSET") == 0) {
                /* CHARSET specified */
@@ -376,13 +426,9 @@ bool cmd_search(struct client_command_context *cmd)
                charset = "UTF-8";
        }
 
-       sargs = imap_search_args_build(cmd->pool, cmd->client->mailbox,
-                                      args, &error);
-       if (sargs == NULL) {
-               /* error in search arguments */
-               client_send_tagline(cmd, t_strconcat("BAD ", error, NULL));
-               return TRUE;
-       }
+       ret = imap_search_args_build(cmd, args, &sargs);
+       if (ret <= 0)
+               return ret < 0;
 
        imap_search_init(ctx, charset, sargs);
        cmd->func = cmd_search_more;
index 820af5bd40d719020f37807c25bd26f42c8d861e..13ece924ab2c1e5c78fee0e258d9b5769fea4de7 100644 (file)
@@ -89,8 +89,8 @@ bool cmd_sort(struct client_command_context *cmd)
        enum mail_sort_type sorting[MAX_SORT_PROGRAM_SIZE];
        const struct imap_arg *args;
        int args_count;
-       pool_t pool;
-       const char *error, *charset;
+       const char *charset;
+       int ret;
 
        args_count = imap_parser_read_args(cmd->parser, 0, 0, &args);
        if (args_count == -2)
@@ -125,22 +125,17 @@ bool cmd_sort(struct client_command_context *cmd)
        charset = IMAP_ARG_STR(args);
        args++;
 
-       pool = pool_alloconly_create("mail_search_args", 2048);
-
-       sargs = imap_search_args_build(pool, client->mailbox, args, &error);
-       if (sargs == NULL) {
-               /* error in search arguments */
-               client_send_tagline(cmd, t_strconcat("NO ", error, NULL));
-       } else if (imap_sort(cmd, charset, sargs, sorting) == 0) {
-               pool_unref(&pool);
-               return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
-                               (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
-                               0, "OK Sort completed.");
-       } else {
+       ret = imap_search_args_build(cmd, args, &sargs);
+       if (ret <= 0)
+               return ret < 0;
+
+       if (imap_sort(cmd, charset, sargs, sorting) < 0) {
                client_send_storage_error(cmd,
                                          mailbox_get_storage(client->mailbox));
+               return TRUE;
        }
 
-       pool_unref(&pool);
-       return TRUE;
+       return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+                       (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+                       0, "OK Sort completed.");
 }
index 8b0ee006994bd024ff54913c94a1840a39746a6a..804ebdf936a58f9157c832edb918ef5451c72a2f 100644 (file)
@@ -11,7 +11,6 @@
 
 struct imap_store_context {
        struct client_command_context *cmd;
-       const char *messageset;
        uint64_t max_modseq;
 
        enum mail_flags flags;
@@ -80,8 +79,6 @@ store_parse_args(struct imap_store_context *ctx, const struct imap_arg *args)
        const char *const *keywords_list = NULL;
 
        ctx->max_modseq = (uint64_t)-1;
-       ctx->messageset = imap_arg_string(args++);
-
        if (args->type == IMAP_ARG_LIST) {
                if (!store_parse_modifiers(ctx, IMAP_ARG_LIST_ARGS(args)))
                        return FALSE;
@@ -89,8 +86,7 @@ store_parse_args(struct imap_store_context *ctx, const struct imap_arg *args)
        }
 
        type = imap_arg_string(args++);
-       if (ctx->messageset == NULL || type == NULL ||
-           !get_modify_type(ctx, type)) {
+       if (type == NULL || !get_modify_type(ctx, type)) {
                client_send_command_error(cmd, "Invalid arguments.");
                return FALSE;
        }
@@ -128,9 +124,10 @@ bool cmd_store(struct client_command_context *cmd)
        struct imap_store_context ctx;
        ARRAY_TYPE(seq_range) modified_set = ARRAY_INIT;
        enum mailbox_transaction_flags flags = 0;
+       enum imap_sync_flags imap_sync_flags = 0;
        const char *tagged_reply;
        string_t *str;
-       bool failed;
+       int ret;
 
        if (!client_read_args(cmd, 0, 0, &args))
                return FALSE;
@@ -138,13 +135,18 @@ bool cmd_store(struct client_command_context *cmd)
        if (!client_verify_open_mailbox(cmd))
                return TRUE;
 
-       memset(&ctx, 0, sizeof(ctx));
-       ctx.cmd = cmd;
-       if (!store_parse_args(&ctx, args))
+       if (args->type != IMAP_ARG_ATOM) {
+               client_send_command_error(cmd, "Invalid arguments.");
                return TRUE;
+       }
+       ret = imap_search_get_seqset(cmd, IMAP_ARG_STR_NONULL(args),
+                                    cmd->uid, &search_arg);
+       if (ret <= 0)
+               return ret < 0;
 
-       search_arg = imap_search_get_seqset(cmd, ctx.messageset, cmd->uid);
-       if (search_arg == NULL)
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.cmd = cmd;
+       if (!store_parse_args(&ctx, ++args))
                return TRUE;
 
        if (ctx.silent)
@@ -188,32 +190,26 @@ bool cmd_store(struct client_command_context *cmd)
        if (ctx.keywords != NULL)
                mailbox_keywords_free(client->mailbox, &ctx.keywords);
 
-       if (mailbox_search_deinit(&search_ctx) < 0) {
-               failed = TRUE;
+       ret = mailbox_search_deinit(&search_ctx);
+       if (ret < 0)
                mailbox_transaction_rollback(&t);
-       } else {
-               failed = mailbox_transaction_commit(&t) < 0;
-       }
-
-       if (!failed) {
-               /* With UID STORE we have to return UID for the flags as well.
-                  Unfortunately we don't have the ability to separate those
-                  flag changes that were caused by UID STORE and those that
-                  came externally, so we'll just send the UID for all flag
-                  changes that we see. */
-               enum imap_sync_flags imap_sync_flags = 0;
-
-               if (cmd->uid &&
-                   (!ctx.silent || (client->enabled_features &
-                                    MAILBOX_FEATURE_CONDSTORE) != 0))
-                       imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;
-
-               return cmd_sync(cmd,
-                               (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
-                               imap_sync_flags, tagged_reply);
-       } else {
+        else
+               ret = mailbox_transaction_commit(&t);
+       if (ret < 0) {
                client_send_storage_error(cmd,
                        mailbox_get_storage(client->mailbox));
                return TRUE;
        }
+
+       /* With UID STORE we have to return UID for the flags as well.
+          Unfortunately we don't have the ability to separate those
+          flag changes that were caused by UID STORE and those that
+          came externally, so we'll just send the UID for all flag
+          changes that we see. */
+       if (cmd->uid && (!ctx.silent || (client->enabled_features &
+                                        MAILBOX_FEATURE_CONDSTORE) != 0))
+               imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;
+
+       return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+                       imap_sync_flags, tagged_reply);
 }
index 32395e6e9887687d1d368c709952716f8e6b2a73..009548769f133599f0f5285155eefb24e04d3bf4 100644 (file)
@@ -12,9 +12,8 @@ bool cmd_thread(struct client_command_context *cmd)
        enum mail_thread_type threading;
        struct mail_search_arg *sargs;
        const struct imap_arg *args;
-       int args_count;
-       pool_t pool;
-       const char *error, *charset, *str;
+       int ret, args_count;
+       const char *charset, *str;
 
        args_count = imap_parser_read_args(cmd->parser, 0, 0, &args);
        if (args_count == -2)
@@ -58,22 +57,17 @@ bool cmd_thread(struct client_command_context *cmd)
        charset = IMAP_ARG_STR(args);
        args++;
 
-       pool = pool_alloconly_create("mail_search_args", 2048);
+       ret = imap_search_args_build(cmd, args, &sargs);
+       if (ret <= 0)
+               return ret < 0;
 
-       sargs = imap_search_args_build(pool, client->mailbox, args, &error);
-       if (sargs == NULL) {
-               /* error in search arguments */
-               client_send_tagline(cmd, t_strconcat("NO ", error, NULL));
-       } else if (imap_thread(cmd, charset, sargs, threading) == 0) {
-               pool_unref(&pool);
-               return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
-                               (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
-                               0, "OK Thread completed.");
-       } else {
+       if (imap_thread(cmd, charset, sargs, threading) < 0) {
                client_send_storage_error(cmd,
                                          mailbox_get_storage(client->mailbox));
+               return TRUE;
        }
 
-       pool_unref(&pool);
-       return TRUE;
+       return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+                       (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+                       0, "OK Thread completed.");
 }
index 1c50f7d37bc201229ad225a16f11d78a79d03b43..b338824c3933a4451f7a18eb255f7bef53e4e917 100644 (file)
@@ -349,7 +349,8 @@ int imap_fetch_begin(struct imap_fetch_context *ctx)
                               ctx->all_headers_ctx);
 
        /* Delayed uidset -> seqset conversion. VANISHED needs the uidset. */
-       mail_search_args_init(ctx->search_args, ctx->box, TRUE);
+       mail_search_args_init(ctx->search_args, ctx->box, TRUE,
+                             &ctx->cmd->client->search_saved_uidset);
        ctx->search_ctx =
                mailbox_search_init(ctx->trans, NULL, ctx->search_args, NULL);
        return 0;
index 32c94ac36808acdc12c0c1d3c7d1d6fbaba413f3..90beee9c5dee2b397ba3ffa203f50b86b1047fc2 100644 (file)
@@ -16,18 +16,48 @@ struct search_build_data {
        const char *error;
 };
 
-struct mail_search_arg *
-imap_search_args_build(pool_t pool, struct mailbox *box,
-                      const struct imap_arg *args, const char **error_r)
+static bool search_args_have_searchres(struct mail_search_arg *sargs)
+{
+       for (; sargs != NULL; sargs = sargs->next) {
+               switch (sargs->type) {
+               case SEARCH_UIDSET:
+                       if (strcmp(sargs->value.str, "$") == 0)
+                               return TRUE;
+                       break;
+               case SEARCH_SUB:
+               case SEARCH_OR:
+                       if (search_args_have_searchres(sargs->value.subargs))
+                               return TRUE;
+                       break;
+               default:
+                       break;
+               }
+       }
+       return FALSE;
+}
+
+int imap_search_args_build(struct client_command_context *cmd,
+                          const struct imap_arg *args,
+                          struct mail_search_arg **search_args_r)
 {
        struct mail_search_arg *sargs;
+       const char *error;
 
-       sargs = mail_search_build_from_imap_args(pool, args, error_r);
-       if (sargs == NULL)
-               return NULL;
+       sargs = mail_search_build_from_imap_args(cmd->pool, args, &error);
+       if (sargs == NULL) {
+               client_send_command_error(cmd, error);
+               return -1;
+       }
 
-       mail_search_args_init(sargs, box, TRUE);
-       return sargs;
+       if (search_args_have_searchres(sargs)) {
+               if (client_handle_search_save_ambiguity(cmd))
+                       return 0;
+       }
+
+       mail_search_args_init(sargs, cmd->client->mailbox, TRUE,
+                             &cmd->client->search_saved_uidset);
+       *search_args_r = sargs;
+       return 1;
 }
 
 static bool
@@ -95,34 +125,63 @@ imap_search_get_uidset_arg(struct client_command_context *cmd,
        return 0;
 }
 
-struct mail_search_arg *
-imap_search_get_seqset(struct client_command_context *cmd,
-                      const char *set, bool uid)
+int imap_search_get_seqset(struct client_command_context *cmd,
+                          const char *set, bool uid,
+                          struct mail_search_arg **search_arg_r)
+{
+       int ret;
+
+       ret = imap_search_get_anyset(cmd, set, uid, search_arg_r);
+       if (ret > 0) {
+               mail_search_args_init(*search_arg_r,
+                                     cmd->client->mailbox, TRUE,
+                                     &cmd->client->search_saved_uidset);
+       }
+       return ret;
+}
+
+static int imap_search_get_searchres(struct client_command_context *cmd,
+                                    struct mail_search_arg **search_arg_r)
 {
        struct mail_search_arg *search_arg;
 
-       search_arg = imap_search_get_anyset(cmd, set, uid);
-       if (uid && search_arg != NULL)
-               mail_search_args_init(search_arg, cmd->client->mailbox, TRUE);
-       return search_arg;
+       if (client_handle_search_save_ambiguity(cmd))
+               return 0;
+       search_arg = p_new(cmd->pool, struct mail_search_arg, 1);
+       if (array_is_created(&cmd->client->search_saved_uidset)) {
+               search_arg->type = SEARCH_UIDSET;
+               p_array_init(&search_arg->value.seqset, cmd->pool,
+                            array_count(&cmd->client->search_saved_uidset));
+               array_append_array(&search_arg->value.seqset,
+                                  &cmd->client->search_saved_uidset);
+       } else {
+               /* $ not set yet, match nothing */
+               search_arg->type = SEARCH_ALL;
+               search_arg->not = TRUE;
+       }
+       *search_arg_r = search_arg;
+       return 1;
 }
 
-struct mail_search_arg *
-imap_search_get_anyset(struct client_command_context *cmd,
-                      const char *set, bool uid)
+int imap_search_get_anyset(struct client_command_context *cmd,
+                          const char *set, bool uid,
+                          struct mail_search_arg **search_arg_r)
 {
-       struct mail_search_arg *search_arg = NULL;
        const char *error = NULL;
        int ret;
 
+       if (strcmp(set, "$") == 0) {
+               /* SEARCHRES extension: replace $ with the last saved
+                  search result */
+               return imap_search_get_searchres(cmd, search_arg_r);
+       }
        if (!uid)
-               ret = imap_search_get_msgset_arg(cmd, set, &search_arg, &error);
+               ret = imap_search_get_msgset_arg(cmd, set, search_arg_r, &error);
        else
-               ret = imap_search_get_uidset_arg(cmd, set, &search_arg, &error);
+               ret = imap_search_get_uidset_arg(cmd, set, search_arg_r, &error);
        if (ret < 0) {
                client_send_command_error(cmd, error);
-               return NULL;
+               return -1;
        }
-
-       return search_arg;
+       return 1;
 }
index 8924598debd59d503557e88a82c0e9078570860d..029f08f7ecfe454be6568917a09ef92d2f46f887 100644 (file)
@@ -5,16 +5,20 @@ struct imap_arg;
 struct mailbox;
 struct client_command_context;
 
-/* Builds search arguments based on IMAP arguments. */
-struct mail_search_arg *
-imap_search_args_build(pool_t pool, struct mailbox *box,
-                      const struct imap_arg *args, const char **error_r);
+/* Builds search arguments based on IMAP arguments. Returns -1 if search
+   arguments are invalid, 0 if we have to wait for unambiguity,
+   1 if we can continue. */
+int imap_search_args_build(struct client_command_context *cmd,
+                          const struct imap_arg *args,
+                          struct mail_search_arg **search_args_r);
 
-struct mail_search_arg *
-imap_search_get_seqset(struct client_command_context *cmd,
-                      const char *set, bool uid);
-struct mail_search_arg *
-imap_search_get_anyset(struct client_command_context *cmd,
-                      const char *set, bool uid);
+/* Returns -1 if set is invalid, 0 if we have to wait for unambiguity,
+   1 if we can continue. */
+int imap_search_get_seqset(struct client_command_context *cmd,
+                          const char *set, bool uid,
+                          struct mail_search_arg **search_arg_r);
+int imap_search_get_anyset(struct client_command_context *cmd,
+                          const char *set, bool uid,
+                          struct mail_search_arg **search_arg_r);
 
 #endif
index 11db3150ee88e65d81e4d7d12cf26ccf15344965..72e76e3674a74ae0e6f0c6b35620d99212d3bc9a 100644 (file)
@@ -473,6 +473,10 @@ static bool search_arg_build(struct search_build_data *data,
 
                        sarg = *next_sarg;
                        p_array_init(&sarg->value.seqset, data->pool, 16);
+                       if (strcmp(sarg->value.str, "$") == 0) {
+                               /* SEARCHRES: delay initialization */
+                               return TRUE;
+                       }
                        if (imap_messageset_parse(&sarg->value.seqset,
                                                  sarg->value.str) < 0) {
                                data->error = "Invalid UID messageset";
@@ -544,6 +548,15 @@ static bool search_arg_build(struct search_build_data *data,
                                return FALSE;
                        }
                        return TRUE;
+               } else if (strcmp(str, "$") == 0) {
+                       /* SEARCHRES: delay initialization */
+                       if (!ARG_NEW_SINGLE(SEARCH_UIDSET))
+                               return FALSE;
+
+                       (*next_sarg)->value.str = p_strdup(data->pool, "$");
+                       p_array_init(&(*next_sarg)->value.seqset,
+                                    data->pool, 16);
+                       return TRUE;
                }
                break;
        }
@@ -576,16 +589,32 @@ mail_search_build_from_imap_args(pool_t pool, const struct imap_arg *args,
 }
 
 static void
-mailbox_uidseq_change(struct mail_search_arg *arg, struct mailbox *box)
+mailbox_uidset_change(struct mail_search_arg *arg, struct mailbox *box,
+                     const ARRAY_TYPE(seq_range) *search_saved_uidset)
 {
        struct seq_range *uids;
        unsigned int i, count;
        uint32_t seq1, seq2;
 
+       if (strcmp(arg->value.str, "$") == 0) {
+               /* SEARCHRES: Replace with saved uidset */
+               array_clear(&arg->value.seqset);
+               if (search_saved_uidset == NULL ||
+                   !array_is_created(search_saved_uidset))
+                       return;
+
+               array_append_array(&arg->value.seqset, search_saved_uidset);
+               return;
+       }
+
        arg->type = SEARCH_SEQSET;
 
        /* make a copy of the UIDs */
        count = array_count(&arg->value.seqset);
+       if (count == 0) {
+               /* empty set, keep it */
+               return;
+       }
        uids = t_new(struct seq_range, count);
        memcpy(uids, array_idx(&arg->value.seqset, 0), sizeof(*uids) * count);
 
@@ -606,7 +635,8 @@ mailbox_uidseq_change(struct mail_search_arg *arg, struct mailbox *box)
 }
 
 void mail_search_args_init(struct mail_search_arg *args,
-                          struct mailbox *box, bool change_uidsets)
+                          struct mailbox *box, bool change_uidsets,
+                          const ARRAY_TYPE(seq_range) *search_saved_uidset)
 {
        const char *keywords[2];
 
@@ -614,7 +644,8 @@ void mail_search_args_init(struct mail_search_arg *args,
                switch (args->type) {
                case SEARCH_UIDSET:
                        if (change_uidsets) T_BEGIN {
-                               mailbox_uidseq_change(args, box);
+                               mailbox_uidset_change(args, box,
+                                                     search_saved_uidset);
                        } T_END;
                        break;
                case SEARCH_MODSEQ:
@@ -633,7 +664,8 @@ void mail_search_args_init(struct mail_search_arg *args,
                case SEARCH_SUB:
                case SEARCH_OR:
                        mail_search_args_init(args->value.subargs, box,
-                                             change_uidsets);
+                                             change_uidsets,
+                                             search_saved_uidset);
                        break;
                default:
                        break;
index 1a3905fd47e5df9b7738ebdd4d0493d55be7cf00..1a5452bfcca86d4ca9de9d4766a1d0bad80844ae 100644 (file)
@@ -11,7 +11,8 @@ mail_search_build_from_imap_args(pool_t pool, const struct imap_arg *args,
 /* Allocate keywords for search arguments. If change_uidsets is TRUE,
    change uidsets to seqsets. */
 void mail_search_args_init(struct mail_search_arg *args,
-                          struct mailbox *box, bool change_uidsets);
+                          struct mailbox *box, bool change_uidsets,
+                          const ARRAY_TYPE(seq_range) *search_saved_uidset);
 /* Free keywords. The args can initialized afterwards again if needed. */
 void mail_search_args_deinit(struct mail_search_arg *args,
                             struct mailbox *box);