changeset 7637:cf0cce4b0e4e HEAD

Implemented ESEARCH extension
author Timo Sirainen <tss@iki.fi>
date Sun, 16 Mar 2008 09:07:53 +0200
parents 8ca66e0ac4ed
children 7f57fd10c9bd
files configure.in src/imap/cmd-search.c
diffstat 2 files changed, 219 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sun Mar 16 09:07:25 2008 +0200
+++ b/configure.in	Sun Mar 16 09:07: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"
+capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH"
 AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities)
 
 CFLAGS="$CFLAGS $EXTRA_CFLAGS"
--- a/src/imap/cmd-search.c	Sun Mar 16 09:07:25 2008 +0200
+++ b/src/imap/cmd-search.c	Sun Mar 16 09:07:53 2008 +0200
@@ -3,29 +3,73 @@
 #include "common.h"
 #include "ostream.h"
 #include "str.h"
+#include "seq-range-array.h"
 #include "commands.h"
 #include "mail-search.h"
 #include "mail-search-build.h"
+#include "imap-quote.h"
+#include "imap-util.h"
 #include "imap-search.h"
 
-#define OUTBUF_SIZE 65536
+enum search_return_options {
+	SEARCH_RETURN_ESEARCH		= 0x01,
+	SEARCH_RETURN_MIN		= 0x02,
+	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)
+};
 
 struct imap_search_context {
+	struct client_command_context *cmd;
 	struct mailbox *box;
 	struct mailbox_transaction_context *trans;
         struct mail_search_context *search_ctx;
 	struct mail *mail;
 	struct mail_search_arg *sargs;
+	enum search_return_options return_options;
 
 	struct timeout *to;
-	string_t *output_buf;
+	ARRAY_TYPE(seq_range) result;
+	unsigned int result_count;
 
 	uint64_t highest_seen_modseq;
 	struct timeval start_time;
+};
 
