changeset 7639:03146f02403f HEAD

Implemented SEARCHRES extension.
author Timo Sirainen <tss@iki.fi>
date Sun, 16 Mar 2008 11:05:53 +0200
parents 7f57fd10c9bd
children c78e9204f3f2
files 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
diffstat 15 files changed, 335 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sun Mar 16 11:04:58 2008 +0200
+++ b/configure.in	Sun Mar 16 11:05:53 2008 +0200
@@ -2119,7 +2119,7 @@
 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"
--- a/src/imap/client.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/client.c	Sun Mar 16 11:05:53 2008 +0200
@@ -155,6 +155,8 @@
 			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 @@
 	}
 }
 
+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;
--- a/src/imap/client.h	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/client.h	Sun Mar 16 11:05:53 2008 +0200
@@ -53,6 +53,7 @@
 	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 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_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);
--- a/src/imap/cmd-copy.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-copy.c	Sun Mar 16 11:05:53 2008 +0200
@@ -109,9 +109,9 @@
 	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)
--- a/src/imap/cmd-expunge.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-expunge.c	Sun Mar 16 11:05:53 2008 +0200
@@ -62,6 +62,7 @@
 	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 @@
 		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)
--- a/src/imap/cmd-fetch.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-fetch.c	Sun Mar 16 11:05:53 2008 +0200
@@ -172,10 +172,12 @@
 
 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 @@
 	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 @@
 		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;
--- a/src/imap/cmd-search.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-search.c	Sun Mar 16 11:05:53 2008 +0200
@@ -17,8 +17,10 @@
 	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 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(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 @@
 		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 @@
 			    (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 @@
 	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 @@
 	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 @@
 	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 @@
 		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 @@
 	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 @@
 			} 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 @@
 			} 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 @@
 	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 @@
 
 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 @@
 					  "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 @@
 		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;
--- a/src/imap/cmd-sort.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-sort.c	Sun Mar 16 11:05:53 2008 +0200
@@ -89,8 +89,8 @@
 	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 @@
 	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_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 {
+	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.");
 }
--- a/src/imap/cmd-store.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-store.c	Sun Mar 16 11:05:53 2008 +0200
@@ -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 @@
 	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 @@
 	}
 
 	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 @@
 	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 @@
 	if (!client_verify_open_mailbox(cmd))
 		return TRUE;
 
+	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;
+
 	memset(&ctx, 0, sizeof(ctx));
 	ctx.cmd = cmd;
-	if (!store_parse_args(&ctx, args))
-		return TRUE;
-
-	search_arg = imap_search_get_seqset(cmd, ctx.messageset, cmd->uid);
-	if (search_arg == NULL)
+	if (!store_parse_args(&ctx, ++args))
 		return TRUE;
 
 	if (ctx.silent)
@@ -188,32 +190,26 @@
 	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);
 }
--- a/src/imap/cmd-thread.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/cmd-thread.c	Sun Mar 16 11:05:53 2008 +0200
@@ -12,9 +12,8 @@
 	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 @@
 	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.");
 }
--- a/src/imap/imap-fetch.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/imap-fetch.c	Sun Mar 16 11:05:53 2008 +0200
@@ -349,7 +349,8 @@
 			       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;
--- a/src/imap/imap-search.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/imap-search.c	Sun Mar 16 11:05:53 2008 +0200
@@ -16,18 +16,48 @@
 	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 @@
 	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;
 }
--- a/src/imap/imap-search.h	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/imap/imap-search.h	Sun Mar 16 11:05:53 2008 +0200
@@ -5,16 +5,20 @@
 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
--- a/src/lib-storage/mail-search-build.c	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/lib-storage/mail-search-build.c	Sun Mar 16 11:05:53 2008 +0200
@@ -473,6 +473,10 @@
 
 			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 @@
 				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 @@
 }
 
 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 @@
 }
 
 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 @@
 		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 @@
 		case SEARCH_SUB:
 		case SEARCH_OR:
 			mail_search_args_init(args->value.subargs, box,
-					      change_uidsets);
+					      change_uidsets,
+					      search_saved_uidset);
 			break;
 		default:
 			break;
--- a/src/lib-storage/mail-search-build.h	Sun Mar 16 11:04:58 2008 +0200
+++ b/src/lib-storage/mail-search-build.h	Sun Mar 16 11:05:53 2008 +0200
@@ -11,7 +11,8 @@
 /* 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);