changeset 7619:56f55bd35aa5 HEAD

Moved IMAP messageset handling to lib-imap/ and searching to lib-storage/. Rewrote messageset handling to use struct seq_range instead.
author Timo Sirainen <tss@iki.fi>
date Fri, 14 Mar 2008 11:59:36 +0200
parents 6dbd70663adf
children 4b8c1c164d8f
files src/imap/Makefile.am src/imap/cmd-search.c src/imap/imap-messageset.c src/imap/imap-messageset.h src/imap/imap-search.c src/imap/imap-search.h src/lib-imap/Makefile.am src/lib-imap/imap-messageset.c src/lib-imap/imap-messageset.h src/lib-storage/Makefile.am src/lib-storage/index/index-search.c src/lib-storage/mail-search-build.c src/lib-storage/mail-search-build.h src/lib-storage/mail-search.h src/plugins/fts/fts-storage.c src/pop3/commands.c
diffstat 16 files changed, 771 insertions(+), 781 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/Makefile.am	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/imap/Makefile.am	Fri Mar 14 11:59:36 2008 +0200
@@ -75,7 +75,6 @@
 	imap-expunge.c \
 	imap-fetch.c \
 	imap-fetch-body.c \
-	imap-messageset.c \
 	imap-search.c \
 	imap-sort.c \
 	imap-status.c \
@@ -92,7 +91,6 @@
 	common.h \
 	imap-expunge.h \
 	imap-fetch.h \
-	imap-messageset.h \
 	imap-search.h \
 	imap-sort.h \
 	imap-status.h \
--- a/src/imap/cmd-search.c	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/imap/cmd-search.c	Fri Mar 14 11:59:36 2008 +0200
@@ -4,6 +4,7 @@
 #include "ostream.h"
 #include "str.h"
 #include "commands.h"
+#include "mail-search-build.h"
 #include "imap-search.h"
 
 #define OUTBUF_SIZE 65536
@@ -62,7 +63,7 @@
 	if (ctx->to != NULL)
 		timeout_remove(&ctx->to);
 	str_free(&ctx->output_buf);
-	imap_search_args_free(ctx->box, ctx->sargs);
+	mail_search_args_deinit(ctx->sargs, ctx->box);
 
 	cmd->context = NULL;
 	return ret;
--- a/src/imap/imap-messageset.c	Fri Mar 14 09:44:34 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "mail-search.h"
-#include "imap-search.h"
-#include "imap-messageset.h"
-
-static uint32_t get_next_number(const char **str)
-{
-	uint32_t num;
-
-	num = 0;
-	while (**str != '\0') {
-		if (**str < '0' || **str > '9')
-			break;
-
-		num = num*10 + (**str - '0');
-		(*str)++;
-	}
-
-	if (num == (uint32_t)-1) {
-		/* FIXME: ugly hack, we're using this number to mean the
-		   last existing message. In reality UIDs should never get
-		   this high, so we can quite safely just drop this one down. */
-		num--;
-	}
-
-	return num;
-}
-
-struct mail_search_seqset *
-imap_messageset_parse(pool_t pool, const char *messageset)
-{
-        struct mail_search_seqset *ret, **next;
-	uint32_t seq1, seq2;
-
-	ret = NULL;
-	next = &ret;
-
-	while (*messageset != '\0') {
-		if (*messageset == '*') {
-			/* last message */
-			seq1 = (uint32_t)-1;
-			messageset++;
-		} else {
-			seq1 = get_next_number(&messageset);
-			if (seq1 == 0)
-				return NULL;
-		}
-
-		if (*messageset != ':')
-			seq2 = seq1;
-		else {
-			/* first:last range */
-			messageset++;
-
-			if (*messageset == '*') {
-				seq2 = (uint32_t)-1;
-				messageset++;
-			} else {
-				seq2 = get_next_number(&messageset);
-				if (seq2 == 0)
-					return NULL;
-			}
-		}
-
-		if (*messageset == ',')
-			messageset++;
-		else if (*messageset != '\0')
-			return NULL;
-
-		if (seq1 > seq2) {
-			/* swap, as specified by RFC-3501 */
-			uint32_t temp = seq1;
-			seq1 = seq2;
-			seq2 = temp;
-		}
-
-		*next = p_new(pool, struct mail_search_seqset, 1);
-		(*next)->seq1 = seq1;
-		(*next)->seq2 = seq2;
-		next = &(*next)->next;
-	}
-
-	return ret;
-}
--- a/src/imap/imap-messageset.h	Fri Mar 14 09:44:34 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-#ifndef IMAP_MESSAGESET_H
-#define IMAP_MESSAGESET_H
-
-struct mail_search_seqset *
-imap_messageset_parse(pool_t pool, const char *messageset);
-
-#endif
--- a/src/imap/imap-search.c	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/imap/imap-search.c	Fri Mar 14 11:59:36 2008 +0200
@@ -3,7 +3,7 @@
 #include "common.h"
 #include "mail-storage.h"
 #include "mail-search.h"
-#include "imap-date.h"
+#include "mail-search-build.h"
 #include "imap-search.h"
 #include "imap-parser.h"
 #include "imap-messageset.h"
@@ -16,556 +16,42 @@
 	const char *error;
 };
 