-	unsigned int output_sent:1;
-	unsigned int show_highest_modseq:1;
-};
+static bool search_parse_return_options(struct imap_search_context *ctx,
+					const struct imap_arg *args)
+{
+	const char *name;
+
+	while (args->type != IMAP_ARG_EOL) {
+		if (args->type != IMAP_ARG_ATOM) {
+			client_send_command_error(ctx->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;
+		else if (strcmp(name, "MAX") == 0)
+			ctx->return_options |= SEARCH_RETURN_MAX;
+		else if (strcmp(name, "ALL") == 0)
+			ctx->return_options |= SEARCH_RETURN_ALL;
+		else if (strcmp(name, "COUNT") == 0)
+			ctx->return_options |= SEARCH_RETURN_COUNT;
+		else {
+			client_send_command_error(ctx->cmd,
+				"Unknown SEARCH return option");
+			return FALSE;
+		}
+	}
+	if (ctx->return_options == 0)
+		ctx->return_options = SEARCH_RETURN_ALL;
+	ctx->return_options |= SEARCH_RETURN_ESEARCH;
+	return TRUE;
+}
 
 static bool imap_search_args_have_modseq(const struct mail_search_arg *sargs)
 {
@@ -46,93 +90,206 @@
 }
 
 static struct imap_search_context *
-imap_search_init(struct client_command_context *cmd, struct mailbox *box,
-		 const char *charset, struct mail_search_arg *sargs)
+imap_search_init(struct imap_search_context *ctx, const char *charset,
+		 struct mail_search_arg *sargs)
 {
-	struct imap_search_context *ctx;
-
-	ctx = p_new(cmd->pool, struct imap_search_context, 1);
 	if (imap_search_args_have_modseq(sargs)) {
-		ctx->show_highest_modseq = TRUE;
-		client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE);
+		ctx->return_options |= SEARCH_RETURN_MODSEQ;
+		client_enable(ctx->cmd->client, MAILBOX_FEATURE_CONDSTORE);
 	}
 
-	ctx->box = box;
-	ctx->trans = mailbox_transaction_begin(cmd->client->mailbox, 0);
+	ctx->box = ctx->cmd->client->mailbox;
+	ctx->trans = mailbox_transaction_begin(ctx->box, 0);
 	ctx->sargs = sargs;
 	ctx->search_ctx = mailbox_search_init(ctx->trans, charset, sargs, NULL);
 	ctx->mail = mail_alloc(ctx->trans, 0, NULL);
 	(void)gettimeofday(&ctx->start_time, NULL);
-	ctx->output_buf = str_new(default_pool, OUTBUF_SIZE);
-	str_append(ctx->output_buf, "* SEARCH");
+	i_array_init(&ctx->result, 128);
 	return ctx;
 }
 
-static int imap_search_deinit(struct client_command_context *cmd,
-			      struct imap_search_context *ctx)
+static void imap_search_send_result_standard(struct imap_search_context *ctx)
+{
+	const struct seq_range *range;
+	unsigned int i, count;
+	string_t *str;
+	uint32_t seq;
+
+	str = t_str_new(1024);
+	range = array_get(&ctx->result, &count);
+	str_append(str, "* SEARCH");
+	for (i = 0; i < count; i++) {
+		for (seq = range[i].seq1; seq <= range[i].seq2; seq++)
+			str_printfa(str, " %u", seq);
+		if (str_len(str) >= 1024-32) {
+			o_stream_send(ctx->cmd->client->output,
+				      str_data(str), str_len(str));
+			str_truncate(str, 0);
+		}
+	}
+
+	if (ctx->highest_seen_modseq != 0) {
+		str_printfa(str, " (MODSEQ %llu)",
+			    (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));
+}
+
+static void imap_search_send_result(struct imap_search_context *ctx)
 {
-	int ret;
+	const struct seq_range *range;
+	unsigned int count;
+	string_t *str;
+
+	if ((ctx->return_options & SEARCH_RETURN_ESEARCH) == 0) {
+		imap_search_send_result_standard(ctx);
+		return;
+	}
+
+	str = str_new(default_pool, 1024);
+	str_append(str, "* ESEARCH (TAG ");
+	imap_quote_append_string(str, ctx->cmd->tag, FALSE);
+	str_append_c(str, ')');
+
+	range = array_get(&ctx->result, &count);
+	if (count > 0) {
+		if ((ctx->return_options & SEARCH_RETURN_MIN) != 0)
+			str_printfa(str, " MIN %u", range[0].seq1);
+		if ((ctx->return_options & SEARCH_RETURN_MAX) != 0)
+			str_printfa(str, " MAX %u", range[count-1].seq2);
+		if ((ctx->return_options & SEARCH_RETURN_ALL) != 0) {
+			str_append(str, " ALL ");
+			imap_write_seq_range(str, &ctx->result);
+		}
+	}
+
+	if ((ctx->return_options & SEARCH_RETURN_COUNT) != 0)
+		str_printfa(str, " COUNT %u", ctx->result_count);
+	if (ctx->highest_seen_modseq != 0) {
+		str_printfa(str, " MODSEQ %llu",
+			    (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));
+}
+
+static int imap_search_deinit(struct imap_search_context *ctx)
+{
+	int ret = 0;
 
 	mail_free(&ctx->mail);
-	ret = mailbox_search_deinit(&ctx->search_ctx);
-
-	if (mailbox_transaction_commit(&ctx->trans) < 0)
+	if (mailbox_search_deinit(&ctx->search_ctx) < 0)
 		ret = -1;
+	else if (!ctx->cmd->cancel)
+		imap_search_send_result(ctx);
 
-	if (ctx->output_sent || (ret == 0 && !cmd->cancel)) {
-		str_append(ctx->output_buf, "\r\n");
-		o_stream_send(cmd->client->output,
-			      str_data(ctx->output_buf),
-			      str_len(ctx->output_buf));
-	}
+	(void)mailbox_transaction_commit(&ctx->trans);
+
 	if (ctx->to != NULL)
 		timeout_remove(&ctx->to);
-	str_free(&ctx->output_buf);
+	array_free(&ctx->result);
 	mail_search_args_deinit(ctx->sargs, ctx->box);
 
-	cmd->context = NULL;
+	ctx->cmd->context = NULL;
 	return ret;
 }
 
 static bool cmd_search_more(struct client_command_context *cmd)
 {
 	struct imap_search_context *ctx = cmd->context;
+	enum search_return_options opts = ctx->return_options;
 	struct timeval end_time;
+	const struct seq_range *range;
+	unsigned int count;
 	uint64_t modseq;
+	uint32_t id, id_min, id_max;
 	bool tryagain;
-	int ret;
 
 	if (cmd->cancel) {
-		(void)imap_search_deinit(cmd, ctx);
+		(void)imap_search_deinit(ctx);
 		return TRUE;
 	}
 
-	while ((ret = mailbox_search_next_nonblock(ctx->search_ctx, ctx->mail,
-						   &tryagain)) > 0) {
-		if (str_len(ctx->output_buf) >= OUTBUF_SIZE - MAX_INT_STRLEN) {
-			/* flush. this also causes us to lock the output. */
-			cmd->client->output_lock = cmd;
-			o_stream_send(cmd->client->output,
-				      str_data(ctx->output_buf),
-				      str_len(ctx->output_buf));
-			str_truncate(ctx->output_buf, 0);
-			ctx->output_sent = TRUE;
+	range = array_get(&ctx->result, &count);
+	if (count == 0) {
+		id_min = (uint32_t)-1;
+		id_max = 0;
+	} else {
+		id_min = range[0].seq1;
+		id_max = range[count-1].seq2;
+	}
+
+	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) {
+			/* 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 */
+				seq_range_array_add(&ctx->result, 0, id);
+			}
+			continue;
 		}
-		if (ctx->show_highest_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;
 		}
 
-		str_printfa(ctx->output_buf, " %u",
-			    cmd->uid ? ctx->mail->uid : ctx->mail->seq);
+		if ((opts & ~(SEARCH_RETURN_EXTRAS |
+			      SEARCH_RETURN_COUNT)) == 0) {
+			/* we only want to count (and get modseqs) */
+			continue;
+		}
+		seq_range_array_add(&ctx->result, 0, id);
 	}
 	if (tryagain)
 		return FALSE;
 
-	if (ctx->highest_seen_modseq != 0) {
-		str_printfa(ctx->output_buf, " (MODSEQ %llu)",
-			    (unsigned long long)ctx->highest_seen_modseq);
+	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 ((opts & SEARCH_RETURN_MIN) != 0) {
+			i_assert(id_min != (uint32_t)-1);
+			if (cmd->uid) {
+				if (!mail_set_uid(ctx->mail, id_min))
+					i_unreached();
+			} else {
+				mail_set_seq(ctx->mail, id_min);
+			}
+			ctx->highest_seen_modseq = mail_get_modseq(ctx->mail);
+		}
+		if ((opts & SEARCH_RETURN_MAX) != 0) {
+			i_assert(id_max != 0);
+			if (cmd->uid) {
+				if (!mail_set_uid(ctx->mail, id_max))
+					i_unreached();
+			} 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;
+		}
+	}
+
+	if (imap_search_deinit(ctx) < 0) {
+		client_send_storage_error(cmd,
+			mailbox_get_storage(cmd->client->mailbox));
+		return TRUE;
 	}
 
 	if (gettimeofday(&end_time, NULL) < 0)
@@ -144,15 +301,6 @@
 		end_time.tv_usec += 1000000;
 	}
 
-	if (imap_search_deinit(cmd, ctx) < 0)
-		ret = -1;
-
-	if (ret < 0) {
-		client_send_storage_error(cmd,
-			mailbox_get_storage(cmd->client->mailbox));
-		return TRUE;
-	}
-
 	return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
 			(cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES), 0,
 			t_strdup_printf("OK Search completed (%d.%03d secs).",
@@ -199,6 +347,18 @@
 	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)))
+			return TRUE;
+		args++;
+	} else {
+		ctx->return_options = SEARCH_RETURN_ALL;
+	}
 	if (args->type == IMAP_ARG_ATOM &&
 	    strcasecmp(IMAP_ARG_STR_NONULL(args), "CHARSET") == 0) {
 		/* CHARSET specified */
@@ -224,7 +384,7 @@
 		return TRUE;
 	}
 
-	ctx = imap_search_init(cmd, cmd->client->mailbox, charset, sargs);
+	imap_search_init(ctx, charset, sargs);
 	cmd->func = cmd_search_more;
 	cmd->context = ctx;