-static int
-imap_uidset_parse(pool_t pool, struct mailbox *box, const char *uidset,
-		  struct mail_search_seqset **seqset_r, const char **error_r)
-{
-	struct mail_search_seqset *seqset, **p;
-	bool last;
-
-	*seqset_r = imap_messageset_parse(pool, uidset);
-	if (*seqset_r == NULL) {
-		*error_r = "Invalid UID messageset";
-		return -1;
-	}
-
-	p = seqset_r;
-	for (seqset = *seqset_r; seqset != NULL; seqset = seqset->next) {
-		if (seqset->seq1 == (uint32_t)-1) {
-			/* last message, stays same */
-			continue;
-		}
-
-		last = seqset->seq2 == (uint32_t)-1;
-		mailbox_get_uids(box, seqset->seq1, seqset->seq2,
-				 &seqset->seq1, &seqset->seq2);
-		if (seqset->seq1 == 0 && last) {
-			/* we need special case for too_high_uid:* case */
-			seqset->seq1 = seqset->seq2 = (uint32_t)-1;
-		}
-
-		if (seqset->seq1 != 0)
-			p = &seqset->next;
-		else
-			*p = seqset->next;
-	}
-
-	*error_r = NULL;
-	return 0;
-}
-
-static struct mail_search_arg *
-search_arg_new(pool_t pool, enum mail_search_arg_type type)
-{
-	struct mail_search_arg *arg;
-
-	arg = p_new(pool, struct mail_search_arg, 1);
-	arg->type = type;
-
-	return arg;
-}
-
-static bool
-arg_get_next(struct search_build_data *data, const struct imap_arg **args,
-	     const char **value_r)
-{
-	if ((*args)->type == IMAP_ARG_EOL) {
-		data->error = "Missing parameter for argument";
-		return FALSE;
-	}
-	if ((*args)->type != IMAP_ARG_ATOM &&
-	    (*args)->type != IMAP_ARG_STRING) {
-		data->error = "Invalid parameter for argument";
-		return FALSE;
-	}
-
-	*value_r = IMAP_ARG_STR(*args);
-	*args += 1;
-	return TRUE;
-}
-
-#define ARG_NEW_SINGLE(type) \
-	arg_new_single(data, next_sarg, type)
-static bool
-arg_new_single(struct search_build_data *data,
-	       struct mail_search_arg **next_sarg,
-	       enum mail_search_arg_type type)
-{
-	*next_sarg = search_arg_new(data->pool, type);
-	return TRUE;
-}
-
-#define ARG_NEW_STR(type) \
-	arg_new_str(data, args, next_sarg, type)
-static bool
-arg_new_str(struct search_build_data *data,
-	    const struct imap_arg **args, struct mail_search_arg **next_sarg,
-	    enum mail_search_arg_type type)
-{
-	struct mail_search_arg *sarg;
-	const char *value;
-
-	*next_sarg = sarg = search_arg_new(data->pool, type);
-	if (!arg_get_next(data, args, &value))
-		return FALSE;
-	sarg->value.str = p_strdup(data->pool, value);
-	return TRUE;
-}
-
-#define ARG_NEW_FLAGS(flags) \
-	arg_new_flags(data, next_sarg, flags)
-static bool
-arg_new_flags(struct search_build_data *data,
-	      struct mail_search_arg **next_sarg, enum mail_flags flags)
-{
-	struct mail_search_arg *sarg;
-
-	*next_sarg = sarg = search_arg_new(data->pool, SEARCH_FLAGS);
-	sarg->value.flags = flags;
-	return TRUE;
-}
-
-static bool
-arg_new_keyword(struct search_build_data *data,
-		const struct imap_arg **args,
-		struct mail_search_arg **next_sarg)
-{
-	struct mail_search_arg *sarg;
-	const char *value, *keywords[2];
-	struct mail_storage *storage;
-	enum mail_error error;
-
-	*next_sarg = sarg = search_arg_new(data->pool, SEARCH_KEYWORDS);
-	if (!arg_get_next(data, args, &value))
-		return FALSE;
-
-	keywords[0] = value;
-	keywords[1] = NULL;
-
-	if (mailbox_keywords_create(data->box, keywords,
-				    &sarg->value.keywords) < 0) {
-		storage = mailbox_get_storage(data->box);
-		data->error = mail_storage_get_last_error(storage, &error);
-		return FALSE;
-	}
-	return TRUE;
-}
-
-#define ARG_NEW_SIZE(type) \
-	arg_new_size(data, args, next_sarg, type)
-static bool
-arg_new_size(struct search_build_data *data,
-	     const struct imap_arg **args, struct mail_search_arg **next_sarg,
-	     enum mail_search_arg_type type)
-{
-	struct mail_search_arg *sarg;
-	const char *value;
-	char *p;
-
-	*next_sarg = sarg = search_arg_new(data->pool, type);
-	if (!arg_get_next(data, args, &value))
-		return FALSE;
-
-	sarg->value.size = strtoull(value, &p, 10);
-	if (*p != '\0') {
-		data->error = "Invalid search size parameter";
-		return FALSE;
-	}
-	return TRUE;
-}
-
-#define ARG_NEW_DATE(type) \
-	arg_new_date(data, args, next_sarg, type)
-static bool
-arg_new_date(struct search_build_data *data,
-	     const struct imap_arg **args, struct mail_search_arg **next_sarg,
-	     enum mail_search_arg_type type)
-{
-	struct mail_search_arg *sarg;
-	const char *value;
-
-	*next_sarg = sarg = search_arg_new(data->pool, type);
-	if (!arg_get_next(data, args, &value))
-		return FALSE;
-	if (!imap_parse_date(value, &sarg->value.time)) {
-		data->error = "Invalid search date parameter";
-		return FALSE;
-	}
-	return TRUE;
-}
-
-#define ARG_NEW_HEADER(type, hdr_name) \
-	arg_new_header(data, args, next_sarg, type, hdr_name)
-static bool
-arg_new_header(struct search_build_data *data,
-	       const struct imap_arg **args, struct mail_search_arg **next_sarg,
-	       enum mail_search_arg_type type, const char *hdr_name)
-{
-	struct mail_search_arg *sarg;
-	const char *value;
-
-	*next_sarg = sarg = search_arg_new(data->pool, type);
-	if (!arg_get_next(data, args, &value))
-		return FALSE;
-
-	sarg->hdr_field_name = p_strdup(data->pool, hdr_name);
-	sarg->value.str = p_strdup(data->pool, value);
-	return TRUE;
-}
-
-static bool search_arg_build(struct search_build_data *data,
-			     const struct imap_arg **args,
-			     struct mail_search_arg **next_sarg)
-{
-        struct mail_search_seqset *seqset;
-	struct mail_search_arg **subargs;
-	const struct imap_arg *arg;
-	const char *str;
-
-	if ((*args)->type == IMAP_ARG_EOL) {
-		data->error = "Missing argument";
-		return FALSE;
-	}
-
-	arg = *args;
-
-	if (arg->type == IMAP_ARG_NIL) {
-		/* NIL not allowed */
-		data->error = "NIL not allowed";
-		return FALSE;
-	}
-
-	if (arg->type == IMAP_ARG_LIST) {
-		const struct imap_arg *listargs = IMAP_ARG_LIST_ARGS(arg);
-
-		if (listargs->type == IMAP_ARG_EOL) {
-			data->error = "Empty list not allowed";
-			return FALSE;
-		}
-
-		*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
-		subargs = &(*next_sarg)->value.subargs;
-		while (listargs->type != IMAP_ARG_EOL) {
-			if (!search_arg_build(data, &listargs, subargs))
-				return FALSE;
-			subargs = &(*subargs)->next;
-		}
-
-		*args += 1;
-		return TRUE;
-	}
-
-	i_assert(arg->type == IMAP_ARG_ATOM ||
-		 arg->type == IMAP_ARG_STRING);
-
-	/* string argument - get the name and jump to next */
-	str = IMAP_ARG_STR(arg);
-	*args += 1;
-	str = t_str_ucase(str);
-
-	switch (*str) {
-	case 'A':
-		if (strcmp(str, "ANSWERED") == 0)
-			return ARG_NEW_FLAGS(MAIL_ANSWERED);
-		else if (strcmp(str, "ALL") == 0)
-			return ARG_NEW_SINGLE(SEARCH_ALL);
-		break;
-	case 'B':
-		if (strcmp(str, "BODY") == 0) {
-			/* <string> */
-			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
-			    *IMAP_ARG_STR(*args) == '\0') {
-				*args += 1;
-				return ARG_NEW_SINGLE(SEARCH_ALL);
-			}
-			return ARG_NEW_STR(SEARCH_BODY);
-		} else if (strcmp(str, "BEFORE") == 0) {
-			/* <date> */
-			return ARG_NEW_DATE(SEARCH_BEFORE);
-		} else if (strcmp(str, "BCC") == 0) {
-			/* <string> */
-			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
-		}
-		break;
-	case 'C':
-		if (strcmp(str, "CC") == 0) {
-			/* <string> */
-			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
-		}
-		break;
-	case 'D':
-		if (strcmp(str, "DELETED") == 0)
-			return ARG_NEW_FLAGS(MAIL_DELETED);
-		else if (strcmp(str, "DRAFT") == 0)
-			return ARG_NEW_FLAGS(MAIL_DRAFT);
-		break;
-	case 'F':
-		if (strcmp(str, "FLAGGED") == 0)
-			return ARG_NEW_FLAGS(MAIL_FLAGGED);
-		else if (strcmp(str, "FROM") == 0) {
-			/* <string> */
-			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
-		}
-		break;
-	case 'H':
-		if (strcmp(str, "HEADER") == 0) {
-			/* <field-name> <string> */
-			const char *key;
-
-			if ((*args)->type == IMAP_ARG_EOL) {
-				data->error = "Missing parameter for HEADER";
-				return FALSE;
-			}
-			if ((*args)->type != IMAP_ARG_ATOM &&
-			    (*args)->type != IMAP_ARG_STRING) {
-				data->error = "Invalid parameter for HEADER";
-				return FALSE;
-			}
-
-			key = t_str_ucase(IMAP_ARG_STR(*args));
-			*args += 1;
-			return ARG_NEW_HEADER(SEARCH_HEADER, key);
-		}
-		break;
-	case 'K':
-		if (strcmp(str, "KEYWORD") == 0) {
-			/* <flag> */
-			return arg_new_keyword(data, args, next_sarg);
-		}
-		break;
-	case 'L':
-		if (strcmp(str, "LARGER") == 0) {
-			/* <n> */
-			return ARG_NEW_SIZE(SEARCH_LARGER);
-		}
-		break;
-	case 'N':
-		if (strcmp(str, "NOT") == 0) {
-			if (!search_arg_build(data, args, next_sarg))
-				return FALSE;
-			(*next_sarg)->not = !(*next_sarg)->not;
-			return TRUE;
-		} else if (strcmp(str, "NEW") == 0) {
-			/* NEW == (RECENT UNSEEN) */
-			*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
-
-			subargs = &(*next_sarg)->value.subargs;
-			*subargs = search_arg_new(data->pool, SEARCH_FLAGS);
-			(*subargs)->value.flags = MAIL_RECENT;
-			(*subargs)->next = search_arg_new(data->pool,
-							  SEARCH_FLAGS);
-			(*subargs)->next->value.flags = MAIL_SEEN;
-			(*subargs)->next->not = TRUE;
-			return TRUE;
-		}
-		break;
-	case 'O':
-		if (strcmp(str, "OR") == 0) {
-			/* <search-key1> <search-key2> */
-			*next_sarg = search_arg_new(data->pool, SEARCH_OR);
-
-			subargs = &(*next_sarg)->value.subargs;
-			for (;;) {
-				if (!search_arg_build(data, args, subargs))
-					return FALSE;
-
-				subargs = &(*subargs)->next;
-
-				/* <key> OR <key> OR ... <key> - put them all
-				   under one SEARCH_OR list. */
-				if ((*args)->type == IMAP_ARG_EOL)
-					break;
-
-				if ((*args)->type != IMAP_ARG_ATOM ||
-				    strcasecmp(IMAP_ARG_STR_NONULL(*args),
-					       "OR") != 0)
-					break;
-
-				*args += 1;
-			}
-
-			if (!search_arg_build(data, args, subargs))
-				return FALSE;
-			return TRUE;
-		} if (strcmp(str, "ON") == 0) {
-			/* <date> */
-			return ARG_NEW_DATE(SEARCH_ON);
-		} if (strcmp(str, "OLD") == 0) {
-			/* OLD == NOT RECENT */
-			if (!ARG_NEW_FLAGS(MAIL_RECENT))
-				return FALSE;
-
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		}
-		break;
-	case 'R':
-		if (strcmp(str, "RECENT") == 0)
-			return ARG_NEW_FLAGS(MAIL_RECENT);
-		break;
-	case 'S':
-		if (strcmp(str, "SEEN") == 0)
-			return ARG_NEW_FLAGS(MAIL_SEEN);
-		else if (strcmp(str, "SUBJECT") == 0) {
-			/* <string> */
-			return ARG_NEW_HEADER(SEARCH_HEADER_COMPRESS_LWSP, str);
-		} else if (strcmp(str, "SENTBEFORE") == 0) {
-			/* <date> */
-			return ARG_NEW_DATE(SEARCH_SENTBEFORE);
-		} else if (strcmp(str, "SENTON") == 0) {
-			/* <date> */
-			return ARG_NEW_DATE(SEARCH_SENTON);
-		} else if (strcmp(str, "SENTSINCE") == 0) {
-			/* <date> */
-			return ARG_NEW_DATE(SEARCH_SENTSINCE);
-		} else if (strcmp(str, "SINCE") == 0) {
-			/* <date> */
-			return ARG_NEW_DATE(SEARCH_SINCE);
-		} else if (strcmp(str, "SMALLER") == 0) {
-			/* <n> */
-			return ARG_NEW_SIZE(SEARCH_SMALLER);
-		}
-		break;
-	case 'T':
-		if (strcmp(str, "TEXT") == 0) {
-			/* <string> */
-			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
-			    *IMAP_ARG_STR(*args) == '\0') {
-				*args += 1;
-				return ARG_NEW_SINGLE(SEARCH_ALL);
-			}
-			return ARG_NEW_STR(SEARCH_TEXT);
-		} else if (strcmp(str, "TO") == 0) {
-			/* <string> */
-			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
-		}
-		break;
-	case 'U':
-		if (strcmp(str, "UID") == 0) {
-			/* <message set> */
-			if (!ARG_NEW_STR(SEARCH_SEQSET))
-				return FALSE;
-
-			return imap_uidset_parse(data->pool, data->box,
-						 (*next_sarg)->value.str,
-						 &(*next_sarg)->value.seqset,
-						 &data->error) == 0;
-		} else if (strcmp(str, "UNANSWERED") == 0) {
-			if (!ARG_NEW_FLAGS(MAIL_ANSWERED))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNDELETED") == 0) {
-			if (!ARG_NEW_FLAGS(MAIL_DELETED))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNDRAFT") == 0) {
-			if (!ARG_NEW_FLAGS(MAIL_DRAFT))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNFLAGGED") == 0) {
-			if (!ARG_NEW_FLAGS(MAIL_FLAGGED))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNKEYWORD") == 0) {
-			/* <flag> */
-			if (!arg_new_keyword(data, args, next_sarg))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNSEEN") == 0) {
-			if (!ARG_NEW_FLAGS(MAIL_SEEN))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		}
-		break;
-	case 'X':
-		if (strcmp(str, "X-BODY-FAST") == 0) {
-			/* <string> */
-			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
-			    *IMAP_ARG_STR(*args) == '\0') {
-				*args += 1;
-				return ARG_NEW_SINGLE(SEARCH_ALL);
-			}
-			return ARG_NEW_STR(SEARCH_BODY_FAST);
-		} else if (strcmp(str, "X-TEXT-FAST") == 0) {
-			/* <string> */
-			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
-			    *IMAP_ARG_STR(*args) == '\0') {
-				*args += 1;
-				return ARG_NEW_SINGLE(SEARCH_ALL);
-			}
-			return ARG_NEW_STR(SEARCH_TEXT_FAST);
-		}
-		break;
-	default:
-		if (*str == '*' || (*str >= '0' && *str <= '9')) {
-			/* <message-set> */
-			seqset = imap_messageset_parse(data->pool, str);
-			if (seqset == NULL) {
-				data->error = "Invalid messageset";
-				return FALSE;
-			}
-
-			if (!ARG_NEW_SINGLE(SEARCH_SEQSET))
-				return FALSE;
-
-			(*next_sarg)->value.seqset = seqset;
-			return TRUE;
-		}
-		break;
-	}
-
-	data->error = t_strconcat("Unknown argument ", str, NULL);
-	return FALSE;
-}
-
 struct mail_search_arg *
 imap_search_args_build(pool_t pool, struct mailbox *box,
 		       const struct imap_arg *args, const char **error_r)
 {
-        struct search_build_data data;
-	struct mail_search_arg *first_sarg, **sargs;
-
-	*error_r = NULL;
-
-	data.box = box;
-	data.pool = pool;
-	data.error = NULL;
+	struct mail_search_arg *sargs;
 
-	/* get the first arg */
-	first_sarg = NULL; sargs = &first_sarg;
-	while (args->type != IMAP_ARG_EOL) {
-		if (!search_arg_build(&data, &args, sargs)) {
-			imap_search_args_free(box, first_sarg);
-			*error_r = data.error;
-			return NULL;
-		}
-		sargs = &(*sargs)->next;
-	}
+	sargs = mail_search_build_from_imap_args(pool, args, error_r);
+	if (sargs == NULL)
+		return NULL;
 
-	return first_sarg;
+	mail_search_args_init(sargs, box, TRUE);
+	return sargs;
 }
 
 static bool
-msgset_is_valid(const struct mail_search_seqset *set, uint32_t messages_count)
+msgset_is_valid(ARRAY_TYPE(seq_range) *seqset, uint32_t messages_count)
 {
+	const struct seq_range *range;
+	unsigned int count;
+
 	/* when there are no messages, all messagesets are invalid.
 	   if there's at least one message:
 	    - * gives seq1 = seq2 = (uint32_t)-1
 	    - n:* should work if n <= messages_count
 	    - n:m or m should work if m <= messages_count
 	*/
-	if (set == NULL || messages_count == 0)
+	range = array_get(seqset, &count);
+	if (count == 0 || messages_count == 0)
 		return FALSE;
 
-	for (; set != NULL; set = set->next) {
-		if ((set->seq1 > messages_count && set->seq1 != (uint32_t)-1) ||
-		    (set->seq2 > messages_count && set->seq2 != (uint32_t)-1))
+	if (range[count-1].seq2 == (uint32_t)-1) {
+		if (range[count-1].seq1 > messages_count &&
+		    range[0].seq1 != (uint32_t)-1)
+			return FALSE;
+	} else {
+		if (range[count-1].seq2 > messages_count)
 			return FALSE;
 	}
 	return TRUE;
@@ -580,8 +66,9 @@
 
 	arg = p_new(cmd->pool, struct mail_search_arg, 1);
 	arg->type = SEARCH_SEQSET;
-	arg->value.seqset = imap_messageset_parse(cmd->pool, messageset);
-	if (!msgset_is_valid(arg->value.seqset, cmd->client->messages_count)) {
+	p_array_init(&arg->value.seqset, cmd->pool, 16);
+	if (imap_messageset_parse(&arg->value.seqset, messageset) < 0 ||
+	    !msgset_is_valid(&arg->value.seqset, cmd->client->messages_count)) {
 		*error_r = "Invalid messageset";
 		return -1;
 	}
@@ -589,16 +76,6 @@
 	return 0;
 }
 
-void imap_search_args_free(struct mailbox *box, struct mail_search_arg *args)
-{
-	for (; args != NULL; args = args->next) {
-		if (args->type == SEARCH_KEYWORDS)
-			mailbox_keywords_free(box, &args->value.keywords);
-		else if (args->type == SEARCH_SUB || args->type == SEARCH_OR)
-			imap_search_args_free(box, args->value.subargs);
-	}
-}
-
 static int
 imap_search_get_uidset_arg(pool_t pool, struct mailbox *box, const char *uidset,
 			   struct mail_search_arg **arg_r, const char **error_r)
@@ -606,10 +83,16 @@
 	struct mail_search_arg *arg;
 
 	arg = p_new(pool, struct mail_search_arg, 1);
-	arg->type = SEARCH_SEQSET;
+	arg->type = SEARCH_UIDSET;
+	p_array_init(&arg->value.seqset, pool, 16);
+	if (imap_messageset_parse(&arg->value.seqset, uidset) < 0) {
+		*error_r = "Invalid uidset";
+		return -1;
+	}
+
+	mail_search_args_init(arg, box, TRUE);
 	*arg_r = arg;
-	return imap_uidset_parse(pool, box, uidset, &arg->value.seqset,
-				 error_r);
+	return 0;
 }
 
 struct mail_search_arg *
@@ -617,7 +100,7 @@
 		    const char *set, bool uid)
 {
 	struct mail_search_arg *search_arg = NULL;
-	const char *error;
+	const char *error = NULL;
 	int ret;
 
 	if (!uid) {
--- a/src/imap/imap-search.h	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/imap/imap-search.h	Fri Mar 14 11:59:36 2008 +0200
@@ -9,8 +9,6 @@
 struct mail_search_arg *
 imap_search_args_build(pool_t pool, struct mailbox *box,
 		       const struct imap_arg *args, const char **error_r);
-/* Free allocated keywords */
-void imap_search_args_free(struct mailbox *box, struct mail_search_arg *args);
 
 struct mail_search_arg *
 imap_search_get_arg(struct client_command_context *cmd,
--- a/src/lib-imap/Makefile.am	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/lib-imap/Makefile.am	Fri Mar 14 11:59:36 2008 +0200
@@ -11,8 +11,9 @@
 	imap-date.c \
 	imap-envelope.c \
 	imap-match.c \
+	imap-messageset.c \
+	imap-parser.c \
 	imap-quote.c \
-	imap-parser.c \
 	imap-util.c
 
 headers = \
@@ -21,8 +22,9 @@
 	imap-date.h \
 	imap-envelope.h \
 	imap-match.h \
+	imap-messageset.h \
+	imap-parser.h \
 	imap-quote.h \
-	imap-parser.h \
 	imap-util.h
 
 if INSTALL_HEADERS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-messageset.c	Fri Mar 14 11:59:36 2008 +0200
@@ -0,0 +1,75 @@
+/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-messageset.h"
+
+static uint32_t get_next_number(const char **str)
+{
+	uint32_t num;
+
+	num = 0;
+	while (**str != '\0') {
+		if (**str < '0' || **str > '9')
+			break;
+
+		num = num*10 + (**str - '0');
+		(*str)++;
+	}
+
+	if (num == (uint32_t)-1) {
+		/* FIXME: ugly hack, we're using this number to mean the
+		   last existing message. In reality UIDs should never get
+		   this high, so we can quite safely just drop this one down. */
+		num--;
+	}
+
+	return num;
+}
+
+int imap_messageset_parse(ARRAY_TYPE(seq_range) *dest, const char *messageset)
+{
+	uint32_t seq1, seq2;
+
+	while (*messageset != '\0') {
+		if (*messageset == '*') {
+			/* last message */
+			seq1 = (uint32_t)-1;
+			messageset++;
+		} else {
+			seq1 = get_next_number(&messageset);
+			if (seq1 == 0)
+				return -1;
+		}
+
+		if (*messageset != ':')
+			seq2 = seq1;
+		else {
+			/* first:last range */
+			messageset++;
+
+			if (*messageset == '*') {
+				seq2 = (uint32_t)-1;
+				messageset++;
+			} else {
+				seq2 = get_next_number(&messageset);
+				if (seq2 == 0)
+					return -1;
+			}
+		}
+
+		if (*messageset == ',')
+			messageset++;
+		else if (*messageset != '\0')
+			return -1;
+
+		if (seq1 > seq2) {
+			/* swap, as specified by RFC-3501 */
+			uint32_t temp = seq1;
+			seq1 = seq2;
+			seq2 = temp;
+		}
+
+		seq_range_array_add_range(dest, seq1, seq2);
+	}
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-messageset.h	Fri Mar 14 11:59:36 2008 +0200
@@ -0,0 +1,8 @@
+#ifndef IMAP_MESSAGESET_H
+#define IMAP_MESSAGESET_H
+
+#include "seq-range-array.h"
+
+int imap_messageset_parse(ARRAY_TYPE(seq_range) *dest, const char *messageset);
+
+#endif
--- a/src/lib-storage/Makefile.am	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/lib-storage/Makefile.am	Fri Mar 14 11:59:36 2008 +0200
@@ -14,6 +14,7 @@
 	mail-error.c \
 	mail-namespace.c \
 	mail-search.c \
+	mail-search-build.c \
 	mail-storage.c \
 	mailbox-list.c \
 	mailbox-tree.c
@@ -23,6 +24,7 @@
 	mail-error.h \
 	mail-namespace.h \
 	mail-search.h \
+	mail-search-build.h \
 	mail-storage.h \
 	mail-storage-private.h \
 	mailbox-list.h \
--- a/src/lib-storage/index/index-search.c	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/lib-storage/index/index-search.c	Fri Mar 14 11:59:36 2008 +0200
@@ -89,22 +89,11 @@
 	}
 }
 
-static int seqset_contains(struct mail_search_seqset *set, uint32_t seq)
-{
-	while (set != NULL) {
-		if (seq >= set->seq1 && seq <= set->seq2)
-			return TRUE;
-		set = set->next;
-	}
-
-	return FALSE;
-}
-
 static void search_seqset_arg(struct mail_search_arg *arg,
 			      struct index_search_context *ctx)
 {
 	if (arg->type == SEARCH_SEQSET) {
-		if (seqset_contains(arg->value.seqset, ctx->mail_ctx.seq))
+		if (seq_range_exists(&arg->value.seqset, ctx->mail_ctx.seq))
 			ARG_SET_RESULT(arg, 1);
 		else
 			ARG_SET_RESULT(arg, 0);
@@ -146,7 +135,7 @@
 
 	switch (arg->type) {
 	case SEARCH_UIDSET:
-		return seqset_contains(arg->value.seqset, rec->uid);
+		return seq_range_exists(&arg->value.seqset, rec->uid);
 	case SEARCH_FLAGS:
 		flags = rec->flags;
 		if ((arg->value.flags & MAIL_RECENT) != 0 &&
@@ -605,106 +594,57 @@
 }
 
 static bool search_msgset_fix_limits(const struct mail_index_header *hdr,
-				     struct mail_search_seqset *set, bool not)
+				     ARRAY_TYPE(seq_range) *seqset, bool not)
 {
-	if (set == NULL)
-		return FALSE;
-
-	for (; set != NULL; set = set->next) {
-		if (set->seq1 > hdr->messages_count) {
-			if (set->seq1 != (uint32_t)-1 &&
-			    set->seq2 != (uint32_t)-1) {
-				if (not)
-					continue;
+	struct seq_range *range;
+	unsigned int count;
 
-				/* completely outside our range */
-				return FALSE;
-			}
-			/* either seq1 or seq2 is '*', so the last message is
-			   in range. */
-			set->seq1 = hdr->messages_count;
-		}
-		if (set->seq2 > hdr->messages_count)
-			set->seq2 = hdr->messages_count;
-
-		if (set->seq1 == 0 || set->seq2 == 0) {
-			/* this shouldn't happen. treat as nonexisting. */
-			return FALSE;
-		}
-	}
-	return TRUE;
-}
-
-static int mail_search_seqset_cmp(const void *p1, const void *p2)
-{
-	struct mail_search_seqset *const *set1 = p1, *const *set2 = p2;
-
-	return (*set1)->seq1 < (*set2)->seq2 ? -1 :
-		((*set1)->seq1 > (*set2)->seq2 ? 1 : 0);
-}
+	i_assert(hdr->messages_count > 0);
 
-static struct mail_search_seqset *
-search_msgset_sort(struct mail_search_seqset *set)
-{
-	struct mail_search_seqset **sets, *cur;
-	unsigned int i, count;
-
-	for (cur = set, count = 0; cur != NULL; cur = cur->next)
-		count++;
-
-	/* @UNSAFE */
-	sets = i_new(struct mail_search_seqset *, count);
-	for (i = 0, cur = set; i < count; i++, cur = cur->next)
-		sets[i] = cur;
-	qsort(sets, count, sizeof(*sets), mail_search_seqset_cmp);
-	for (i = 0; i < count-1; i++)
-		sets[i]->next = sets[i+1];
-	sets[i]->next = NULL;
-	set = sets[0];
-	i_free(sets);
-	return set;
-}
-
-static void search_msgset_compress(struct mail_search_seqset *set,
-				   struct mail_search_seqset **last_r)
-{
-	struct mail_search_seqset *cur;
-
-	for (cur = set; cur->next != NULL; ) {
-		if (cur->seq2 + 1 >= cur->next->seq1) {
-			if (cur->seq2 < cur->next->seq2)
-				cur->seq2 = cur->next->seq2;
-			cur->next = cur->next->next;
-		} else {
-			cur = cur->next;
+	range = array_get_modifiable(seqset, &count);
+	if (count > 0) {
+		i_assert(range[0].seq1 != 0);
+		if (range[count-1].seq2 == (uint32_t)-1) {
+			/* "*" used, make sure the last message is in the range
+			   (e.g. with count+1:* we still want to include it) */
+			seq_range_array_add(seqset, 0, hdr->messages_count);
 		}
+		/* remove all non-existing messages */
+		seq_range_array_remove_range(seqset, hdr->messages_count + 1,
+					     (uint32_t)-1);
 	}
-	*last_r = cur;
+	if (!not)
+		return array_count(seqset) > 0;
+	else {
+		/* if all messages are in the range, it can't match */
+		range = array_get_modifiable(seqset, &count);
+		return range[0].seq1 == 1 &&
+			range[count-1].seq2 == hdr->messages_count;
+	}
 }
 
 static void search_msgset_fix(const struct mail_index_header *hdr,
-			      struct mail_search_seqset **set_p,
+			      ARRAY_TYPE(seq_range) *seqset,
 			      uint32_t *seq1_r, uint32_t *seq2_r, bool not)
 {
-	struct mail_search_seqset *set = *set_p;
-	struct mail_search_seqset *last;
+	const struct seq_range *range;
+	unsigned int count;
 	uint32_t min_seq, max_seq;
 
-	if (!search_msgset_fix_limits(hdr, set, not)) {
+	if (!search_msgset_fix_limits(hdr, seqset, not)) {
 		*seq1_r = (uint32_t)-1;
 		*seq2_r = 0;
 		return;
 	}
-	set = *set_p = search_msgset_sort(set);
-	search_msgset_compress(set, &last);
 
+	range = array_get(seqset, &count);
 	if (!not) {
-		min_seq = set->seq1;
-		max_seq = last->seq2;
+		min_seq = range[0].seq1;
+		max_seq = range[count-1].seq2;
 	} else {
-		min_seq = set->seq1 > 1 ? 1 : set->seq2 + 1;
-		max_seq = last->seq2 < hdr->messages_count ?
-			hdr->messages_count : last->seq1 - 1;
+		min_seq = range[0].seq1 > 1 ? 1 : range[0].seq2 + 1;
+		max_seq = range[count-1].seq2 < hdr->messages_count ?
+			hdr->messages_count : range[count-1].seq1 - 1;
 		if (min_seq > max_seq) {
 			*seq1_r = (uint32_t)-1;
 			*seq2_r = 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-build.c	Fri Mar 14 11:59:36 2008 +0200
@@ -0,0 +1,565 @@
+/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-date.h"
+#include "imap-parser.h"
+#include "imap-messageset.h"
+#include "mail-search.h"
+#include "mail-search-build.h"
+#include "mail-storage.h"
+
+#include <stdlib.h>
+
+struct search_build_data {
+	pool_t pool;
+	const char *error;
+};
+
+static struct mail_search_arg *
+search_arg_new(pool_t pool, enum mail_search_arg_type type)
+{
+	struct mail_search_arg *arg;
+
+	arg = p_new(pool, struct mail_search_arg, 1);
+	arg->type = type;
+
+	return arg;
+}
+
+static bool
+arg_get_next(struct search_build_data *data, const struct imap_arg **args,
+	     const char **value_r)
+{
+	if ((*args)->type == IMAP_ARG_EOL) {
+		data->error = "Missing parameter for argument";
+		return FALSE;
+	}
+	if ((*args)->type != IMAP_ARG_ATOM &&
+	    (*args)->type != IMAP_ARG_STRING) {
+		data->error = "Invalid parameter for argument";
+		return FALSE;
+	}
+
+	*value_r = IMAP_ARG_STR(*args);
+	*args += 1;
+	return TRUE;
+}
+
+#define ARG_NEW_SINGLE(type) \
+	arg_new_single(data, next_sarg, type)
+static bool
+arg_new_single(struct search_build_data *data,
+	       struct mail_search_arg **next_sarg,
+	       enum mail_search_arg_type type)
+{
+	*next_sarg = search_arg_new(data->pool, type);
+	return TRUE;
+}
+
+#define ARG_NEW_STR(type) \
+	arg_new_str(data, args, next_sarg, type)
+static bool
+arg_new_str(struct search_build_data *data,
+	    const struct imap_arg **args, struct mail_search_arg **next_sarg,
+	    enum mail_search_arg_type type)
+{
+	struct mail_search_arg *sarg;
+	const char *value;
+
+	*next_sarg = sarg = search_arg_new(data->pool, type);
+	if (!arg_get_next(data, args, &value))
+		return FALSE;
+	sarg->value.str = p_strdup(data->pool, value);
+	return TRUE;
+}
+
+#define ARG_NEW_FLAGS(flags) \
+	arg_new_flags(data, next_sarg, flags)
+static bool
+arg_new_flags(struct search_build_data *data,
+	      struct mail_search_arg **next_sarg, enum mail_flags flags)
+{
+	struct mail_search_arg *sarg;
+
+	*next_sarg = sarg = search_arg_new(data->pool, SEARCH_FLAGS);
+	sarg->value.flags = flags;
+	return TRUE;
+}
+
+#define ARG_NEW_SIZE(type) \
+	arg_new_size(data, args, next_sarg, type)
+static bool
+arg_new_size(struct search_build_data *data,
+	     const struct imap_arg **args, struct mail_search_arg **next_sarg,
+	     enum mail_search_arg_type type)
+{
+	struct mail_search_arg *sarg;
+	const char *value;
+	char *p;
+
+	*next_sarg = sarg = search_arg_new(data->pool, type);
+	if (!arg_get_next(data, args, &value))
+		return FALSE;
+
+	sarg->value.size = strtoull(value, &p, 10);
+	if (*p != '\0') {
+		data->error = "Invalid search size parameter";
+		return FALSE;
+	}
+	return TRUE;
+}
+
+#define ARG_NEW_DATE(type) \
+	arg_new_date(data, args, next_sarg, type)
+static bool
+arg_new_date(struct search_build_data *data,
+	     const struct imap_arg **args, struct mail_search_arg **next_sarg,
+	     enum mail_search_arg_type type)
+{
+	struct mail_search_arg *sarg;
+	const char *value;
+
+	*next_sarg = sarg = search_arg_new(data->pool, type);
+	if (!arg_get_next(data, args, &value))
+		return FALSE;
+	if (!imap_parse_date(value, &sarg->value.time)) {
+		data->error = "Invalid search date parameter";
+		return FALSE;
+	}
+	return TRUE;
+}
+
+#define ARG_NEW_HEADER(type, hdr_name) \
+	arg_new_header(data, args, next_sarg, type, hdr_name)
+static bool
+arg_new_header(struct search_build_data *data,
+	       const struct imap_arg **args, struct mail_search_arg **next_sarg,
+	       enum mail_search_arg_type type, const char *hdr_name)
+{
+	struct mail_search_arg *sarg;
+	const char *value;
+
+	*next_sarg = sarg = search_arg_new(data->pool, type);
+	if (!arg_get_next(data, args, &value))
+		return FALSE;
+
+	sarg->hdr_field_name = p_strdup(data->pool, hdr_name);
+	sarg->value.str = p_strdup(data->pool, value);
+	return TRUE;
+}
+
+static bool search_arg_build(struct search_build_data *data,
+			     const struct imap_arg **args,
+			     struct mail_search_arg **next_sarg)
+{
+	struct mail_search_arg **subargs, *sarg;
+	const struct imap_arg *arg;
+	const char *str;
+
+	if ((*args)->type == IMAP_ARG_EOL) {
+		data->error = "Missing argument";
+		return FALSE;
+	}
+
+	arg = *args;
+
+	if (arg->type == IMAP_ARG_NIL) {
+		/* NIL not allowed */
+		data->error = "NIL not allowed";
+		return FALSE;
+	}
+
+	if (arg->type == IMAP_ARG_LIST) {
+		const struct imap_arg *listargs = IMAP_ARG_LIST_ARGS(arg);
+
+		if (listargs->type == IMAP_ARG_EOL) {
+			data->error = "Empty list not allowed";
+			return FALSE;
+		}
+
+		*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
+		subargs = &(*next_sarg)->value.subargs;
+		while (listargs->type != IMAP_ARG_EOL) {
+			if (!search_arg_build(data, &listargs, subargs))
+				return FALSE;
+			subargs = &(*subargs)->next;
+		}
+
+		*args += 1;
+		return TRUE;
+	}
+
+	i_assert(arg->type == IMAP_ARG_ATOM ||
+		 arg->type == IMAP_ARG_STRING);
+
+	/* string argument - get the name and jump to next */
+	str = IMAP_ARG_STR(arg);
+	*args += 1;
+	str = t_str_ucase(str);
+
+	switch (*str) {
+	case 'A':
+		if (strcmp(str, "ANSWERED") == 0)
+			return ARG_NEW_FLAGS(MAIL_ANSWERED);
+		else if (strcmp(str, "ALL") == 0)
+			return ARG_NEW_SINGLE(SEARCH_ALL);
+		break;
+	case 'B':
+		if (strcmp(str, "BODY") == 0) {
+			/* <string> */
+			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
+			    *IMAP_ARG_STR(*args) == '\0') {
+				*args += 1;
+				return ARG_NEW_SINGLE(SEARCH_ALL);
+			}
+			return ARG_NEW_STR(SEARCH_BODY);
+		} else if (strcmp(str, "BEFORE") == 0) {
+			/* <date> */
+			return ARG_NEW_DATE(SEARCH_BEFORE);
+		} else if (strcmp(str, "BCC") == 0) {
+			/* <string> */
+			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
+		}
+		break;
+	case 'C':
+		if (strcmp(str, "CC") == 0) {
+			/* <string> */
+			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
+		}
+		break;
+	case 'D':
+		if (strcmp(str, "DELETED") == 0)
+			return ARG_NEW_FLAGS(MAIL_DELETED);
+		else if (strcmp(str, "DRAFT") == 0)
+			return ARG_NEW_FLAGS(MAIL_DRAFT);
+		break;
+	case 'F':
+		if (strcmp(str, "FLAGGED") == 0)
+			return ARG_NEW_FLAGS(MAIL_FLAGGED);
+		else if (strcmp(str, "FROM") == 0) {
+			/* <string> */
+			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
+		}
+		break;
+	case 'H':
+		if (strcmp(str, "HEADER") == 0) {
+			/* <field-name> <string> */
+			const char *key;
+
+			if ((*args)->type == IMAP_ARG_EOL) {
+				data->error = "Missing parameter for HEADER";
+				return FALSE;
+			}
+			if ((*args)->type != IMAP_ARG_ATOM &&
+			    (*args)->type != IMAP_ARG_STRING) {
+				data->error = "Invalid parameter for HEADER";
+				return FALSE;
+			}
+
+			key = t_str_ucase(IMAP_ARG_STR(*args));
+			*args += 1;
+			return ARG_NEW_HEADER(SEARCH_HEADER, key);
+		}
+		break;
+	case 'K':
+		if (strcmp(str, "KEYWORD") == 0) {
+			return ARG_NEW_STR(SEARCH_KEYWORDS);
+		}
+		break;
+	case 'L':
+		if (strcmp(str, "LARGER") == 0) {
+			/* <n> */
+			return ARG_NEW_SIZE(SEARCH_LARGER);
+		}
+		break;
+	case 'N':
+		if (strcmp(str, "NOT") == 0) {
+			if (!search_arg_build(data, args, next_sarg))
+				return FALSE;
+			(*next_sarg)->not = !(*next_sarg)->not;
+			return TRUE;
+		} else if (strcmp(str, "NEW") == 0) {
+			/* NEW == (RECENT UNSEEN) */
+			*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
+
+			subargs = &(*next_sarg)->value.subargs;
+			*subargs = search_arg_new(data->pool, SEARCH_FLAGS);
+			(*subargs)->value.flags = MAIL_RECENT;
+			(*subargs)->next = search_arg_new(data->pool,
+							  SEARCH_FLAGS);
+			(*subargs)->next->value.flags = MAIL_SEEN;
+			(*subargs)->next->not = TRUE;
+			return TRUE;
+		}
+		break;
+	case 'O':
+		if (strcmp(str, "OR") == 0) {
+			/* <search-key1> <search-key2> */
+			*next_sarg = search_arg_new(data->pool, SEARCH_OR);
+
+			subargs = &(*next_sarg)->value.subargs;
+			for (;;) {
+				if (!search_arg_build(data, args, subargs))
+					return FALSE;
+
+				subargs = &(*subargs)->next;
+
+				/* <key> OR <key> OR ... <key> - put them all
+				   under one SEARCH_OR list. */
+				if ((*args)->type == IMAP_ARG_EOL)
+					break;
+
+				if ((*args)->type != IMAP_ARG_ATOM ||
+				    strcasecmp(IMAP_ARG_STR_NONULL(*args),
+					       "OR") != 0)
+					break;
+
+				*args += 1;
+			}
+
+			if (!search_arg_build(data, args, subargs))
+				return FALSE;
+			return TRUE;
+		} if (strcmp(str, "ON") == 0) {
+			/* <date> */
+			return ARG_NEW_DATE(SEARCH_ON);
+		} if (strcmp(str, "OLD") == 0) {
+			/* OLD == NOT RECENT */
+			if (!ARG_NEW_FLAGS(MAIL_RECENT))
+				return FALSE;
+
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		}
+		break;
+	case 'R':
+		if (strcmp(str, "RECENT") == 0)
+			return ARG_NEW_FLAGS(MAIL_RECENT);
+		break;
+	case 'S':
+		if (strcmp(str, "SEEN") == 0)
+			return ARG_NEW_FLAGS(MAIL_SEEN);
+		else if (strcmp(str, "SUBJECT") == 0) {
+			/* <string> */
+			return ARG_NEW_HEADER(SEARCH_HEADER_COMPRESS_LWSP, str);
+		} else if (strcmp(str, "SENTBEFORE") == 0) {
+			/* <date> */
+			return ARG_NEW_DATE(SEARCH_SENTBEFORE);
+		} else if (strcmp(str, "SENTON") == 0) {
+			/* <date> */
+			return ARG_NEW_DATE(SEARCH_SENTON);
+		} else if (strcmp(str, "SENTSINCE") == 0) {
+			/* <date> */
+			return ARG_NEW_DATE(SEARCH_SENTSINCE);
+		} else if (strcmp(str, "SINCE") == 0) {
+			/* <date> */
+			return ARG_NEW_DATE(SEARCH_SINCE);
+		} else if (strcmp(str, "SMALLER") == 0) {
+			/* <n> */
+			return ARG_NEW_SIZE(SEARCH_SMALLER);
+		}
+		break;
+	case 'T':
+		if (strcmp(str, "TEXT") == 0) {
+			/* <string> */
+			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
+			    *IMAP_ARG_STR(*args) == '\0') {
+				*args += 1;
+				return ARG_NEW_SINGLE(SEARCH_ALL);
+			}
+			return ARG_NEW_STR(SEARCH_TEXT);
+		} else if (strcmp(str, "TO") == 0) {
+			/* <string> */
+			return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
+		}
+		break;
+	case 'U':
+		if (strcmp(str, "UID") == 0) {
+			/* <message set> */
+			if (!ARG_NEW_STR(SEARCH_SEQSET))
+				return FALSE;
+
+			sarg = *next_sarg;
+			p_array_init(&sarg->value.seqset, data->pool, 16);
+			if (imap_messageset_parse(&sarg->value.seqset,
+						  sarg->value.str) < 0) {
+				data->error = "Invalid UID messageset";
+				return FALSE;
+			}
+			return TRUE;
+		} else if (strcmp(str, "UNANSWERED") == 0) {
+			if (!ARG_NEW_FLAGS(MAIL_ANSWERED))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNDELETED") == 0) {
+			if (!ARG_NEW_FLAGS(MAIL_DELETED))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNDRAFT") == 0) {
+			if (!ARG_NEW_FLAGS(MAIL_DRAFT))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNFLAGGED") == 0) {
+			if (!ARG_NEW_FLAGS(MAIL_FLAGGED))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNKEYWORD") == 0) {
+			if (!ARG_NEW_STR(SEARCH_KEYWORDS))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNSEEN") == 0) {
+			if (!ARG_NEW_FLAGS(MAIL_SEEN))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		}
+		break;
+	case 'X':
+		if (strcmp(str, "X-BODY-FAST") == 0) {
+			/* <string> */
+			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
+			    *IMAP_ARG_STR(*args) == '\0') {
+				*args += 1;
+				return ARG_NEW_SINGLE(SEARCH_ALL);
+			}
+			return ARG_NEW_STR(SEARCH_BODY_FAST);
+		} else if (strcmp(str, "X-TEXT-FAST") == 0) {
+			/* <string> */
+			if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
+			    *IMAP_ARG_STR(*args) == '\0') {
+				*args += 1;
+				return ARG_NEW_SINGLE(SEARCH_ALL);
+			}
+			return ARG_NEW_STR(SEARCH_TEXT_FAST);
+		}
+		break;
+	default:
+		if (*str == '*' || (*str >= '0' && *str <= '9')) {
+			/* <message-set> */
+			if (!ARG_NEW_SINGLE(SEARCH_SEQSET))
+				return FALSE;
+
+			p_array_init(&(*next_sarg)->value.seqset,
+				     data->pool, 16);
+			if (imap_messageset_parse(&(*next_sarg)->value.seqset,
+						  str) < 0) {
+				data->error = "Invalid messageset";
+				return FALSE;
+			}
+			return TRUE;
+		}
+		break;
+	}
+
+	data->error = t_strconcat("Unknown argument ", str, NULL);
+	return FALSE;
+}
+
+struct mail_search_arg *
+mail_search_build_from_imap_args(pool_t pool, const struct imap_arg *args,
+				 const char **error_r)
+{
+        struct search_build_data data;
+	struct mail_search_arg *first_sarg, **sargs;
+
+	data.pool = pool;
+	data.error = NULL;
+
+	first_sarg = NULL; sargs = &first_sarg;
+	while (args->type != IMAP_ARG_EOL) {
+		if (!search_arg_build(&data, &args, sargs)) {
+			first_sarg = NULL;
+			break;
+		}
+		sargs = &(*sargs)->next;
+	}
+
+	*error_r = data.error;
+	return first_sarg;
+}
+
+static void
+mailbox_uidseq_change(struct mail_search_arg *arg, struct mailbox *box)
+{
+	struct seq_range *uids;
+	unsigned int i, count;
+	uint32_t seq1, seq2;
+
+	arg->type = SEARCH_SEQSET;
+
+	/* make a copy of the UIDs */
+	count = array_count(&arg->value.seqset);
+	uids = t_new(struct seq_range, count);
+	memcpy(uids, array_idx(&arg->value.seqset, 0), sizeof(*uids) * count);
+
+	/* put them back to the range as sequences */
+	array_clear(&arg->value.seqset);
+	for (i = 0; i < count; i++) {
+		mailbox_get_uids(box, uids[i].seq1, uids[i].seq2, &seq1, &seq2);
+		if (seq1 != 0) {
+			seq_range_array_add_range(&arg->value.seqset,
+						  seq1, seq2);
+		}
+		if (uids[i].seq2 == (uint32_t)-1) {
+			/* make sure the last message is in the range */
+			mailbox_get_uids(box, 1, (uint32_t)-1, &seq1, &seq2);
+			seq_range_array_add(&arg->value.seqset, 0, seq2);
+		}
+	}
+}
+
+void mail_search_args_init(struct mail_search_arg *args,
+			   struct mailbox *box, bool change_uidsets)
+{
+	const char *keywords[2];
+
+	for (; args != NULL; args = args->next) {
+		switch (args->type) {
+		case SEARCH_UIDSET:
+			if (change_uidsets) T_BEGIN {
+				mailbox_uidseq_change(args, box);
+			} T_END;
+			break;
+		case SEARCH_KEYWORDS:
+			keywords[0] = args->value.str;
+			keywords[1] = NULL;
+
+			i_assert(args->value.keywords == NULL);
+			args->value.keywords =
+				mailbox_keywords_create_valid(box, keywords);
+			break;
+		case SEARCH_SUB:
+		case SEARCH_OR:
+			mail_search_args_init(args->value.subargs, box,
+					      change_uidsets);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+void mail_search_args_deinit(struct mail_search_arg *args,
+			     struct mailbox *box)
+{
+	for (; args != NULL; args = args->next) {
+		switch (args->type) {
+		case SEARCH_KEYWORDS:
+			if (args->value.keywords == NULL)
+				break;
+			mailbox_keywords_free(box, &args->value.keywords);
+			break;
+		case SEARCH_SUB:
+		case SEARCH_OR:
+			mail_search_args_deinit(args->value.subargs, box);
+			break;
+		default:
+			break;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-build.h	Fri Mar 14 11:59:36 2008 +0200
@@ -0,0 +1,19 @@
+#ifndef MAIL_SEARCH_BUILD_H
+#define MAIL_SEARCH_BUILD_H
+
+struct imap_arg;
+struct mailbox;
+
+struct mail_search_arg *
+mail_search_build_from_imap_args(pool_t pool, const struct imap_arg *args,
+				 const char **error_r);
+
+/* 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);
+/* Free keywords. The args can initialized afterwards again if needed. */
+void mail_search_args_deinit(struct mail_search_arg *args,
+			     struct mailbox *box);
+
+#endif
--- a/src/lib-storage/mail-search.h	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/lib-storage/mail-search.h	Fri Mar 14 11:59:36 2008 +0200
@@ -1,6 +1,7 @@
 #ifndef MAIL_SEARCH_H
 #define MAIL_SEARCH_H
 
+#include "seq-range-array.h"
 #include "mail-types.h"
 
 enum mail_search_arg_type {
@@ -40,18 +41,13 @@
 	SEARCH_TEXT_FAST
 };
 
-struct mail_search_seqset {
-	uint32_t seq1, seq2;
-        struct mail_search_seqset *next;
-};
-
 struct mail_search_arg {
 	struct mail_search_arg *next;
 
 	enum mail_search_arg_type type;
 	struct {
 		struct mail_search_arg *subargs;
-                struct mail_search_seqset *seqset;
+		ARRAY_TYPE(seq_range) seqset;
 		const char *str;
 		time_t time;
 		uoff_t size;
--- a/src/plugins/fts/fts-storage.c	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/plugins/fts/fts-storage.c	Fri Mar 14 11:59:36 2008 +0200
@@ -25,7 +25,6 @@
 
 struct fts_storage_build_context {
 	struct mail_search_context *search_ctx;
-	struct mail_search_seqset seqset;
 	struct mail_search_arg search_arg;
 	struct mail *mail;
 	struct fts_backend_build_context *build;
@@ -171,16 +170,13 @@
 	struct fts_backend *backend = fctx->build_backend;
 	struct fts_storage_build_context *ctx;
 	struct fts_backend_build_context *build;
-	struct mail_search_seqset seqset;
-	uint32_t last_uid, last_uid_locked;
+	uint32_t last_uid, last_uid_locked, seq1, seq2;
 
 	if (fts_backend_get_last_uid(backend, &last_uid) < 0)
 		return -1;
 
-	memset(&seqset, 0, sizeof(seqset));
-	mailbox_get_uids(t->box, last_uid+1, (uint32_t)-1,
-			 &seqset.seq1, &seqset.seq2);
-	if (seqset.seq1 == 0) {
+	mailbox_get_uids(t->box, last_uid+1, (uint32_t)-1, &seq1, &seq2);
+	if (seq1 == 0) {
 		/* no new messages */
 		return 0;
 	}
@@ -197,8 +193,8 @@
 
 		last_uid = last_uid_locked;
 		mailbox_get_uids(t->box, last_uid+1, (uint32_t)-1,
-				 &seqset.seq1, &seqset.seq2);
-		if (seqset.seq1 == 0) {
+				 &seq1, &seq2);
+		if (seq1 == 0) {
 			/* no new messages */
 			(void)fts_backend_build_deinit(&build);
 			return 0;
@@ -207,9 +203,9 @@
 
 	ctx = i_new(struct fts_storage_build_context, 1);
 	ctx->build = build;
-	ctx->seqset = seqset;
 	ctx->search_arg.type = SEARCH_SEQSET;
-	ctx->search_arg.value.seqset = &ctx->seqset;
+	i_array_init(&ctx->search_arg.value.seqset, 1);
+	seq_range_array_add_range(&ctx->search_arg.value.seqset, seq1, seq2);
 
 	ctx->headers = str_new(default_pool, 512);
 	ctx->mail = mail_alloc(t, 0, NULL);
@@ -243,6 +239,7 @@
 	}
 
 	str_free(&ctx->headers);
+	array_free(&ctx->search_arg.value.seqset);
 	i_free(ctx);
 	return ret;
 }
@@ -250,6 +247,7 @@
 static void fts_build_notify(struct fts_storage_build_context *ctx)
 {
 	struct mailbox *box = ctx->mail->transaction->box;
+	const struct seq_range *range;
 	float percentage;
 	unsigned int msecs, secs;
 
@@ -258,8 +256,9 @@
 		   already spent some time indexing the mailbox */
 		ctx->search_start_time = ioloop_timeval;
 	} else if (box->storage->callbacks->notify_ok != NULL) {
-		percentage = (ctx->mail->seq - ctx->seqset.seq1) * 100.0 /
-			(ctx->seqset.seq2 - ctx->seqset.seq1);
+		range = array_idx(&ctx->search_arg.value.seqset, 0);
+		percentage = (ctx->mail->seq - range->seq1) * 100.0 /
+			(range->seq2 - range->seq1);
 		msecs = (ioloop_timeval.tv_sec -
 			 ctx->search_start_time.tv_sec) * 1000 +
 			(ioloop_timeval.tv_usec -
--- a/src/pop3/commands.c	Fri Mar 14 09:44:34 2008 +0200
+++ b/src/pop3/commands.c	Fri Mar 14 11:59:36 2008 +0200
@@ -1,6 +1,7 @@
 /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
 
 #include "common.h"
+#include "array.h"
 #include "istream.h"
 #include "ostream.h"
 #include "str.h"
@@ -185,7 +186,6 @@
 static bool expunge_mails(struct client *client)
 {
 	struct mail_search_arg search_arg;
-        struct mail_search_seqset seqset;
 	struct mail_search_context *ctx;
 	struct mail *mail;
 	uint32_t idx;
@@ -198,12 +198,11 @@
 		return TRUE;
 	}
 
-	memset(&seqset, 0, sizeof(seqset));
 	memset(&search_arg, 0, sizeof(search_arg));
-	seqset.seq1 = 1;
-	seqset.seq2 = client->messages_count;
 	search_arg.type = SEARCH_SEQSET;
-	search_arg.value.seqset = &seqset;
+	t_array_init(&search_arg.value.seqset, 1);
+	seq_range_array_add_range(&search_arg.value.seqset,
+				  1, client->messages_count);
 
 	ctx = mailbox_search_init(client->trans, NULL, &search_arg, NULL);
 	mail = mail_alloc(client->trans, 0, NULL);
@@ -255,7 +254,6 @@
 	uoff_t body_lines;
 
 	struct mail_search_arg search_arg;
-        struct mail_search_seqset seqset;
 
 	unsigned char last;
 	bool cr_skipped, in_body;
@@ -264,6 +262,7 @@
 static void fetch_deinit(struct fetch_context *ctx)
 {
 	(void)mailbox_search_deinit(&ctx->search_ctx);
+	array_free(&ctx->search_arg.value.seqset);
 	mail_free(&ctx->mail);
 	i_free(ctx);
 }
@@ -371,9 +370,9 @@
 
 	ctx = i_new(struct fetch_context, 1);
 
-	ctx->seqset.seq1 = ctx->seqset.seq2 = msgnum+1;
 	ctx->search_arg.type = SEARCH_SEQSET;
-	ctx->search_arg.value.seqset = &ctx->seqset;
+	i_array_init(&ctx->search_arg.value.seqset, 1);
+	seq_range_array_add(&ctx->search_arg.value.seqset, 0, msgnum+1);
 
 	ctx->search_ctx = mailbox_search_init(client->trans, NULL,
 					      &ctx->search_arg, NULL);
@@ -432,7 +431,6 @@
 	struct mail_search_context *search_ctx;
 	struct mail *mail;
 	struct mail_search_arg search_arg;
-        struct mail_search_seqset seqset;
 
 	client->last_seen = 0;
 
@@ -445,12 +443,11 @@
 
 	if (enable_last_command) {
 		/* remove all \Seen flags (as specified by RFC 1460) */
-		memset(&seqset, 0, sizeof(seqset));
 		memset(&search_arg, 0, sizeof(search_arg));
-		seqset.seq1 = 1;
-		seqset.seq2 = client->messages_count;
 		search_arg.type = SEARCH_SEQSET;
-		search_arg.value.seqset = &seqset;
+		t_array_init(&search_arg.value.seqset, 1);
+		seq_range_array_add_range(&search_arg.value.seqset,
+					  1, client->messages_count);
 
 		search_ctx = mailbox_search_init(client->trans, NULL,
 						 &search_arg, NULL);
@@ -503,7 +500,6 @@
 	unsigned int message;
 
 	struct mail_search_arg search_arg;
-	struct mail_search_seqset seqset;
 };
 
 static bool list_uids_iter(struct client *client, struct cmd_uidl_context *ctx)
@@ -586,6 +582,7 @@
 
 	if (ctx->message == 0)
 		client_send_line(client, ".");
+	array_free(&ctx->search_arg.value.seqset);
 	i_free(ctx);
 	return found;
 }
@@ -605,15 +602,15 @@
 
 	ctx = i_new(struct cmd_uidl_context, 1);
 
+	ctx->search_arg.type = SEARCH_SEQSET;
+	i_array_init(&ctx->search_arg.value.seqset, 1);
 	if (message == 0) {
-		ctx->seqset.seq1 = 1;
-		ctx->seqset.seq2 = client->messages_count;
+		seq_range_array_add_range(&ctx->search_arg.value.seqset,
+					  1, client->messages_count);
 	} else {
 		ctx->message = message;
-		ctx->seqset.seq1 = ctx->seqset.seq2 = message;
+		seq_range_array_add(&ctx->search_arg.value.seqset, 0, message);
 	}
-	ctx->search_arg.type = SEARCH_SEQSET;
-	ctx->search_arg.value.seqset = &ctx->seqset;
 
 	wanted_fields = 0;
 	if ((uidl_keymask & UIDL_MD5) != 0)