changeset 988:8028c4dcf38f HEAD

mail-storage.h interface changes, affects pretty much everything. FETCH, SEARCH, SORT and THREAD handling were pretty much moved from lib-storage/ to imap/ so adding non-index storages would be much easier now. Also POP3 server can now be easily implemented with lib-storage. Not too well tested, and at least one major problem: partial fetching is _slow_.
author Timo Sirainen <tss@iki.fi>
date Mon, 20 Jan 2003 16:52:51 +0200
parents cbf096fbb9f0
children e6a7afe4cca7
files src/imap/Makefile.am src/imap/cmd-append.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/commands-util.c src/imap/commands-util.h src/imap/imap-fetch-body-section.c src/imap/imap-fetch.c src/imap/imap-fetch.h src/imap/imap-search.c src/imap/imap-search.h src/imap/imap-sort.c src/imap/imap-sort.h src/imap/imap-thread.c src/imap/imap-thread.h src/lib-imap/Makefile.am src/lib-imap/imap-envelope.c src/lib-imap/imap-message-cache.c src/lib-imap/imap-message-cache.h src/lib-index/mail-index-data.c src/lib-mail/message-address.c src/lib-mail/message-address.h src/lib-mail/message-body-search.c src/lib-mail/message-body-search.h src/lib-mail/message-date.c src/lib-mail/message-date.h src/lib-mail/message-parser.h src/lib-mail/message-send.c src/lib-mail/message-send.h src/lib-mail/message-size.h src/lib-mail/message-tokenize.h src/lib-storage/Makefile.am src/lib-storage/index/Makefile.am src/lib-storage/index/index-copy.c src/lib-storage/index/index-fetch-section.c src/lib-storage/index/index-fetch.c src/lib-storage/index/index-fetch.h src/lib-storage/index/index-messageset.c src/lib-storage/index/index-messageset.h src/lib-storage/index/index-msgcache.c src/lib-storage/index/index-search.c src/lib-storage/index/index-sort.c src/lib-storage/index/index-sort.h src/lib-storage/index/index-storage.c src/lib-storage/index/index-storage.h src/lib-storage/index/index-update-flags.c src/lib-storage/index/maildir/maildir-copy.c src/lib-storage/index/maildir/maildir-save.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-storage.h src/lib-storage/index/mbox/mbox-save.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/mbox/mbox-storage.h src/lib-storage/mail-search.c src/lib-storage/mail-search.h src/lib-storage/mail-sort.c src/lib-storage/mail-sort.h src/lib-storage/mail-storage.h src/lib-storage/mail-thread.c src/lib-storage/mail-thread.h src/lib/strfuncs.c src/lib/strfuncs.h
diffstat 65 files changed, 4178 insertions(+), 4947 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/Makefile.am	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/Makefile.am	Mon Jan 20 16:52:51 2003 +0200
@@ -56,13 +56,23 @@
 	client.c \
 	commands.c \
 	commands-util.c \
+	imap-fetch.c \
+	imap-fetch-body-section.c \
+	imap-search.c \
+	imap-sort.c \
+	imap-thread.c \
 	mail-storage-callbacks.c \
 	main.c \
 	rawlog.c
 
+
 noinst_HEADERS = \
 	client.h \
 	commands.h \
 	commands-util.h \
 	common.h \
+	imap-fetch.h \
+	imap-search.h \
+	imap-sort.h \
+	imap-thread.h \
 	rawlog.h
--- a/src/imap/cmd-append.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/cmd-append.c	Mon Jan 20 16:52:51 2003 +0200
@@ -76,9 +76,8 @@
 {
 	struct imap_arg_list *flags_list;
 	struct mailbox *box;
-	enum mail_flags flags;
+	struct mail_full_flags flags;
 	time_t internal_date;
-	const char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT];
 	const char *mailbox, *internal_date_str;
 	uoff_t msg_size;
 	int failed, timezone_offset;
@@ -97,13 +96,10 @@
 
 	if (flags_list != NULL) {
 		if (!client_parse_mail_flags(client, flags_list->args,
-					     flags_list->size,
-					     &flags, custom_flags))
+					     &flags))
 			return TRUE;
 	} else {
-		if (!client_parse_mail_flags(client, NULL, 0,
-					     &flags, custom_flags))
-			return TRUE;
+		memset(&flags, 0, sizeof(flags));
 	}
 
 	if (internal_date_str == NULL) {
@@ -131,8 +127,7 @@
 	o_stream_flush(client->output);
 
 	/* save the mail */
-	failed = !box->save(box, flags, custom_flags,
-			    internal_date, timezone_offset,
+	failed = !box->save(box, &flags, internal_date, timezone_offset,
 			    client->input, msg_size);
 	box->close(box);
 
--- a/src/imap/cmd-fetch.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/cmd-fetch.c	Mon Jan 20 16:52:51 2003 +0200
@@ -2,6 +2,7 @@
 
 #include "common.h"
 #include "commands.h"
+#include "imap-fetch.h"
 
 /* Parse next digits in string into integer. Returns FALSE if the integer
    becomes too big and wraps. */
@@ -23,23 +24,96 @@
 	return TRUE;
 }
 
-/* BODY[] and BODY.PEEK[] items. item points to next character after '[' */
-static int parse_body_section(struct client *client, const char *item,
-			      struct mail_fetch_data *data, int peek)
+static int check_header_section(const char *section)
+{
+	/* HEADER, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
+	if (*section == '\0')
+		return TRUE;
+
+	if (strncmp(section, ".FIELDS", 7) != 0)
+		return FALSE;
+
+	section += 7;
+	if (strncmp(section, ".NOT", 4) == 0)
+		section += 4;
+
+	while (*section == ' ') section++;
+	if (*section++ != '(')
+		return FALSE;
+
+	while (*section != '\0' && *section != ')') {
+		if (*section == '(')
+			return FALSE;
+		section++;
+	}
+
+	if (*section++ != ')')
+		return FALSE;
+
+	if (*section != '\0')
+		return FALSE;
+	return TRUE;
+}
+
+static int check_section(struct client *client, const char *section,
+			 enum mail_fetch_field *fetch_data)
 {
-	struct mail_fetch_body_data *body;
+	if (*section == '\0') {
+		*fetch_data |= MAIL_FETCH_STREAM_HEADER |
+			MAIL_FETCH_STREAM_BODY;
+		return TRUE;
+	}
+
+	if (strcmp(section, "TEXT") == 0) {
+		*fetch_data |= MAIL_FETCH_STREAM_BODY;
+		return TRUE;
+	}
+
+	if (strncmp(section, "HEADER", 6) == 0) {
+		*fetch_data |= MAIL_FETCH_STREAM_HEADER;
+		if (check_header_section(section+6))
+			return TRUE;
+	} else if (*section >= '0' && *section <= '9') {
+		*fetch_data |= MAIL_FETCH_STREAM_BODY |
+			MAIL_FETCH_MESSAGE_PARTS;
+
+		while ((*section >= '0' && *section <= '9') ||
+		       *section == '.') section++;
+
+		if (*section == '\0')
+			return TRUE;
+		if (strcmp(section, "MIME") == 0 ||
+		    strcmp(section, "TEXT") == 0)
+			return TRUE;
+
+		if (strncmp(section, "HEADER", 6) == 0 &&
+		    check_header_section(section+6))
+			return TRUE;
+	}
+
+	client_send_tagline(client, t_strconcat(
+		"BAD Invalid BODY[] section: ", section, NULL));
+	return FALSE;
+}
+
+/* BODY[] and BODY.PEEK[] items. item points to next character after '[' */
+static int parse_body_section(struct client *client, const char *item, int peek,
+			      enum mail_fetch_field *fetch_data,
+			      struct imap_fetch_body_data ***bodies)
+{
+	/* @UNSAFE */
+	struct imap_fetch_body_data *body;
 	uoff_t num;
-	const char *section;
 	char *p;
 
-	body = t_new(struct mail_fetch_body_data, 1);
+	body = t_new(struct imap_fetch_body_data, 1);
 	body->peek = peek;
 
 	p = t_strdup_noconst(item);
 
 	/* read section */
 	body->section = p;
-	for (section = p; *p != ']'; p++) {
+	for (; *p != ']'; p++) {
 		if (*p == '\0') {
 			client_send_tagline(client, t_strconcat(
 				"BAD Missing ']' with ", item, NULL));
@@ -48,6 +122,9 @@
 	}
 	*p++ = '\0';
 
+	if (!check_section(client, body->section, fetch_data))
+		return FALSE;
+
 	/* <start.end> */
 	body->skip = 0;
 	body->max_size = (uoff_t)-1;
@@ -89,14 +166,15 @@
 		}
 	}
 
-	body->next = data->body_sections;
-	data->body_sections = body;
-
+	**bodies = body;
+	*bodies = &body->next;
 	return TRUE;
 }
 
 static int parse_arg(struct client *client, struct imap_arg *arg,
-		     struct mail_fetch_data *data)
+		     enum mail_fetch_field *fetch_data,
+		     enum imap_fetch_field *imap_data,
+		     struct imap_fetch_body_data ***bodies)
 {
 	char *item;
 
@@ -111,10 +189,10 @@
 	switch (*item) {
 	case 'A':
 		if (strcmp(item, "ALL") == 0) {
-			data->flags = TRUE;
-			data->internaldate = TRUE;
-			data->rfc822_size = TRUE;
-			data->envelope = TRUE;
+			*fetch_data |= MAIL_FETCH_FLAGS |
+				MAIL_FETCH_RECEIVED_DATE |
+				MAIL_FETCH_SIZE |
+				MAIL_FETCH_IMAP_ENVELOPE;
 		} else
 			item = NULL;
 		break;
@@ -128,46 +206,48 @@
 
 		if (*item == '\0') {
 			/* BODY */
-			data->body = TRUE;
+			*fetch_data |= MAIL_FETCH_IMAP_BODY;
 		} else if (*item == '[') {
 			/* BODY[...] */
-			if (!parse_body_section(client, item+1, data, FALSE))
+			if (!parse_body_section(client, item+1, FALSE,
+						fetch_data, bodies))
 				return FALSE;
 		} else if (strncmp(item, ".PEEK[", 6) == 0) {
 			/* BODY.PEEK[..] */
-			if (!parse_body_section(client, item+6, data, TRUE))
+			if (!parse_body_section(client, item+6, TRUE,
+						fetch_data, bodies))
 				return FALSE;
 		} else if (strcmp(item, "STRUCTURE") == 0) {
 			/* BODYSTRUCTURE */
-			data->bodystructure = TRUE;
+			*fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
 		} else
 			item = NULL;
 		break;
 	case 'E':
 		if (strcmp(item, "ENVELOPE") == 0)
-			data->envelope = TRUE;
+			*fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
 		else
 			item = NULL;
 		break;
 	case 'F':
 		if (strcmp(item, "FLAGS") == 0)
-			data->flags = TRUE;
+			*fetch_data |= MAIL_FETCH_FLAGS;
 		else if (strcmp(item, "FAST") == 0) {
-			data->flags = TRUE;
-			data->internaldate = TRUE;
-			data->rfc822_size = TRUE;
+			*fetch_data |= MAIL_FETCH_FLAGS |
+				MAIL_FETCH_RECEIVED_DATE |
+				MAIL_FETCH_SIZE;
 		} else if (strcmp(item, "FULL") == 0) {
-			data->flags = TRUE;
-			data->internaldate = TRUE;
-			data->rfc822_size = TRUE;
-			data->envelope = TRUE;
-			data->body = TRUE;
+			*fetch_data |= MAIL_FETCH_FLAGS |
+				MAIL_FETCH_RECEIVED_DATE |
+				MAIL_FETCH_SIZE |
+				MAIL_FETCH_IMAP_ENVELOPE |
+				MAIL_FETCH_IMAP_BODY;
 		} else
 			item = NULL;
 		break;
 	case 'I':
 		if (strcmp(item, "INTERNALDATE") == 0)
-			data->internaldate = TRUE;
+			*fetch_data |= MAIL_FETCH_RECEIVED_DATE;
 		else
 			item = NULL;
 		break;
@@ -181,7 +261,9 @@
 
 		if (*item == '\0') {
 			/* RFC822 */
-			data->rfc822 = TRUE;
+			*fetch_data |= MAIL_FETCH_STREAM_HEADER |
+				MAIL_FETCH_STREAM_BODY;
+			*imap_data |= IMAP_FETCH_RFC822;
 			break;
 		}
 
@@ -192,18 +274,20 @@
 		}
 		item++;
 
-		if (strcmp(item, "HEADER") == 0)
-			data->rfc822_header = TRUE;
-		else if (strcmp(item, "SIZE") == 0)
-			data->rfc822_size = TRUE;
-		else if (strcmp(item, "TEXT") == 0)
-			data->rfc822_text = TRUE;
+		if (strcmp(item, "HEADER") == 0) {
+			*fetch_data |= MAIL_FETCH_STREAM_HEADER;
+			*imap_data |= IMAP_FETCH_RFC822_HEADER;
+		} else if (strcmp(item, "TEXT") == 0) {
+			*fetch_data |= MAIL_FETCH_STREAM_BODY;
+			*imap_data |= IMAP_FETCH_RFC822_TEXT;
+		} else if (strcmp(item, "SIZE") == 0)
+			*fetch_data |= MAIL_FETCH_SIZE;
 		else
 			item = NULL;
 		break;
 	case 'U':
 		if (strcmp(item, "UID") == 0)
-			data->uid = TRUE;
+			*imap_data |= IMAP_FETCH_UID;
 		else
 			item = NULL;
 		break;
@@ -225,9 +309,11 @@
 int cmd_fetch(struct client *client)
 {
 	struct imap_arg *args, *listargs;
-	struct mail_fetch_data data;
+	enum mail_fetch_field fetch_data;
+	enum imap_fetch_field imap_data;
+	struct imap_fetch_body_data *bodies, **bodies_p;
 	const char *messageset;
-	int all_found;
+	int ret;
 
 	if (!client_read_args(client, 2, 0, &args))
 		return FALSE;
@@ -243,33 +329,29 @@
 	}
 
 	/* parse items argument */
-	memset(&data, 0, sizeof(struct mail_fetch_data));
+	fetch_data = 0; imap_data = 0; bodies = NULL; bodies_p = &bodies;
 	if (args[1].type == IMAP_ARG_ATOM) {
-		if (!parse_arg(client, &args[1], &data))
+		if (!parse_arg(client, &args[1], &fetch_data,
+			       &imap_data, &bodies_p))
 			return TRUE;
 	} else {
 		listargs = IMAP_ARG_LIST(&args[1])->args;
 		while (listargs->type != IMAP_ARG_EOL) {
-			if (!parse_arg(client, listargs, &data))
+			if (!parse_arg(client, listargs, &fetch_data,
+				       &imap_data, &bodies_p))
 				return TRUE;
 
 			listargs++;
 		}
 	}
 
-	data.messageset = messageset;
-	data.uidset = client->cmd_uid;
-	if (data.uidset)
-                data.uid = TRUE;
-
-	/* fetch it */
-	if (client->mailbox->fetch(client->mailbox, &data,
-				   client->output, &all_found)) {
+	ret = imap_fetch(client, fetch_data, imap_data,
+			 bodies, messageset, client->cmd_uid);
+	if (ret >= 0) {
 		/* NOTE: syncing isn't allowed here */
                 client_sync_without_expunges(client);
-		client_send_tagline(client, all_found ? "OK Fetch completed." :
-				    "NO Some of the requested messages "
-				    "no longer exist.");
+		client_send_tagline(client, ret > 0 ? "OK Fetch completed." :
+			"NO Some of the requested messages no longer exist.");
 	} else {
 		client_send_storage_error(client);
 	}
--- a/src/imap/cmd-search.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/cmd-search.c	Mon Jan 20 16:52:51 2003 +0200
@@ -1,8 +1,50 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
+#include "ostream.h"
+#include "str.h"
 #include "commands.h"
-#include "mail-search.h"
+#include "imap-search.h"
+
+#define STRBUF_SIZE 1024
+
+static int imap_search(struct client *client, const char *charset,
+		       struct mail_search_arg *sargs)
+{
+        struct mail_search_context *ctx;
+	const struct mail *mail;
+	string_t *str;
+	int ret, uid, first = TRUE;
+
+	str = t_str_new(STRBUF_SIZE);
+	uid = client->cmd_uid;
+
+	ctx = client->mailbox->search_init(client->mailbox, charset, sargs,
+					   NULL, 0, NULL);
+	if (ctx == NULL)
+		return FALSE;
+
+	str_append(str, "* SEARCH");
+	while ((mail = client->mailbox->search_next(ctx)) != NULL) {
+		if (str_len(str) >= STRBUF_SIZE-MAX_INT_STRLEN) {
+			/* flush */
+			o_stream_send(client->output,
+				      str_data(str), str_len(str));
+			str_truncate(str, 0);
+			first = FALSE;
+		}
+
+		str_printfa(str, " %u", uid ? mail->uid : mail->seq);
+	}
+
+	ret = client->mailbox->search_deinit(ctx);
+
+	if (!first || ret) {
+		str_append(str, "\r\n");
+		o_stream_send(client->output, str_data(str), str_len(str));
+	}
+	return ret;
+}
 
 int cmd_search(struct client *client)
 {
@@ -44,23 +86,19 @@
 
 	pool = pool_alloconly_create("mail_search_args", 2048);
 
-	sargs = mail_search_args_build(pool, args, &error);
+	sargs = imap_search_args_build(pool, args, &error);
 	if (sargs == NULL) {
 		/* error in search arguments */
 		client_send_tagline(client, t_strconcat("NO ", error, NULL));
+	} else if (imap_search(client, charset, sargs)) {
+		/* NOTE: syncing is allowed when returning UIDs */
+		if (client->cmd_uid)
+			client_sync_full(client);
+		else
+			client_sync_without_expunges(client);
+		client_send_tagline(client, "OK Search completed.");
 	} else {
-		if (client->mailbox->search(client->mailbox, charset,
-					    sargs, NULL, MAIL_THREAD_NONE,
-					    client->output, client->cmd_uid)) {
-			/* NOTE: syncing is allowed when returning UIDs */
-			if (client->cmd_uid)
-				client_sync_full(client);
-			else
-				client_sync_without_expunges(client);
-			client_send_tagline(client, "OK Search completed.");
-		} else {
-			client_send_storage_error(client);
-		}
+		client_send_storage_error(client);
 	}
 
 	pool_unref(pool);
--- a/src/imap/cmd-sort.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/cmd-sort.c	Mon Jan 20 16:52:51 2003 +0200
@@ -3,8 +3,8 @@
 #include "common.h"
 #include "buffer.h"
 #include "commands.h"
-#include "mail-search.h"
-#include "mail-sort.h"
+#include "imap-search.h"
+#include "imap-sort.h"
 
 struct sort_name {
 	enum mail_sort_type type;
@@ -110,23 +110,19 @@
 
 	pool = pool_alloconly_create("mail_search_args", 2048);
 
-	sargs = mail_search_args_build(pool, args, &error);
+	sargs = imap_search_args_build(pool, args, &error);
 	if (sargs == NULL) {
 		/* error in search arguments */
 		client_send_tagline(client, t_strconcat("NO ", error, NULL));
+	} else if (imap_sort(client, charset, sargs, sorting)) {
+		/* NOTE: syncing is allowed when returning UIDs */
+		if (client->cmd_uid)
+			client_sync_full(client);
+		else
+			client_sync_without_expunges(client);
+		client_send_tagline(client, "OK Sort completed.");
 	} else {
-		if (client->mailbox->search(client->mailbox, charset,
-					    sargs, sorting, MAIL_THREAD_NONE,
-					    client->output, client->cmd_uid)) {
-			/* NOTE: syncing is allowed when returning UIDs */
-			if (client->cmd_uid)
-				client_sync_full(client);
-			else
-				client_sync_without_expunges(client);
-			client_send_tagline(client, "OK Search completed.");
-		} else {
-			client_send_storage_error(client);
-		}
+		client_send_storage_error(client);
 	}
 
 	pool_unref(pool);
--- a/src/imap/cmd-store.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/cmd-store.c	Mon Jan 20 16:52:51 2003 +0200
@@ -35,9 +35,8 @@
 int cmd_store(struct client *client)
 {
 	struct imap_arg *args;
-	enum mail_flags flags;
+	struct mail_full_flags flags;
 	enum modify_type modify_type;
-	const char *custflags[MAIL_CUSTOM_FLAGS_COUNT];
 	const char *messageset, *item;
 	int silent, all_found;
 
@@ -62,19 +61,17 @@
 	if (args[2].type == IMAP_ARG_LIST) {
 		if (!client_parse_mail_flags(client,
 					     IMAP_ARG_LIST(&args[2])->args,
-					     IMAP_ARG_LIST(&args[2])->size,
-					     &flags, custflags))
+					     &flags))
 			return TRUE;
 	} else {
-		if (!client_parse_mail_flags(client, &args[2], 1,
-					     &flags, custflags))
+		if (!client_parse_mail_flags(client, args+2, &flags))
 			return TRUE;
 	}
 
 	/* and update the flags */
 	client->sync_flags_send_uid = client->cmd_uid;
 	if (client->mailbox->update_flags(client->mailbox, messageset,
-					  client->cmd_uid, flags, custflags,
+					  client->cmd_uid, &flags,
 					  modify_type, !silent, &all_found)) {
 		/* NOTE: syncing isn't allowed here */
 		client_sync_without_expunges(client);
--- a/src/imap/cmd-thread.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/cmd-thread.c	Mon Jan 20 16:52:51 2003 +0200
@@ -3,8 +3,8 @@
 #include "common.h"
 #include "buffer.h"
 #include "commands.h"
-#include "mail-search.h"
-#include "mail-sort.h"
+#include "imap-search.h"
+#include "imap-thread.h"
 
 int cmd_thread(struct client *client)
 {
@@ -58,23 +58,19 @@
 
 	pool = pool_alloconly_create("mail_search_args", 2048);
 
-	sargs = mail_search_args_build(pool, args, &error);
+	sargs = imap_search_args_build(pool, args, &error);
 	if (sargs == NULL) {
 		/* error in search arguments */
 		client_send_tagline(client, t_strconcat("NO ", error, NULL));
+	} else if (imap_thread(client, charset, sargs, threading)) {
+		/* NOTE: syncing is allowed when returning UIDs */
+		if (client->cmd_uid)
+			client_sync_full(client);
+		else
+			client_sync_without_expunges(client);
+		client_send_tagline(client, "OK Search completed.");
 	} else {
-		if (client->mailbox->search(client->mailbox, charset,
-					    sargs, NULL, threading,
-					    client->output, client->cmd_uid)) {
-			/* NOTE: syncing is allowed when returning UIDs */
-			if (client->cmd_uid)
-				client_sync_full(client);
-			else
-				client_sync_without_expunges(client);
-			client_send_tagline(client, "OK Search completed.");
-		} else {
-			client_send_storage_error(client);
-		}
+		client_send_storage_error(client);
 	}
 
 	pool_unref(pool);
--- a/src/imap/commands-util.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/commands-util.c	Mon Jan 20 16:52:51 2003 +0200
@@ -122,37 +122,39 @@
 }
 
 int client_parse_mail_flags(struct client *client, struct imap_arg *args,
-			    size_t args_count, enum mail_flags *flags,
-			    const char *custflags[MAIL_CUSTOM_FLAGS_COUNT])
+			    struct mail_full_flags *flags)
 {
+	/* @UNSAFE */
 	char *atom;
-	size_t pos;
-	int i, custpos;
+	size_t max_flags, flag_pos, i;
+
+	max_flags = MAIL_CUSTOM_FLAGS_COUNT;
 
-	memset(custflags, 0, sizeof(const char *) * MAIL_CUSTOM_FLAGS_COUNT);
+	memset(flags, 0, sizeof(*flags));
+	flags->custom_flags = t_new(const char *, flags->custom_flags_count);
 
-	*flags = 0; custpos = 0;
-	for (pos = 0; pos < args_count; pos++) {
-		if (args[pos].type != IMAP_ARG_ATOM) {
+	flag_pos = 0;
+	while (args->type != IMAP_ARG_EOL) {
+		if (args->type != IMAP_ARG_ATOM) {
 			client_send_command_error(client,
 				"Flags list contains non-atoms.");
 			return FALSE;
 		}
 
-		atom = IMAP_ARG_STR(&args[pos]);
+		atom = IMAP_ARG_STR(args);
 		if (*atom == '\\') {
 			/* system flag */
 			str_ucase(atom);
 			if (strcmp(atom, "\\ANSWERED") == 0)
-				*flags |= MAIL_ANSWERED;
+				flags->flags |= MAIL_ANSWERED;
 			else if (strcmp(atom, "\\FLAGGED") == 0)
-				*flags |= MAIL_FLAGGED;
+				flags->flags |= MAIL_FLAGGED;
 			else if (strcmp(atom, "\\DELETED") == 0)
-				*flags |= MAIL_DELETED;
+				flags->flags |= MAIL_DELETED;
 			else if (strcmp(atom, "\\SEEN") == 0)
-				*flags |= MAIL_SEEN;
+				flags->flags |= MAIL_SEEN;
 			else if (strcmp(atom, "\\DRAFT") == 0)
-				*flags |= MAIL_DRAFT;
+				flags->flags |= MAIL_DRAFT;
 			else {
 				client_send_tagline(client, t_strconcat(
 					"BAD Invalid system flag ",
@@ -161,26 +163,30 @@
 			}
 		} else {
 			/* custom flag - first make sure it's not a duplicate */
-			for (i = 0; i < custpos; i++) {
-				if (strcasecmp(custflags[i], atom) == 0)
+			for (i = 0; i < flag_pos; i++) {
+				if (strcasecmp(flags->custom_flags[i],
+					       atom) == 0)
 					break;
 			}
 
-			if (i == MAIL_CUSTOM_FLAGS_COUNT) {
+			if (i == max_flags) {
 				client_send_tagline(client,
 					"Maximum number of different custom "
 					"flags exceeded");
 				return FALSE;
 			}
 
-			if (i == custpos) {
-				*flags |= 1 << (custpos +
-						MAIL_CUSTOM_FLAG_1_BIT);
-				custflags[custpos++] = atom;
+			if (i == flags->custom_flags_count) {
+				flags->flags |= 1 << (flag_pos +
+						      MAIL_CUSTOM_FLAG_1_BIT);
+				flags->custom_flags[flag_pos++] = atom;
 			}
 		}
+
+		args++;
 	}
 
+	flags->custom_flags_count = flag_pos;
 	return TRUE;
 }
 
--- a/src/imap/commands-util.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/imap/commands-util.h	Mon Jan 20 16:52:51 2003 +0200
@@ -24,12 +24,10 @@
 /* Send last mail storage error message to client. */
 void client_send_storage_error(struct client *client);
 
-/* Parse flags, stores custom flag names into custflags[]. The names point to
-   strings in ImapArgList. Returns TRUE if successful, if not sends an error
-   message to client. */
+/* Parse flags. Returns TRUE if successful, if not sends an error message to
+   client. */
 int client_parse_mail_flags(struct client *client, struct imap_arg *args,
-			    size_t args_count, enum mail_flags *flags,
-			    const char *custflags[MAIL_CUSTOM_FLAGS_COUNT]);
+			    struct mail_full_flags *flags);
 
 /* Send FLAGS + PERMANENTFLAGS to client. */
 void client_send_mailbox_flags(struct client *client, struct mailbox *box,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-fetch-body-section.c	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,461 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "message-parser.h"
+#include "message-send.h"
+#include "mail-storage.h"
+#include "imap-fetch.h"
+
+#include <ctype.h>
+#include <unistd.h>
+
+/* For FETCH[HEADER.FIELDS*] we need to modify the header data before sending
+   it. We can either save it in memory and then send it, or we can parse it
+   twice, first calculating the size and then send it. This value specifies
+   the maximum amount of memory we allow to allocate before using
+   double-parsing. */
+#define MAX_HEADER_BUFFER_SIZE (32*1024)
+
+#define UNSIGNED_CRLF (const unsigned char *) "\r\n"
+
+struct fetch_header_field_context {
+	string_t *dest;
+	struct ostream *output;
+	uoff_t dest_size;
+
+	uoff_t skip, max_size;
+	const char *const *fields;
+	int (*match_func) (const char *const *, const unsigned char *, size_t);
+};
+
+/* fetch BODY[] or BODY[TEXT] */
+static int fetch_body(struct imap_fetch_context *ctx,
+		      const struct imap_fetch_body_data *body,
+		      struct mail *mail, int fetch_header)
+{
+	struct message_size hdr_size, body_size;
+	struct istream *stream;
+	const char *str;
+
+	stream = mail->get_stream(mail, &hdr_size, &body_size);
+	if (stream == NULL)
+		return FALSE;
+
+	if (!fetch_header)
+		i_stream_seek(stream, hdr_size.physical_size);
+	else
+		message_size_add(&body_size, &hdr_size);
+
+	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
+			      ctx->prefix, body_size.virtual_size);
+	if (o_stream_send_str(ctx->output, str) < 0)
+		return FALSE;
+
+	/* FIXME: SLOW! we need some cache for this. */
+	return message_send(ctx->output, stream, &body_size,
+			    body->skip, body->max_size);
+}
+
+static const char **get_fields_array(const char *fields)
+{
+	const char **field_list, **field;
+
+	while (*fields == ' ')
+		fields++;
+	if (*fields == '(')
+		fields++;
+
+	field_list = t_strsplit(fields, " )");
+
+	/* array ends at ")" element */
+	for (field = field_list; *field != NULL; field++) {
+		if (strcmp(*field, ")") == 0)
+			*field = NULL;
+	}
+
+	return field_list;
+}
+
+static int header_match(const char *const *fields,
+			const unsigned char *name, size_t size)
+{
+	const unsigned char *name_start, *name_end;
+	const char *field;
+
+	if (size == 0)
+		return FALSE;
+
+	name_start = name;
+	name_end = name + size;
+
+	for (; *fields != NULL; fields++) {
+		field = *fields;
+		if (*field == '\0')
+			continue;
+
+		for (name = name_start; name != name_end; name++) {
+			/* field has been uppercased long time ago while
+			   parsing FETCH command */
+			if (i_toupper(*name) != *field)
+				break;
+
+			field++;
+			if (*field == '\0') {
+				if (name+1 == name_end)
+					return TRUE;
+				break;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+static int header_match_not(const char *const *fields,
+			    const unsigned char *name, size_t size)
+{
+	return !header_match(fields, name, size);
+}
+
+static int header_match_mime(const char *const *fields __attr_unused__,
+			     const unsigned char *name, size_t size)
+{
+	if (size > 8 && memcasecmp(name, "Content-", 8) == 0)
+		return TRUE;
+
+	if (size == 12 && memcasecmp(name, "Mime-Version", 12) == 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+static int fetch_header_append(struct fetch_header_field_context *ctx,
+			       const unsigned char *str, size_t size)
+{
+	if (ctx->skip > 0) {
+		if (ctx->skip >= size) {
+			ctx->skip -= size;
+			return TRUE;
+		}
+
+		str += ctx->skip;
+		size -= ctx->skip;
+		ctx->skip = 0;
+	}
+
+	if (ctx->dest_size + size > ctx->max_size) {
+		i_assert(ctx->dest_size <= ctx->max_size);
+		size = ctx->max_size - ctx->dest_size;
+	}
+
+	if (ctx->dest != NULL)
+		str_append_n(ctx->dest, str, size);
+	ctx->dest_size += size;
+
+	if (ctx->output != NULL) {
+		if (o_stream_send(ctx->output, str, size) < 0)
+			return FALSE;
+	}
+	return ctx->dest_size < ctx->max_size;
+}
+
+static void fetch_header_field(struct message_part *part __attr_unused__,
+			       const unsigned char *name, size_t name_len,
+			       const unsigned char *value __attr_unused__,
+			       size_t value_len __attr_unused__,
+			       void *context)
+{
+	struct fetch_header_field_context *ctx = context;
+	const unsigned char *field_start, *field_end, *cr, *p;
+
+	/* see if we want this field. */
+	if (!ctx->match_func(ctx->fields, name, name_len) || name_len == 0)
+		return;
+
+	/* add the field, inserting CRs when needed. FIXME: is this too
+	   kludgy? we assume name continues with ": value". but otherwise
+	   we wouldn't reply with correct LWSP around ":". */
+	field_start = name;
+	field_end = value + value_len;
+
+	cr = NULL;
+	for (p = field_start; p != field_end; p++) {
+		if (*p == '\r')
+			cr = p;
+		else if (*p == '\n' && cr != p-1) {
+			/* missing CR */
+			if (!fetch_header_append(ctx, field_start,
+						 (size_t) (p-field_start)))
+				return;
+			if (!fetch_header_append(ctx, UNSIGNED_CRLF, 2))
+				return;
+
+			field_start = p+1;
+		}
+	}
+
+	if (field_start != field_end) {
+		if (!fetch_header_append(ctx, field_start,
+					 (size_t) (field_end-field_start)))
+			return;
+	}
+
+	(void)fetch_header_append(ctx, UNSIGNED_CRLF, 2);
+}
+
+static int fetch_header_fields(struct istream *input, const char *section,
+			       struct fetch_header_field_context *ctx)
+{
+	if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
+		ctx->fields = get_fields_array(section + 14);
+		ctx->match_func = header_match;
+	} else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
+		ctx->fields = get_fields_array(section + 18);
+		ctx->match_func = header_match_not;
+	} else if (strcmp(section, "MIME") == 0) {
+		/* Mime-Version + Content-* fields */
+		ctx->match_func = header_match_mime;
+	} else {
+		i_warning("BUG: Accepted invalid section from user: '%s'",
+			  section);
+		return FALSE;
+	}
+
+	ctx->dest_size = 0;
+	message_parse_header(NULL, input, NULL, fetch_header_field, ctx);
+
+	/* FIXME: The blank line must not be filtered, says RFC. However, we
+	   shouldn't add it if it wasn't there in the first place. Not very
+	   easy to know currently so we'll just do it always, it'll be present
+	   in all sane messages anyway.. */
+	(void)fetch_header_append(ctx, UNSIGNED_CRLF, 2);
+
+	i_assert(ctx->dest_size <= ctx->max_size);
+	i_assert(ctx->dest == NULL || str_len(ctx->dest) == ctx->dest_size);
+	return TRUE;
+}
+
+/* fetch wanted headers from given data */
+static int fetch_header_from(struct imap_fetch_context *ctx,
+			     struct istream *input,
+			     const struct message_size *size,
+			     const struct imap_fetch_body_data *body)
+{
+	struct fetch_header_field_context hdr_ctx;
+	const char *str;
+	uoff_t start_offset;
+	int failed;
+
+	/* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
+
+	if (strcmp(body->section, "HEADER") == 0) {
+		/* all headers */
+		str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
+				      ctx->prefix, size->virtual_size);
+		if (o_stream_send_str(ctx->output, str) < 0)
+			return FALSE;
+		return message_send(ctx->output, input, size,
+				    body->skip, body->max_size);
+	}
+
+	/* partial headers - copy the wanted fields into memory, inserting
+	   missing CRs on the way. If the header is too large, calculate 
+	   the size first and then send the data directly to output stream. */
+
+	memset(&hdr_ctx, 0, sizeof(hdr_ctx));
+	hdr_ctx.skip = body->skip;
+	hdr_ctx.max_size = body->max_size;
+
+	failed = FALSE;
+	start_offset = input->v_offset;
+
+	t_push();
+
+	/* first pass, we need at least the size */
+	if (size->virtual_size > MAX_HEADER_BUFFER_SIZE &&
+	    body->max_size > MAX_HEADER_BUFFER_SIZE) {
+		if (!fetch_header_fields(input, body->section, &hdr_ctx))
+			failed = TRUE;
+
+		i_assert(hdr_ctx.dest_size <= size->virtual_size);
+	} else {
+		hdr_ctx.dest = t_str_new(size->virtual_size < 8192 ?
+					 size->virtual_size : 8192);
+		if (!fetch_header_fields(input, body->section, &hdr_ctx))
+			failed = TRUE;
+	}
+
+	if (!failed) {
+		str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
+				      ctx->prefix, hdr_ctx.dest_size);
+		if (o_stream_send_str(ctx->output, str) < 0)
+			failed = TRUE;
+	}
+
+	if (!failed) {
+		if (hdr_ctx.dest == NULL) {
+			/* second pass, write the data to output stream */
+			uoff_t first_size = hdr_ctx.dest_size;
+
+			hdr_ctx.output = ctx->output;
+			i_stream_seek(input, start_offset);
+
+			if (!failed &&
+			    !fetch_header_fields(input, body->section,
+						 &hdr_ctx))
+				failed = TRUE;
+
+			i_assert(first_size == hdr_ctx.dest_size);
+		} else {
+			if (o_stream_send(ctx->output, str_data(hdr_ctx.dest),
+					  str_len(hdr_ctx.dest)) < 0)
+				failed = TRUE;
+		}
+	}
+
+	t_pop();
+	return !failed;
+}
+
+static int fetch_header(struct imap_fetch_context *ctx, struct mail *mail,
+			const struct imap_fetch_body_data *body)
+{
+	struct istream *stream;
+	struct message_size hdr_size;
+
+	stream = mail->get_stream(mail, &hdr_size, NULL);
+	if (stream == NULL)
+		return FALSE;
+
+	return fetch_header_from(ctx, stream, &hdr_size, body);
+}
+
+/* Find message_part for section (eg. 1.3.4) */
+static const struct message_part *
+part_find(struct mail *mail, const struct imap_fetch_body_data *body,
+	  const char **section)
+{
+	const struct message_part *part;
+	const char *path;
+	unsigned int num;
+
+	part = mail->get_parts(mail);
+	if (part == NULL)
+		return NULL;
+
+	path = body->section;
+	while (*path >= '0' && *path <= '9' && part != NULL) {
+		/* get part number */
+		num = 0;
+		while (*path != '\0' && *path != '.') {
+			if (*path < '0' || *path > '9')
+				return NULL;
+			num = num*10 + (*path - '0');
+			path++;
+		}
+
+		if (*path == '.')
+			path++;
+
+		if (part->flags & MESSAGE_PART_FLAG_MULTIPART) {
+			/* find the part */
+			part = part->children;
+			for (; num > 1 && part != NULL; num--)
+				part = part->next;
+		} else {
+			/* only 1 allowed with non-multipart messages */
+			if (num != 1)
+				return NULL;
+		}
+
+		if (part != NULL &&
+		    (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822)) {
+			/* skip the message/rfc822 part */
+			part = part->children;
+		}
+	}
+
+	*section = path;
+	return part;
+}
+
+/* fetch BODY[1.2] or BODY[1.2.TEXT] */
+static int fetch_part_body(struct imap_fetch_context *ctx,
+			   struct istream *stream,
+			   const struct imap_fetch_body_data *body,
+			   const struct message_part *part)
+{
+	const char *str;
+
+	/* jump to beginning of part body */
+	i_stream_seek(stream, part->physical_pos +
+		      part->header_size.physical_size);
+
+	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
+			      ctx->prefix, part->body_size.virtual_size);
+	if (o_stream_send_str(ctx->output, str) < 0)
+		return FALSE;
+
+	/* FIXME: potential performance problem with big messages:
+	   FETCH BODY[1]<100000..1024>, hopefully no clients do this */
+	return message_send(ctx->output, stream, &part->body_size,
+			    body->skip, body->max_size);
+}
+
+static int fetch_part(struct imap_fetch_context *ctx, struct mail *mail,
+		      const struct imap_fetch_body_data *body)
+{
+	struct istream *stream;
+	const struct message_part *part;
+	const char *section;
+
+	part = part_find(mail, body, &section);
+	if (part == NULL)
+		return FALSE;
+
+	stream = mail->get_stream(mail, NULL, NULL);
+	if (stream == NULL)
+		return FALSE;
+
+	if (*section == '\0' || strcmp(section, "TEXT") == 0)
+		return fetch_part_body(ctx, stream, body, part);
+
+	if (strncmp(section, "HEADER", 6) == 0 ||
+	    strcmp(section, "MIME") == 0) {
+		i_stream_seek(stream, part->physical_pos);
+		return fetch_header_from(ctx, stream, &part->header_size, body);
+	}
+
+	i_warning("BUG: Accepted invalid section from user: '%s'",
+		  body->section);
+	return FALSE;
+}
+
+int imap_fetch_body_section(struct imap_fetch_context *ctx,
+			    const struct imap_fetch_body_data *body,
+			    struct mail *mail)
+{
+	ctx->prefix = !body->skip_set ?
+		t_strdup_printf(" BODY[%s]", body->section) :
+		t_strdup_printf(" BODY[%s]<%"PRIuUOFF_T">",
+				body->section, body->skip);
+	if (ctx->first) {
+		ctx->prefix++; ctx->first = FALSE;
+	}
+
+	if (*body->section == '\0')
+		return fetch_body(ctx, body, mail, TRUE);
+	if (strcmp(body->section, "TEXT") == 0)
+		return fetch_body(ctx, body, mail, FALSE);
+	if (strncmp(body->section, "HEADER", 6) == 0)
+		return fetch_header(ctx, mail, body);
+	if (*body->section >= '0' && *body->section <= '9')
+		return fetch_part(ctx, mail, body);
+
+	i_warning("BUG: Accepted invalid section from user: '%s'",
+		  body->section);
+	return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-fetch.c	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,279 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "message-send.h"
+#include "message-size.h"
+#include "imap-date.h"
+#include "commands.h"
+#include "imap-fetch.h"
+
+#include <unistd.h>
+
+static void fetch_uid(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	str_printfa(ctx->str, "UID %u ", mail->uid);
+}
+
+static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	const struct mail_full_flags *flags;
+
+	flags = mail->get_flags(mail);
+	if (flags == NULL)
+		return FALSE;
+
+	str_printfa(ctx->str, "FLAGS (%s) ",
+		    imap_write_flags(flags->flags, flags->custom_flags,
+				     flags->custom_flags_count));
+	return TRUE;
+}
+
+static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	time_t time;
+
+	time = mail->get_received_date(mail);
+	if (time == (time_t)-1)
+		return FALSE;
+
+	str_printfa(ctx->str, "INTERNALDATE \"%s\" ", imap_to_datetime(time));
+	return TRUE;
+}
+
+static int fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail->get_size(mail);
+	if (size == (uoff_t)-1)
+		return FALSE;
+
+	str_printfa(ctx->str, "RFC822.SIZE %"PRIuUOFF_T" ", size);
+	return TRUE;
+}
+
+static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	const char *body;
+
+	body = mail->get_special(mail, MAIL_FETCH_IMAP_BODY);
+	if (body == NULL)
+		return FALSE;
+
+	str_printfa(ctx->str, "BODY (%s) ", body);
+	return TRUE;
+}
+
+static int fetch_bodystructure(struct imap_fetch_context *ctx,
+			       struct mail *mail)
+{
+	const char *bodystructure;
+
+	bodystructure = mail->get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE);
+	if (bodystructure == NULL)
+		return FALSE;
+
+	str_printfa(ctx->str, "BODYSTRUCTURE (%s) ", bodystructure);
+	return TRUE;
+}
+
+static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	const char *envelope;
+
+	envelope = mail->get_special(mail, MAIL_FETCH_IMAP_ENVELOPE);
+	if (envelope == NULL)
+		return FALSE;
+
+	str_printfa(ctx->str, "ENVELOPE (%s) ", envelope);
+	return TRUE;
+}
+
+static int fetch_send_rfc822(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	struct message_size hdr_size, body_size;
+	struct istream *stream;
+	const char *str;
+
+	stream = mail->get_stream(mail, &hdr_size, &body_size);
+	if (stream == NULL)
+		return FALSE;
+
+	message_size_add(&body_size, &hdr_size);
+
+	str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n",
+			      body_size.virtual_size);
+	if (ctx->first) {
+		str++; ctx->first = FALSE;
+	}
+	if (o_stream_send_str(ctx->output, str) < 0)
+		return FALSE;
+
+	return message_send(ctx->output, stream, &body_size, 0, (uoff_t)-1);
+}
+
+static int fetch_send_rfc822_header(struct imap_fetch_context *ctx,
+				    struct mail *mail)
+{
+	struct message_size hdr_size;
+	struct istream *stream;
+	const char *str;
+
+	stream = mail->get_stream(mail, &hdr_size, NULL);
+	if (stream == NULL)
+		return FALSE;
+
+	str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n",
+			      hdr_size.virtual_size);
+	if (ctx->first) {
+		str++; ctx->first = FALSE;
+	}
+	if (o_stream_send_str(ctx->output, str) < 0)
+		return FALSE;
+
+	return message_send(ctx->output, stream, &hdr_size, 0, (uoff_t)-1);
+}
+
+static int fetch_send_rfc822_text(struct imap_fetch_context *ctx,
+				  struct mail *mail)
+{
+	struct message_size hdr_size, body_size;
+	struct istream *stream;
+	const char *str;
+
+	stream = mail->get_stream(mail, &hdr_size, &body_size);
+	if (stream == NULL)
+		return FALSE;
+
+	str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n",
+			      body_size.virtual_size);
+	if (ctx->first) {
+		str++; ctx->first = FALSE;
+	}
+	if (o_stream_send_str(ctx->output, str) < 0)
+		return FALSE;
+
+	i_stream_seek(stream, hdr_size.physical_size);
+	return message_send(ctx->output, stream, &body_size, 0, (uoff_t)-1);
+}
+
+static int fetch_mail(struct imap_fetch_context *ctx, struct mail *mail)
+{
+	struct imap_fetch_body_data *body;
+	size_t len, orig_len;
+	int failed, data_written;
+
+	str_truncate(ctx->str, 0);
+	str_printfa(ctx->str, "* %u FETCH (", mail->seq);
+	orig_len = str_len(ctx->str);
+
+	failed = TRUE;
+	data_written = FALSE;
+	do {
+		/* write the data into temp string */
+		if (ctx->imap_data & IMAP_FETCH_UID)
+			fetch_uid(ctx, mail);
+		if ((ctx->fetch_data & MAIL_FETCH_FLAGS) || mail->seen_updated)
+			fetch_flags(ctx, mail);
+		if (ctx->fetch_data & MAIL_FETCH_RECEIVED_DATE)
+			fetch_internaldate(ctx, mail);
+		if (ctx->fetch_data & MAIL_FETCH_SIZE)
+			fetch_rfc822_size(ctx, mail);
+		if (ctx->fetch_data & MAIL_FETCH_IMAP_BODY)
+			fetch_body(ctx, mail);
+		if (ctx->fetch_data & MAIL_FETCH_IMAP_BODYSTRUCTURE)
+			fetch_bodystructure(ctx, mail);
+		if (ctx->fetch_data & MAIL_FETCH_IMAP_ENVELOPE)
+			fetch_envelope(ctx, mail);
+
+		/* send the data written into temp string */
+		len = str_len(ctx->str);
+		ctx->first = len == orig_len;
+
+		if (!ctx->first)
+			str_truncate(ctx->str, --len);
+		if (o_stream_send(ctx->output, str_data(ctx->str), len) < 0)
+			break;
+
+		data_written = TRUE;
+
+		/* large data */
+		if (ctx->imap_data & IMAP_FETCH_RFC822)
+			if (!fetch_send_rfc822(ctx, mail))
+				break;
+		if (ctx->imap_data & IMAP_FETCH_RFC822_HEADER)
+			if (!fetch_send_rfc822_header(ctx, mail))
+				break;
+		if (ctx->imap_data & IMAP_FETCH_RFC822_TEXT)
+			if (!fetch_send_rfc822_text(ctx, mail))
+				break;
+
+		for (body = ctx->bodies; body != NULL; body = body->next) {
+			if (!imap_fetch_body_section(ctx, body, mail))
+				break;
+		}
+
+		failed = FALSE;
+	} while (0);
+
+	if (data_written) {
+		if (o_stream_send(ctx->output, ")\r\n", 3) < 0)
+			failed = TRUE;
+	}
+
+	return !failed;
+}
+
+int imap_fetch(struct client *client,
+	       enum mail_fetch_field fetch_data,
+	       enum imap_fetch_field imap_data,
+	       struct imap_fetch_body_data *bodies,
+	       const char *messageset, int uidset)
+{
+	struct imap_fetch_context ctx;
+	struct mail *mail;
+	int all_found, update_seen = FALSE;
+
+	if (!client->mailbox->readonly) {
+		/* If we have any BODY[..] sections, \Seen flag is added for
+		   all messages */
+		struct imap_fetch_body_data *body;
+
+		for (body = bodies; body != NULL; body = body->next) {
+			if (!body->peek) {
+				update_seen = TRUE;
+				break;
+			}
+		}
+
+		if (imap_data & (IMAP_FETCH_RFC822|IMAP_FETCH_RFC822_TEXT))
+			update_seen = TRUE;
+	}
+
+	memset(&ctx, 0, sizeof(ctx));
+	ctx.fetch_data = fetch_data;
+	ctx.imap_data = imap_data;
+	ctx.bodies = bodies;
+	ctx.output = client->output;
+	ctx.str = t_str_new(8192);
+
+	ctx.fetch_ctx = client->mailbox->
+		fetch_init(client->mailbox, fetch_data, &update_seen,
+			   messageset, uidset);
+	if (ctx.fetch_ctx == NULL)
+		return -1;
+
+	while ((mail = client->mailbox->fetch_next(ctx.fetch_ctx)) != NULL) {
+		if (!fetch_mail(&ctx, mail)) {
+			ctx.failed = TRUE;
+			break;
+		}
+	}
+
+	if (!client->mailbox->fetch_deinit(ctx.fetch_ctx, &all_found))
+		return -1;
+	return ctx.failed ? -1 : all_found;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-fetch.h	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,45 @@
+#ifndef __IMAP_FETCH_H
+#define __IMAP_FETCH_H
+
+enum imap_fetch_field {
+	IMAP_FETCH_UID			= 0x01,
+	IMAP_FETCH_RFC822		= 0x02,
+	IMAP_FETCH_RFC822_HEADER	= 0x04,
+	IMAP_FETCH_RFC822_TEXT		= 0x08
+};
+
+struct imap_fetch_body_data {
+	struct imap_fetch_body_data *next;
+
+	const char *section; /* NOTE: always uppercased */
+	uoff_t skip, max_size; /* if you don't want max_size,
+	                          set it to (uoff_t)-1 */
+	unsigned int skip_set:1;
+	unsigned int peek:1;
+};
+
+struct imap_fetch_context {
+	struct mail_fetch_context *fetch_ctx;
+
+	enum mail_fetch_field fetch_data;
+	enum imap_fetch_field imap_data;
+	struct imap_fetch_body_data *bodies;
+
+	string_t *str;
+	struct ostream *output;
+	const char *prefix;
+
+	int first, failed;
+};
+
+int imap_fetch(struct client *client,
+	       enum mail_fetch_field fetch_data,
+	       enum imap_fetch_field imap_data,
+	       struct imap_fetch_body_data *bodies,
+	       const char *messageset, int uidset);
+
+int imap_fetch_body_section(struct imap_fetch_context *ctx,
+			    const struct imap_fetch_body_data *body,
+			    struct mail *mail);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-search.c	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,377 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "mail-search.h"
+#include "imap-search.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;
+}
+
+#define ARG_NEW(type, value) \
+	arg_new(data, args, next_sarg, type, value)
+
+static int arg_new(struct search_build_data *data, struct imap_arg **args,
+		   struct mail_search_arg **next_sarg,
+		   enum mail_search_arg_type type, int value)
+{
+	struct mail_search_arg *sarg;
+
+	*next_sarg = sarg = search_arg_new(data->pool, type);
+	if (value == 0)
+		return TRUE;
+
+	/* first arg */
+	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;
+	}
+
+	sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
+	*args += 1;
+
+	/* second arg */
+	if (value == 2) {
+		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;
+		}
+
+                sarg->hdr_field_name = sarg->value.str;
+		sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
+		*args += 1;
+	}
+
+	return TRUE;
+}
+
+static int search_arg_build(struct search_build_data *data,
+			    struct imap_arg **args,
+			    struct mail_search_arg **next_sarg)
+{
+	struct mail_search_arg **subargs;
+	struct imap_arg *arg;
+	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) {
+		struct imap_arg *listargs = IMAP_ARG_LIST(arg)->args;
+
+		*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_ucase(str);
+
+	switch (*str) {
+	case 'A':
+		if (strcmp(str, "ANSWERED") == 0)
+			return ARG_NEW(SEARCH_ANSWERED, 0);
+		else if (strcmp(str, "ALL") == 0)
+			return ARG_NEW(SEARCH_ALL, 0);
+		break;
+	case 'B':
+		if (strcmp(str, "BODY") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_BODY, 1);
+		} else if (strcmp(str, "BEFORE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_BEFORE, 1);
+		} else if (strcmp(str, "BCC") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_BCC, 1);
+		}
+		break;
+	case 'C':
+		if (strcmp(str, "CC") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_CC, 1);
+		}
+		break;
+	case 'D':
+		if (strcmp(str, "DELETED") == 0)
+			return ARG_NEW(SEARCH_DELETED, 0);
+		else if (strcmp(str, "DRAFT") == 0)
+			return ARG_NEW(SEARCH_DRAFT, 0);
+		break;
+	case 'F':
+		if (strcmp(str, "FLAGGED") == 0)
+			return ARG_NEW(SEARCH_FLAGGED, 0);
+		else if (strcmp(str, "FROM") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_FROM, 1);
+		}
+		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 = str_ucase(IMAP_ARG_STR(*args));
+
+			if (strcmp(key, "FROM") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_FROM, 1);
+			} else if (strcmp(key, "TO") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_TO, 1);
+			} else if (strcmp(key, "CC") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_CC, 1);
+			} else if (strcmp(key, "BCC") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_BCC, 1);
+			} else if (strcmp(key, "SUBJECT") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_SUBJECT, 1);
+			} else if (strcmp(key, "IN-REPLY-TO") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_IN_REPLY_TO, 1);
+			} else if (strcmp(key, "MESSAGE-ID") == 0) {
+				*args += 1;
+				return ARG_NEW(SEARCH_MESSAGE_ID, 1);
+			} else {
+				return ARG_NEW(SEARCH_HEADER, 2);
+			}
+		}
+		break;
+	case 'K':
+		if (strcmp(str, "KEYWORD") == 0) {
+			/* <flag> */
+			return ARG_NEW(SEARCH_KEYWORD, 1);
+		}
+		break;
+	case 'L':
+		if (strcmp(str, "LARGER") == 0) {
+			/* <n> */
+			return ARG_NEW(SEARCH_LARGER, 1);
+		}
+		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_RECENT);
+			(*subargs)->next = search_arg_new(data->pool,
+							  SEARCH_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(*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(SEARCH_ON, 1);
+		} if (strcmp(str, "OLD") == 0) {
+			/* OLD == NOT RECENT */
+			if (!ARG_NEW(SEARCH_RECENT, 0))
+				return FALSE;
+
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		}
+		break;
+	case 'R':
+		if (strcmp(str, "RECENT") == 0)
+			return ARG_NEW(SEARCH_RECENT, 0);
+		break;
+	case 'S':
+		if (strcmp(str, "SEEN") == 0)
+			return ARG_NEW(SEARCH_SEEN, 0);
+		else if (strcmp(str, "SUBJECT") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_SUBJECT, 1);
+		} else if (strcmp(str, "SENTBEFORE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SENTBEFORE, 1);
+		} else if (strcmp(str, "SENTON") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SENTON, 1);
+		} else if (strcmp(str, "SENTSINCE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SENTSINCE, 1);
+		} else if (strcmp(str, "SINCE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SINCE, 1);
+		} else if (strcmp(str, "SMALLER") == 0) {
+			/* <n> */
+			return ARG_NEW(SEARCH_SMALLER, 1);
+		}
+		break;
+	case 'T':
+		if (strcmp(str, "TEXT") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_TEXT, 1);
+		} else if (strcmp(str, "TO") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_TO, 1);
+		}
+		break;
+	case 'U':
+		if (strcmp(str, "UID") == 0) {
+			/* <message set> */
+			return ARG_NEW(SEARCH_UID, 1);
+		} else if (strcmp(str, "UNANSWERED") == 0) {
+			if (!ARG_NEW(SEARCH_ANSWERED, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNDELETED") == 0) {
+			if (!ARG_NEW(SEARCH_DELETED, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNDRAFT") == 0) {
+			if (!ARG_NEW(SEARCH_DRAFT, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNFLAGGED") == 0) {
+			if (!ARG_NEW(SEARCH_FLAGGED, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNKEYWORD") == 0) {
+			if (!ARG_NEW(SEARCH_KEYWORD, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNSEEN") == 0) {
+			if (!ARG_NEW(SEARCH_SEEN, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		}
+		break;
+	default:
+		if (*str == '*' || (*str >= '0' && *str <= '9')) {
+			/* <message-set> */
+			if (!ARG_NEW(SEARCH_SET, 0))
+				return FALSE;
+
+			(*next_sarg)->value.str = str;
+			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 imap_arg *args, const char **error)
+{
+        struct search_build_data data;
+	struct mail_search_arg *first_sarg, **sargs;
+
+	data.pool = pool;
+	data.error = NULL;
+
+	/* get the first arg */
+	first_sarg = NULL; sargs = &first_sarg;
+	while (args->type != IMAP_ARG_EOL) {
+		if (!search_arg_build(&data, &args, sargs)) {
+			*error = data.error;
+			return NULL;
+		}
+		sargs = &(*sargs)->next;
+	}
+
+	*error = NULL;
+	return first_sarg;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-search.h	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,8 @@
+#ifndef __IMAP_SEARCH_H
+#define __IMAP_SEARCH_H
+
+/* Builds search arguments based on IMAP arguments. */
+struct mail_search_arg *
+imap_search_args_build(pool_t pool, struct imap_arg *args, const char **error);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-sort.c	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,690 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+/* Implementation of draft-ietf-imapext-sort-10 sorting algorithm.
+   Pretty messy code actually, adding any sort types requires care.
+   This is pretty fast however and takes only as much memory as needed to be
+   reasonably fast. */
+
+#include "common.h"
+#include "buffer.h"
+#include "hash.h"
+#include "ostream.h"
+#include "str.h"
+#include "imap-base-subject.h"
+#include "imap-sort.h"
+
+#include <stdlib.h>
+
+#define MAX_WANTED_HEADERS 10
+#define STRBUF_SIZE 1024
+
+#define IS_SORT_STRING(type) \
+	((type) == MAIL_SORT_CC || (type) == MAIL_SORT_FROM || \
+	 (type) == MAIL_SORT_SUBJECT || (type) == MAIL_SORT_TO)
+
+#define IS_SORT_TIME(type) \
+	((type) == MAIL_SORT_ARRIVAL || (type) == MAIL_SORT_DATE)
+
+struct sort_context {
+	struct mail_search_context *search_ctx;
+
+	enum mail_sort_type sort_program[MAX_SORT_PROGRAM_SIZE];
+	enum mail_sort_type common_mask, cache_mask;
+
+	struct mailbox *box;
+	struct ostream *output;
+	string_t *str;
+
+	buffer_t *sort_buffer;
+	size_t sort_element_size;
+
+	pool_t temp_pool, str_pool;
+	struct hash_table *string_table;
+
+	time_t last_arrival, last_date;
+	uoff_t last_size;
+	char *last_cc, *last_from, *last_subject, *last_to;
+
+	int written, id_is_uid;
+};
+
+static void mail_sort_input(struct sort_context *ctx, struct mail *mail);
+static void mail_sort_flush(struct sort_context *ctx);
+
+static enum mail_sort_type
+mail_sort_normalize(const enum mail_sort_type *input, buffer_t *output)
+{
+        enum mail_sort_type type, mask = 0;
+	int pos, reverse;
+
+	reverse = FALSE;
+	for (pos = 0; *input != MAIL_SORT_END; input++) {
+		if (*input == MAIL_SORT_REVERSE)
+			reverse = !reverse;
+		else {
+			if ((mask & *input) == 0) {
+				if (reverse) {
+					type = MAIL_SORT_REVERSE;
+					buffer_append(output,
+						      &type, sizeof(type));
+				}
+
+				buffer_append(output, input, sizeof(*input));
+				mask |= *input;
+			}
+
+			reverse = FALSE;
+		}
+	}
+
+	type = MAIL_SORT_END;
+	buffer_append(output, &type, sizeof(type));
+
+	return mask;
+}
+
+static enum mail_sort_type
+mail_sort_get_common_mask(const enum mail_sort_type *sort1,
+			  const enum mail_sort_type *sort2,
+			  unsigned int *count)
+{
+	enum mail_sort_type mask = 0;
+
+	*count = 0;
+	while (*sort1 == *sort2 && *sort1 != MAIL_SORT_END) {
+		if (*sort1 != MAIL_SORT_REVERSE)
+			mask |= *sort1;
+		sort1++; sort2++; (*count)++;
+	}
+
+	return mask;
+}
+
+static enum mail_fetch_field
+init_sort_elements(struct sort_context *ctx,
+		   const char *wanted_headers[MAX_WANTED_HEADERS])
+{
+	unsigned int i;
+        enum mail_fetch_field fields;
+
+	/* figure out what data we'd like to cache */
+	ctx->sort_element_size = sizeof(unsigned int);
+	ctx->cache_mask = 0;
+
+	for (i = 0; ctx->sort_program[i] != MAIL_SORT_END; i++) {
+		enum mail_sort_type type = ctx->sort_program[i];
+
+		if (IS_SORT_STRING(type)) {
+			ctx->sort_element_size += sizeof(const char *);
+
+			/* cache the second rule as well, if available */
+			if (ctx->cache_mask != 0) {
+				ctx->cache_mask |= type;
+				break;
+			}
+			ctx->cache_mask |= type;
+		} else if (IS_SORT_TIME(type)) {
+			ctx->sort_element_size += sizeof(time_t);
+			ctx->cache_mask |= type;
+			break;
+		} else if (type == MAIL_SORT_SIZE) {
+			ctx->sort_element_size += sizeof(uoff_t);
+			ctx->cache_mask |= type;
+			break;
+		}
+	}
+
+	fields = 0;
+	if (ctx->cache_mask & MAIL_SORT_ARRIVAL)
+		fields |= MAIL_FETCH_RECEIVED_DATE;
+	if (ctx->cache_mask & MAIL_SORT_DATE)
+		fields |= MAIL_FETCH_DATE;
+	if (ctx->cache_mask & MAIL_SORT_SIZE)
+		fields |= MAIL_FETCH_SIZE;
+
+	/* @UNSAFE */
+	i_assert(MAX_WANTED_HEADERS > 4);
+	i = 0;
+	if (ctx->cache_mask & MAIL_SORT_CC)
+		wanted_headers[i++] = "cc";
+	if (ctx->cache_mask & MAIL_SORT_FROM)
+		wanted_headers[i++] = "from";
+	if (ctx->cache_mask & MAIL_SORT_TO)
+		wanted_headers[i++] = "to";
+	if (ctx->cache_mask & MAIL_SORT_SUBJECT)
+		wanted_headers[i++] = "subject";
+	wanted_headers[i] = NULL;
+
+	if ((ctx->cache_mask & MAIL_SORT_CC) ||
+	    (ctx->cache_mask & MAIL_SORT_FROM) ||
+	    (ctx->cache_mask & MAIL_SORT_TO) ||
+	    (ctx->cache_mask & MAIL_SORT_SUBJECT)) {
+		ctx->str_pool = pool_alloconly_create("sort str", 8192);
+		ctx->string_table = hash_create(default_pool, ctx->str_pool,
+						0, str_hash,
+						(hash_cmp_callback_t)strcmp);
+	}
+
+	return fields;
+}
+
+static void mail_sort_deinit(struct sort_context *ctx)
+{
+	mail_sort_flush(ctx);
+
+	if (ctx->string_table != NULL)
+		hash_destroy(ctx->string_table);
+	if (ctx->str_pool != NULL)
+		pool_unref(ctx->str_pool);
+	buffer_free(ctx->sort_buffer);
+	pool_unref(ctx->temp_pool);
+
+	i_free(ctx->last_cc);
+	i_free(ctx->last_from);
+	i_free(ctx->last_subject);
+	i_free(ctx->last_to);
+}
+
+int imap_sort(struct client *client, const char *charset,
+	      struct mail_search_arg *args,
+	      const enum mail_sort_type *sort_program)
+{
+	enum mail_sort_type norm_prog[MAX_SORT_PROGRAM_SIZE];
+        enum mail_fetch_field wanted_fields;
+	const char *wanted_headers[MAX_WANTED_HEADERS];
+	struct sort_context *ctx;
+	struct mail *mail;
+	buffer_t *buf;
+	unsigned int count;
+	int ret;
+
+	ctx = t_new(struct sort_context, 1);
+
+	/* normalize sorting program */
+	buf = buffer_create_data(data_stack_pool, norm_prog, sizeof(norm_prog));
+	mail_sort_normalize(sort_program, buf);
+	memcpy(ctx->sort_program, norm_prog, sizeof(ctx->sort_program));
+
+	/* remove the common part from sort program, we already know input is
+	   sorted that much so we don't have to worry about it. */
+	if (!client->mailbox->search_get_sorting(client->mailbox, norm_prog))
+		return FALSE;
+	ctx->common_mask = mail_sort_get_common_mask(ctx->sort_program,
+						     norm_prog, &count);
+	if (count > 0) {
+		memmove(ctx->sort_program, ctx->sort_program + count,
+			sizeof(ctx->sort_program) -
+			sizeof(ctx->sort_program[0]) * count);
+	}
+
+	memset(wanted_headers, 0, sizeof(wanted_headers));
+	wanted_fields = init_sort_elements(ctx, wanted_headers);
+
+	/* initialize searching */
+	ctx->search_ctx = client->mailbox->
+		search_init(client->mailbox, charset, args, norm_prog,
+			    wanted_fields, wanted_headers);
+	if (ctx->search_ctx == NULL)
+		return FALSE;
+
+	ctx->box = client->mailbox;
+	ctx->output = client->output;
+	ctx->temp_pool = pool_alloconly_create("sort temp", 8192);
+	ctx->sort_buffer = buffer_create_dynamic(system_pool,
+						 128 * ctx->sort_element_size,
+						 (size_t)-1);
+
+	ctx->str = t_str_new(STRBUF_SIZE);
+	str_append(ctx->str, "* SORT");
+
+        ctx->id_is_uid = client->cmd_uid;
+
+	while ((mail = client->mailbox->search_next(ctx->search_ctx)) != NULL)
+		mail_sort_input(ctx, mail);
+
+	mail_sort_flush(ctx);
+	ret = client->mailbox->search_deinit(ctx->search_ctx);
+
+	if (ctx->written || ret) {
+		str_append(ctx->str, "\r\n");
+		o_stream_send(client->output, str_data(ctx->str),
+			      str_len(ctx->str));
+	}
+
+        mail_sort_deinit(ctx);
+	return ret;
+}
+
+static const char *string_table_get(struct sort_context *ctx, const char *str)
+{
+	char *value;
+
+	if (str == NULL)
+		return NULL;
+	if (*str == '\0')
+		return "";
+
+	value = hash_lookup(ctx->string_table, str);
+	if (value == NULL) {
+		value = p_strdup(ctx->str_pool, str);
+		hash_insert(ctx->string_table, value, value);
+	}
+
+	return value;
+}
+
+static void mail_sort_check_flush(struct sort_context *ctx, struct mail *mail)
+{
+	const char *str;
+	time_t t;
+	uoff_t size;
+	int changed = FALSE;
+
+	if (ctx->common_mask & MAIL_SORT_ARRIVAL) {
+		t = mail->get_received_date(mail);
+		if (t != ctx->last_arrival) {
+			ctx->last_arrival = t;
+			changed = TRUE;
+		}
+	}
+
+	if (ctx->common_mask & MAIL_SORT_CC) {
+		str = mail->get_first_mailbox(mail, "cc");
+		if (str != NULL)
+			str = str_ucase(t_strdup_noconst(str));
+
+		if (null_strcmp(str, ctx->last_cc) != 0) {
+			i_free(ctx->last_cc);
+			ctx->last_cc = i_strdup(str);
+			changed = TRUE;
+		}
+	}
+
+	if (ctx->common_mask & MAIL_SORT_DATE) {
+		t = mail->get_date(mail, NULL);
+		if (t != ctx->last_date) {
+			ctx->last_date = t;
+			changed = TRUE;
+		}
+	}
+
+	if (ctx->common_mask & MAIL_SORT_FROM) {
+		str = mail->get_first_mailbox(mail, "from");
+		if (str != NULL)
+			str = str_ucase(t_strdup_noconst(str));
+
+		if (null_strcmp(str, ctx->last_from) != 0) {
+			i_free(ctx->last_from);
+			ctx->last_from = i_strdup(str);
+			changed = TRUE;
+		}
+	}
+
+	if (ctx->common_mask & MAIL_SORT_SIZE) {
+		size = mail->get_size(mail);
+		if (size != ctx->last_size) {
+			ctx->last_size = size;
+			changed = TRUE;
+		}
+	}
+
+	if (ctx->common_mask & MAIL_SORT_SUBJECT) {
+		str = mail->get_header(mail, "subject");
+		if (str != NULL) {
+			p_clear(ctx->temp_pool);
+			str = imap_get_base_subject_cased(ctx->temp_pool,
+							  str, NULL);
+		}
+
+		if (null_strcmp(str, ctx->last_subject) != 0) {
+			i_free(ctx->last_subject);
+			ctx->last_subject = i_strdup(str);
+			changed = TRUE;
+		}
+	}
+
+	if (ctx->common_mask & MAIL_SORT_TO) {
+		str = mail->get_first_mailbox(mail, "to");
+		if (str != NULL)
+			str = str_ucase(t_strdup_noconst(str));
+
+		if (null_strcmp(str, ctx->last_to) != 0) {
+			i_free(ctx->last_to);
+			ctx->last_to = i_strdup(str);
+			changed = TRUE;
+		}
+	}
+
+	if (changed)
+		mail_sort_flush(ctx);
+}
+
+static void mail_sort_input(struct sort_context *ctx, struct mail *mail)
+{
+	/* @UNSAFE */
+	unsigned char *buf;
+	unsigned int id;
+	time_t t;
+	uoff_t size;
+	const char *str;
+	size_t pos;
+
+	t_push();
+	if (ctx->common_mask != 0)
+		mail_sort_check_flush(ctx, mail);
+
+	buf = buffer_append_space(ctx->sort_buffer, ctx->sort_element_size);
+	id = ctx->id_is_uid ? mail->uid : mail->seq;
+	memcpy(buf, &id, sizeof(id)); pos = sizeof(id);
+
+	if (ctx->cache_mask & MAIL_SORT_ARRIVAL) {
+		if (ctx->common_mask & MAIL_SORT_ARRIVAL)
+			t = ctx->last_arrival;
+		else
+			t = mail->get_received_date(mail);
+		memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t);
+	}
+
+	if (ctx->cache_mask & MAIL_SORT_DATE) {
+		if (ctx->common_mask & MAIL_SORT_DATE)
+			t = ctx->last_date;
+		else
+			t = mail->get_date(mail, NULL);
+		memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t);
+	}
+
+	if (ctx->cache_mask & MAIL_SORT_SIZE) {
+		if (ctx->common_mask & MAIL_SORT_SIZE)
+			size = ctx->last_size;
+		else
+			size = mail->get_size(mail);
+
+		memcpy(buf + pos, &size, sizeof(size)); pos += sizeof(size);
+	}
+
+	if (ctx->cache_mask & MAIL_SORT_CC) {
+		if (ctx->common_mask & MAIL_SORT_CC)
+			str = ctx->last_cc;
+		else {
+			str = mail->get_first_mailbox(mail, "cc");
+			if (str != NULL)
+				str = str_ucase(t_strdup_noconst(str));
+		}
+		str = string_table_get(ctx, str);
+
+		memcpy(buf + pos, &str, sizeof(const char *));
+		pos += sizeof(const char *);
+	}
+
+	if (ctx->cache_mask & MAIL_SORT_FROM) {
+		if (ctx->common_mask & MAIL_SORT_FROM)
+			str = ctx->last_from;
+		else {
+			str = mail->get_first_mailbox(mail, "from");
+			if (str != NULL)
+				str = str_ucase(t_strdup_noconst(str));
+		}
+		str = string_table_get(ctx, str);
+
+		memcpy(buf + pos, &str, sizeof(const char *));
+		pos += sizeof(const char *);
+	}
+
+	if (ctx->cache_mask & MAIL_SORT_TO) {
+		if (ctx->common_mask & MAIL_SORT_TO)
+			str = ctx->last_to;
+		else {
+			str = mail->get_first_mailbox(mail, "to");
+			if (str != NULL)
+				str = str_ucase(t_strdup_noconst(str));
+		}
+		str = string_table_get(ctx, str);
+
+		memcpy(buf + pos, &str, sizeof(const char *));
+		pos += sizeof(const char *);
+	}
+
+	if (ctx->cache_mask & MAIL_SORT_SUBJECT) {
+		if (ctx->common_mask & MAIL_SORT_SUBJECT)
+			str = ctx->last_subject;
+		else {
+			str = mail->get_header(mail, "subject");
+
+			if (str != NULL) {
+				p_clear(ctx->temp_pool);
+				str = imap_get_base_subject_cased(
+					ctx->temp_pool, str, NULL);
+			}
+		}
+		str = string_table_get(ctx, str);
+
+		memcpy(buf + pos, &str, sizeof(const char *));
+		pos += sizeof(const char *);
+	}
+
+	i_assert(pos == ctx->sort_element_size);
+
+	t_pop();
+}
+
+static struct sort_context *qsort_context;
+
+static struct mail *get_mail(struct sort_context *ctx, const unsigned char *buf)
+{
+	unsigned int id = *((unsigned int *) buf);
+
+	if (ctx->id_is_uid)
+		return ctx->box->fetch_uid(ctx->box, id, 0);
+	else
+		return ctx->box->fetch_seq(ctx->box, id, 0);
+
+}
+
+static time_t get_time(enum mail_sort_type type, const unsigned char *buf,
+		       struct sort_context *ctx)
+{
+	time_t t;
+
+	if ((ctx->cache_mask & type) == 0) {
+		struct mail *mail = get_mail(ctx, buf);
+
+		if (mail == NULL)
+			return 0;
+
+		switch (type) {
+		case MAIL_SORT_ARRIVAL:
+			return mail->get_received_date(mail);
+		case MAIL_SORT_DATE:
+			t = mail->get_date(mail, NULL);
+			if (t == (time_t)-1)
+				t = 0;
+			return t;
+		default:
+			i_unreached();
+			return 0;
+		}
+	}
+
+	/* use memcpy() to avoid any alignment problems */
+	memcpy(&t, buf + sizeof(unsigned int), sizeof(t));
+	return t;
+}
+
+static time_t get_uofft(enum mail_sort_type type, const unsigned char *buf,
+			struct sort_context *ctx)
+{
+	uoff_t size;
+
+	if ((ctx->cache_mask & type) == 0) {
+		struct mail *mail = get_mail(ctx, buf);
+
+		if (mail == NULL)
+			return 0;
+
+		i_assert(type == MAIL_SORT_SIZE);
+
+		return mail->get_size(mail);
+	}
+
+	/* use memcpy() to avoid any alignment problems */
+	memcpy(&size, buf + sizeof(unsigned int), sizeof(size));
+	return size;
+}
+
+static const char *get_str(enum mail_sort_type type, const unsigned char *buf,
+			   struct sort_context *ctx)
+{
+	const char *str;
+	enum mail_sort_type type2;
+	int pos;
+
+	if ((ctx->cache_mask & type) == 0) {
+		struct mail *mail = get_mail(ctx, buf);
+
+		if (mail == NULL)
+			return NULL;
+
+		switch (type) {
+		case MAIL_SORT_SUBJECT:
+			str = mail->get_header(mail, "subject");
+			if (str == NULL)
+				return NULL;
+
+			p_clear(ctx->temp_pool);
+			return imap_get_base_subject_cased(ctx->temp_pool,
+							   str, NULL);
+		case MAIL_SORT_CC:
+			str = mail->get_first_mailbox(mail, "cc");
+			break;
+		case MAIL_SORT_FROM:
+			str = mail->get_first_mailbox(mail, "from");
+			break;
+		case MAIL_SORT_TO:
+			str = mail->get_first_mailbox(mail, "to");
+			break;
+		default:
+			i_unreached();
+		}
+
+		if (str != NULL)
+			str = str_ucase(t_strdup_noconst(str));
+		return str;
+	}
+
+	/* figure out where it is. pretty ugly. */
+	type2 = (ctx->cache_mask & ~type);
+
+	if (type2 == 0)
+		pos = 0;
+	else if (IS_SORT_TIME(type2))
+		pos = sizeof(time_t);
+	else if (type2 == MAIL_SORT_SIZE)
+		pos = sizeof(uoff_t);
+	else {
+		if (type == MAIL_SORT_SUBJECT)
+			pos = sizeof(const char *);
+		else if (type2 != MAIL_SORT_SUBJECT && type > type2)
+			pos = sizeof(const char *);
+		else
+			pos = 0;
+	}
+
+	/* use memcpy() to avoid any alignment problems */
+	memcpy(&str, buf + pos + sizeof(unsigned int), sizeof(const char *));
+	return str;
+}
+
+static int mail_sort_qsort_func(const void *p1, const void *p2)
+{
+	enum mail_sort_type *sorting;
+	int ret, reverse = FALSE;
+
+	sorting = qsort_context->sort_program;
+
+	t_push();
+
+	ret = 0;
+	for (; *sorting != MAIL_SORT_END && ret == 0; sorting++) {
+		if (*sorting == MAIL_SORT_REVERSE) {
+			reverse = !reverse;
+			continue;
+		}
+
+		switch (*sorting) {
+		case MAIL_SORT_ARRIVAL:
+		case MAIL_SORT_DATE: {
+			time_t r1, r2;
+
+			r1 = get_time(*sorting, p1, qsort_context);
+			r2 = get_time(*sorting, p2, qsort_context);
+			ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0;
+			break;
+		}
+		case MAIL_SORT_SIZE: {
+			uoff_t r1, r2;
+
+			r1 = get_uofft(*sorting, p1, qsort_context);
+			r2 = get_uofft(*sorting, p2, qsort_context);
+			ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0;
+			break;
+		}
+		case MAIL_SORT_CC:
+		case MAIL_SORT_FROM:
+		case MAIL_SORT_TO:
+		case MAIL_SORT_SUBJECT:
+			ret = null_strcmp(get_str(*sorting, p1, qsort_context),
+					  get_str(*sorting, p2, qsort_context));
+			break;
+		default:
+			i_unreached();
+		}
+
+		if (reverse) {
+			if (ret > 0)
+				ret = -1;
+			else if (ret < 0)
+				ret = 1;
+		}
+
+		reverse = FALSE;
+	}
+
+	t_pop();
+
+	return ret != 0 ? ret :
+		(*((unsigned int *) p1) < *((unsigned int *) p2) ? -1 : 1);
+}
+
+static void mail_sort_flush(struct sort_context *ctx)
+{
+	unsigned char *arr;
+	size_t i, count;
+
+	qsort_context = ctx;
+
+	arr = buffer_get_modifyable_data(ctx->sort_buffer, NULL);
+	count = buffer_get_used_size(ctx->sort_buffer) / ctx->sort_element_size;
+	if (count == 0)
+		return;
+
+	qsort(arr, count, ctx->sort_element_size, mail_sort_qsort_func);
+
+	for (i = 0; i < count; i++, arr += ctx->sort_element_size) {
+		if (str_len(ctx->str) >= STRBUF_SIZE-MAX_INT_STRLEN) {
+			/* flush */
+			o_stream_send(ctx->output,
+				      str_data(ctx->str), str_len(ctx->str));
+			str_truncate(ctx->str, 0);
+			ctx->written = TRUE;
+		}
+
+		str_printfa(ctx->str, " %u", *((unsigned int *) arr));
+	}
+
+	buffer_set_used_size(ctx->sort_buffer, 0);
+
+	if (ctx->string_table != NULL) {
+		hash_clear(ctx->string_table, TRUE);
+		p_clear(ctx->str_pool);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-sort.h	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,7 @@
+#ifndef __IMAP_SORT_H
+#define __IMAP_SORT_H
+
+int imap_sort(struct client *client, const char *charset,
+	      struct mail_search_arg *args, enum mail_sort_type *sorting);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-thread.c	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,950 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+/*
+ * Merge sort code in sort_nodes() is copyright 2001 Simon Tatham.
+ * 
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/* Implementation of draft-ietf-imapext-thread-12 threading algorithm */
+
+#include "common.h"
+#include "hash.h"
+#include "ostream.h"
+#include "str.h"
+#include "message-tokenize.h"
+#include "imap-base-subject.h"
+#include "imap-thread.h"
+
+#include <stdlib.h>
+
+/* how much memory to allocate initially. these are very rough
+   approximations. */
+#define APPROX_MSG_COUNT 128
+#define APPROX_MSGID_SIZE 45
+
+/* Try to buffer this much data before sending it to output stream. */
+#define OUTPUT_BUF_SIZE 2048
+
+#define NODE_IS_DUMMY(node) ((node)->id == 0)
+#define NODE_HAS_PARENT(ctx, node) \
+	((node)->parent != NULL && (node)->parent != &(ctx)->root_node)
+
+struct root_info {
+	char *base_subject;
+	unsigned int reply:1;
+	unsigned int sorted:1;
+};
+
+struct node {
+	struct node *parent, *first_child, *next;
+
+	unsigned int id;
+	time_t sent_date;
+
+	union {
+		char *msgid;
+		struct root_info *info;
+	} u;
+};
+
+struct thread_context {
+	struct mail_search_context *search_ctx;
+	struct mailbox *box;
+	struct ostream *output;
+
+	pool_t pool;
+	pool_t temp_pool;
+
+	struct hash_table *msgid_hash;
+	struct hash_table *subject_hash;
+
+	struct node root_node;
+	size_t root_count; /* not exact after prune_dummy_messages() */
+
+	int id_is_uid;
+};
+
+static void mail_thread_input(struct thread_context *ctx, struct mail *mail);
+static void mail_thread_finish(struct thread_context *ctx);
+
+static void mail_thread_deinit(struct thread_context *ctx)
+{
+	if (ctx->msgid_hash != NULL)
+		hash_destroy(ctx->msgid_hash);
+	if (ctx->subject_hash != NULL)
+		hash_destroy(ctx->subject_hash);
+
+	pool_unref(ctx->temp_pool);
+	pool_unref(ctx->pool);
+}
+
+int imap_thread(struct client *client, const char *charset,
+		struct mail_search_arg *args, enum mail_thread_type type)
+{
+	static const char *wanted_headers[] = {
+		"message-id", "in-reply-to", "references",
+		NULL
+	};
+	struct thread_context *ctx;
+	struct mail *mail;
+	int ret;
+
+	if (type != MAIL_THREAD_REFERENCES)
+		i_fatal("Only REFERENCES threading supported");
+
+	ctx = t_new(struct thread_context, 1);
+
+	/* initialize searching */
+	ctx->search_ctx = client->mailbox->
+		search_init(client->mailbox, charset, args, NULL,
+			    MAIL_FETCH_DATE, wanted_headers);
+	if (ctx->search_ctx == NULL)
+		return FALSE;
+
+	ctx->box = client->mailbox;
+	ctx->output = client->output;
+	ctx->pool = pool_alloconly_create("thread_context",
+					  sizeof(struct node) *
+					  APPROX_MSG_COUNT);
+	ctx->temp_pool = pool_alloconly_create("thread_context temp",
+					       APPROX_MSG_COUNT *
+					       APPROX_MSGID_SIZE);
+	ctx->msgid_hash = hash_create(default_pool, ctx->temp_pool,
+				      APPROX_MSG_COUNT*2, str_hash,
+				      (hash_cmp_callback_t)strcmp);
+
+	ctx->id_is_uid = client->cmd_uid;
+	while ((mail = client->mailbox->search_next(ctx->search_ctx)) != NULL)
+		mail_thread_input(ctx, mail);
+
+	o_stream_send_str(client->output, "* THREAD");
+	mail_thread_finish(ctx);
+	o_stream_send_str(client->output, "\r\n");
+
+	ret = client->mailbox->search_deinit(ctx->search_ctx);
+        mail_thread_deinit(ctx);
+	return ret;
+}
+
+static void add_root(struct thread_context *ctx, struct node *node)
+{
+	node->parent = &ctx->root_node;
+	node->next = ctx->root_node.first_child;
+	ctx->root_node.first_child = node;
+
+	ctx->root_count++;
+}
+
+static struct node *create_node(struct thread_context *ctx, const char *msgid)
+{
+	struct node *node;
+
+	node = p_new(ctx->pool, struct node, 1);
+	node->u.msgid = p_strdup(ctx->temp_pool, msgid);
+
+	hash_insert(ctx->msgid_hash, node->u.msgid, node);
+	return node;
+}
+
+static struct node *create_id_node(struct thread_context *ctx,
+				   unsigned int id, time_t sent_date)
+{
+	struct node *node;
+
+	node = p_new(ctx->pool, struct node, 1);
+	node->id = id;
+	node->sent_date = sent_date;
+
+	add_root(ctx, node);
+	return node;
+}
+
+static struct node *update_message(struct thread_context *ctx,
+				   const char *msgid, time_t sent_date,
+				   unsigned int id)
+{
+	struct node *node;
+
+	if (msgid == NULL)
+		return create_id_node(ctx, id, sent_date);
+
+	node = hash_lookup(ctx->msgid_hash, msgid);
+	if (node == NULL) {
+		/* first time we see this message */
+		node = create_node(ctx, msgid);
+		node->id = id;
+		node->sent_date = sent_date;
+		return node;
+	}
+
+	if (node->id == 0) {
+		/* seen before in references */
+		node->id = id;
+		node->sent_date = sent_date;
+	} else {
+		/* duplicate */
+		node = create_id_node(ctx, id, sent_date);
+	}
+
+	return node;
+}
+
+static int get_untokenized_msgid(const char **msgid_p, string_t *msgid)
+{
+	static const enum message_token stop_tokens[] = { '>', TOKEN_LAST };
+	struct message_tokenizer *tok;
+	int valid_end;
+
+	tok = message_tokenize_init((const unsigned char *) *msgid_p,
+				    (size_t)-1, NULL, NULL);
+	message_tokenize_dot_token(tok, FALSE); /* just a minor speedup */
+
+	message_tokenize_get_string(tok, msgid, NULL, stop_tokens);
+	valid_end = message_tokenize_get(tok) == '>';
+
+	*msgid_p += message_tokenize_get_parse_position(tok);
+	message_tokenize_deinit(tok);
+
+	if (valid_end) {
+		if (strchr(str_c(msgid), '@') != NULL) {
+			/* <xx@xx> - valid message ID found */
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static void strip_lwsp(char *str)
+{
+	/* @UNSAFE */
+	char *dest;
+
+	/* find the first lwsp */
+	while (*str != ' ' && *str != '\t' && *str != '\r' && *str != '\n') {
+		if (*str == '\0')
+			return;
+		str++;
+	}
+
+	for (dest = str; *str != '\0'; str++) {
+		if (*str != ' ' && *str != '\t' && *str != '\r' && *str != '\n')
+			*dest++ = *str;
+	}
+	*dest = '\0';
+}
+
+static const char *get_msgid(const char **msgid_p)
+{
+	const char *msgid = *msgid_p;
+	const char *p;
+	string_t *str = NULL;
+	int found_at;
+
+	if (*msgid_p == NULL)
+		return NULL;
+
+	for (;;) {
+		/* skip until '<' */
+		while (*msgid != '<') {
+			if (*msgid == '\0') {
+				*msgid_p = msgid;
+				return NULL;
+			}
+			msgid++;
+		}
+		msgid++;
+
+		/* check it through quickly to see if it's already normalized */
+		p = msgid; found_at = FALSE;
+		for (;; p++) {
+			if ((unsigned char)*p >= 'A') /* matches most */
+				continue;
+
+			if (*p == '@')
+				found_at = TRUE;
+			if (*p == '>' || *p == '"' || *p == '(')
+				break;
+
+			if (*p == '\0') {
+				*msgid_p = p;
+				return NULL;
+			}
+		}
+
+		if (*p == '>') {
+			*msgid_p = p+1;
+			if (found_at) {
+				char *s;
+
+				s = p_strdup_until(data_stack_pool, msgid, p);
+				strip_lwsp(s);
+				return s;
+			}
+		} else {
+			/* ok, do it the slow way */
+			*msgid_p = msgid;
+
+			if (str == NULL) {
+				/* allocate only once, so we don't leak
+				   with multiple invalid message IDs */
+				str = t_str_new(256);
+			}
+			if (get_untokenized_msgid(msgid_p, str))
+				return str_c(str);
+		}
+
+		/* invalid message id, see if there's another valid one */
+		msgid = *msgid_p;
+	}
+}
+
+static void unlink_child(struct thread_context *ctx,
+			 struct node *child, int add_to_root)
+{
+	struct node **node;
+
+        node = &child->parent->first_child;
+	for (; *node != NULL; node = &(*node)->next) {
+		if (*node == child) {
+			*node = child->next;
+			break;
+		}
+	}
+
+	child->next = NULL;
+	if (!add_to_root)
+		child->parent = NULL;
+	else
+		add_root(ctx, child);
+}
+
+static int find_child(struct node *node, struct node *child)
+{
+	do {
+		if (node == child)
+			return TRUE;
+
+		if (node->first_child != NULL) {
+			if (find_child(node->first_child, child))
+				return TRUE;
+		}
+
+		node = node->next;
+	} while (node != NULL);
+
+	return FALSE;
+}
+
+static void link_node(struct thread_context *ctx, const char *parent_msgid,
+		      struct node *child, int replace)
+{
+	struct node *parent, **node;
+
+	if (NODE_HAS_PARENT(ctx, child) && !replace) {
+		/* already got a parent, don't want to replace it */
+		return;
+	}
+
+	parent = hash_lookup(ctx->msgid_hash, parent_msgid);
+	if (parent == NULL)
+		parent = create_node(ctx, parent_msgid);
+
+	if (child->parent == parent) {
+		/* already have this parent, ignore */
+		return;
+	}
+
+	if (find_child(child, parent)) {
+		/* this would create a loop, not allowed */
+		return;
+	}
+
+	if (child->parent != NULL)
+		unlink_child(ctx, child, FALSE);
+
+	/* link them */
+	child->parent = parent;
+
+	node = &parent->first_child;
+	while (*node != NULL)
+		node = &(*node)->next;
+	*node = child;
+}
+
+static void link_message(struct thread_context *ctx,
+			 const char *parent_msgid, const char *child_msgid,
+			 int replace)
+{
+	struct node *child;
+
+	child = hash_lookup(ctx->msgid_hash, child_msgid);
+	if (child == NULL)
+		child = create_node(ctx, child_msgid);
+
+	link_node(ctx, parent_msgid, child, replace);
+}
+
+static int link_references(struct thread_context *ctx,
+			   struct node *node, const char *references)
+{
+	const char *parent_id, *child_id;
+
+	parent_id = get_msgid(&references);
+	if (parent_id == NULL)
+		return FALSE;
+
+	while ((child_id = get_msgid(&references)) != NULL) {
+		link_message(ctx, parent_id, child_id, FALSE);
+		parent_id = child_id;
+	}
+
+	/* link the last message to us */
+	link_node(ctx, parent_id, node, TRUE);
+	return TRUE;
+}
+
+static void mail_thread_input(struct thread_context *ctx, struct mail *mail)
+{
+	const char *refid, *message_id, *in_reply_to, *references;
+	struct node *node;
+	time_t sent_date;
+
+	t_push();
+
+	sent_date = mail->get_date(mail, NULL);
+	if (sent_date == (time_t)-1)
+		sent_date = 0;
+
+	message_id = mail->get_header(mail, "message-id");
+	node = update_message(ctx, get_msgid(&message_id), sent_date,
+			      ctx->id_is_uid ? mail->uid : mail->seq);
+
+	/* link references */
+	references = mail->get_header(mail, "references");
+	if (!link_references(ctx, node, references)) {
+		in_reply_to = mail->get_header(mail, "in-reply-to");
+		refid = in_reply_to == NULL ? NULL : get_msgid(&in_reply_to);
+
+		if (refid != NULL)
+			link_node(ctx, refid, node, TRUE);
+		else {
+			/* no references, make sure it's not linked */
+			if (node != NULL && NODE_HAS_PARENT(ctx, node))
+				unlink_child(ctx, node, TRUE);
+		}
+	}
+
+	t_pop();
+}
+
+static struct node *find_last_child(struct node *node)
+{
+	node = node->first_child;
+	while (node->next != NULL)
+		node = node->next;
+
+	return node;
+}
+
+static struct node **promote_children(struct node **parent)
+{
+	struct node *new_parent, *old_parent, *child;
+
+	old_parent = *parent;
+	new_parent = old_parent->parent;
+
+	child = old_parent->first_child;
+	*parent = child;
+
+	for (;;) {
+		child->parent = new_parent;
+		if (child->next == NULL)
+			break;
+		child = child->next;
+	}
+
+	child->next = old_parent->next;
+	return &child->next;
+}
+
+static void prune_dummy_messages(struct thread_context *ctx,
+				 struct node **node_p)
+{
+	struct node **a;
+
+	a = node_p;
+	while (*node_p != NULL) {
+		if ((*node_p)->first_child != NULL)
+			prune_dummy_messages(ctx, &(*node_p)->first_child);
+
+		if (NODE_IS_DUMMY(*node_p)) {
+			if ((*node_p)->first_child == NULL) {
+				/* no children -> delete */
+				*node_p = (*node_p)->next;
+				continue;
+			} else if (NODE_HAS_PARENT(ctx, *node_p) ||
+				   (*node_p)->first_child->next == NULL) {
+				/* promote children to our level,
+				   deleting the dummy node */
+				node_p = promote_children(node_p);
+				continue;
+			}
+		}
+
+                node_p = &(*node_p)->next;
+	}
+}
+
+static int node_cmp(struct node *a, struct node *b)
+{
+	time_t date_a, date_b;
+	unsigned int id_a, id_b;
+
+	date_a = a->id != 0 ? a->sent_date : a->first_child->sent_date;
+	date_b = b->id != 0 ? b->sent_date : b->first_child->sent_date;
+
+	if (date_a != date_b && date_a != 0 && date_b != 0)
+		return date_a < date_b ? -1 : 1;
+
+	id_a = a->id != 0 ? a->id : a->first_child->id;
+	id_b = b->id != 0 ? b->id : b->first_child->id;
+	return id_a < id_b ? -1 : 1;
+}
+
+static struct node *sort_nodes(struct node *list)
+{
+	struct node *p, *q, *e, *tail;
+	size_t insize, nmerges, psize, qsize, i;
+
+	i_assert(list != NULL);
+
+	if (list->next == NULL)
+		return list; /* just one node */
+
+	insize = 1;
+
+	for (;;) {
+		p = list;
+		list = NULL;
+		tail = NULL;
+
+		nmerges = 0;  /* count number of merges we do in this pass */
+		while (p != 0) {
+			nmerges++;  /* there exists a merge to be done */
+
+			/* step `insize' places along from p */
+			q = p;
+			psize = 0;
+			for (i = 0; i < insize; i++) {
+				psize++;
+				q = q->next;
+				if (q == NULL) break;
+			}
+
+			/* if q hasn't fallen off end, we have two lists to
+			   merge */
+			qsize = insize;
+
+			/* now we have two lists; merge them */
+			while (psize > 0 || (qsize > 0 && q != NULL)) {
+				/* decide whether next element of merge comes
+				   from p or q */
+				if (psize == 0) {
+					/* p is empty; e must come from q. */
+					e = q; q = q->next; qsize--;
+				} else if (qsize == 0 || !q) {
+					/* q is empty; e must come from p. */
+					e = p; p = p->next; psize--;
+				} else if (node_cmp(p, q) <= 0) {
+					/* First element of p is lower
+					   (or same); e must come from p. */
+					e = p; p = p->next; psize--;
+				} else {
+					/* First element of q is lower;
+					   e must come from q. */
+					e = q; q = q->next; qsize--;
+				}
+
+				/* add the next element to the merged list */
+				if (tail)
+					tail->next = e;
+				else
+					list = e;
+				tail = e;
+			}
+
+			/* now p has stepped `insize' places along,
+			   and q has too */
+			p = q;
+		}
+		tail->next = NULL;
+
+		/* If we have done only one merge, we're finished. */
+		if (nmerges <= 1) {
+                        /* allow for nmerges == 0, the empty list case */
+			return list;
+		}
+
+		/* Otherwise repeat, merging lists twice the size */
+		insize *= 2;
+	}
+}
+
+static void add_base_subject(struct thread_context *ctx,
+			     const char *subject, struct node *node)
+{
+	struct node *hash_node;
+	char *hash_subject;
+	void *key, *value;
+	int is_reply_or_forward;
+
+	if (subject == NULL)
+		return;
+
+	subject = imap_get_base_subject_cased(data_stack_pool, subject,
+					      &is_reply_or_forward);
+	if (*subject == '\0')
+		return;
+
+	if (!hash_lookup_full(ctx->subject_hash, subject, &key, &value)) {
+		hash_subject = p_strdup(ctx->temp_pool, subject);
+		hash_insert(ctx->subject_hash, hash_subject, node);
+	} else {
+		hash_subject = key;
+		hash_node = value;
+
+		if (!NODE_IS_DUMMY(hash_node) &&
+		    (NODE_IS_DUMMY(node) ||
+		     (hash_node->u.info->reply && !is_reply_or_forward)))
+			hash_update(ctx->subject_hash, hash_subject, node);
+	}
+
+	node->u.info->base_subject = hash_subject;
+	node->u.info->reply = is_reply_or_forward;
+}
+
+static void gather_base_subjects(struct thread_context *ctx)
+{
+	struct mail *mail;
+	struct node *node;
+	unsigned int id;
+
+	ctx->subject_hash =
+		hash_create(default_pool, ctx->temp_pool, ctx->root_count * 2,
+			    str_hash, (hash_cmp_callback_t)strcmp);
+
+	node = ctx->root_node.first_child;
+	for (; node != NULL; node = node->next) {
+		if (!NODE_IS_DUMMY(node))
+			id = node->id;
+		else {
+			/* sort children, use the first one's id */
+			node->first_child = sort_nodes(node->first_child);
+			id = node->first_child->id;
+
+			node->u.info->sorted = TRUE;
+		}
+
+		if (ctx->id_is_uid)
+			mail = ctx->box->fetch_uid(ctx->box, id, 0);
+		else
+			mail = ctx->box->fetch_seq(ctx->box, id, 0);
+
+		if (mail != NULL) {
+			t_push();
+			add_base_subject(ctx, mail->get_header(mail, "subject"),
+					 node);
+			t_pop();
+		}
+	}
+}
+
+static void reset_children_parent(struct node *parent)
+{
+	struct node *node;
+
+	for (node = parent->first_child; node != NULL; node = node->next)
+		node->parent = parent;
+}
+
+static void merge_subject_threads(struct thread_context *ctx)
+{
+	struct node **node_p, *node, *hash_node;
+	char *base_subject;
+
+	for (node_p = &ctx->root_node.first_child; *node_p != NULL; ) {
+		node = *node_p;
+
+		if (node->u.info == NULL) {
+			/* deleted node */
+			*node_p = node->next;
+			continue;
+		}
+
+		/* (ii) If the thread subject is empty, skip this message. */
+		base_subject = node->u.info->base_subject;
+		if (base_subject == NULL) {
+			node_p = &node->next;
+			continue;
+		}
+
+		/* (iii) Lookup the message associated with this thread
+		   subject in the subject table. */
+		hash_node = hash_lookup(ctx->subject_hash, base_subject);
+		i_assert(hash_node != NULL);
+
+		/* (iv) If the message in the subject table is the current
+		   message, skip this message. */
+		if (hash_node == node) {
+			node_p = &node->next;
+			continue;
+		}
+
+		/* Otherwise, merge the current message with the one in the
+		   subject table using the following rules: */
+
+		if (NODE_IS_DUMMY(node) &&
+		    NODE_IS_DUMMY(hash_node)) {
+			/* If both messages are dummies, append the current
+			   message's children to the children of the message in
+			   the subject table (the children of both messages
+			   become siblings), and then delete the current
+			   message. */
+			find_last_child(hash_node)->next = node->first_child;
+
+			*node_p = node->next;
+			hash_node->u.info->sorted = FALSE;
+		} else if (NODE_IS_DUMMY(hash_node) ||
+			   (node->u.info->reply && !hash_node->u.info->reply)) {
+			/* If the message in the subject table is a dummy
+			   and the current message is not, make the current
+			   message a child of the message in the subject table
+			   (a sibling of its children).
+
+			   If the current message is a reply or forward and
+			   the message in the subject table is not, make the
+			   current message a child of the message in the
+			   subject table (a sibling of its children). */
+			*node_p = node->next;
+
+			node->parent = hash_node;
+			node->next = hash_node->first_child;
+			hash_node->first_child = node;
+
+			hash_node->u.info->sorted = FALSE;
+		} else {
+			/* Otherwise, create a new dummy message and make both
+			   the current message and the message in the subject
+			   table children of the dummy.  Then replace the
+			   message in the subject table with the dummy
+			   message. */
+
+			/* create new nodes for the children - reusing
+			   existing ones have problems since the other one
+			   might have been handled already and we'd introduce
+			   loops..
+
+			   current node will be destroyed, hash_node will be
+			   the dummy so we don't need to update hash */
+			struct node *node1, *node2;
+
+			node1 = p_new(ctx->pool, struct node, 1);
+			node2 = p_new(ctx->pool, struct node, 1);
+
+			memcpy(node1, node, sizeof(struct node));
+			memcpy(node2, hash_node, sizeof(struct node));
+
+			node1->parent = hash_node;
+			node2->parent = hash_node;
+			node1->next = node2;
+			node2->next = NULL;
+
+			reset_children_parent(node1);
+			reset_children_parent(node2);
+
+			hash_node->id = 0;
+			hash_node->first_child = node1;
+			hash_node->u.info->reply = FALSE;
+			hash_node->u.info->sorted = FALSE;
+
+			node->first_child = NULL;
+			node->u.info = NULL;
+			*node_p = node->next;
+		}
+	}
+}
+
+static void sort_root_nodes(struct thread_context *ctx)
+{
+	struct node *node;
+
+	/* sort the children first, they're needed to sort dummy root nodes */
+        node = ctx->root_node.first_child;
+	for (; node != NULL; node = node->next) {
+		if (node->u.info == NULL)
+			continue;
+
+		if (NODE_IS_DUMMY(node) && !node->u.info->sorted &&
+		    node->first_child != NULL)
+			node->first_child = sort_nodes(node->first_child);
+	}
+
+	ctx->root_node.first_child = sort_nodes(ctx->root_node.first_child);
+}
+
+static int send_nodes(struct thread_context *ctx,
+		      string_t *str, struct node *node)
+{
+	if (node->next == NULL && NODE_HAS_PARENT(ctx, node)) {
+		/* no siblings - special case to avoid extra paranthesis */
+		if (node->first_child == NULL)
+			str_printfa(str, "%u", node->id);
+		else {
+			str_printfa(str, "%u ", node->id);
+			send_nodes(ctx, str, sort_nodes(node->first_child));
+		}
+		return TRUE;
+	}
+
+	while (node != NULL) {
+		if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) {
+			/* string getting full, flush it */
+			if (!o_stream_send(ctx->output,
+					   str_data(str), str_len(str)))
+				return FALSE;
+			str_truncate(str, 0);
+		}
+
+		if (node->first_child == NULL)
+			str_printfa(str, "(%u)", node->id);
+		else {
+			str_printfa(str, "(%u ", node->id);
+			send_nodes(ctx, str, sort_nodes(node->first_child));
+			str_append_c(str, ')');
+		}
+
+		node = node->next;
+	}
+	return TRUE;
+}
+
+static void send_roots(struct thread_context *ctx)
+{
+	struct node *node;
+	string_t *str;
+
+	str = t_str_new(OUTPUT_BUF_SIZE);
+	str_append_c(str, ' ');
+
+	/* sort root nodes again, they have been modified since the last time */
+	sort_root_nodes(ctx);
+
+        node = ctx->root_node.first_child;
+	for (; node != NULL; node = node->next) {
+		if (node->u.info == NULL)
+			continue;
+
+		if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) {
+			/* string getting full, flush it */
+			if (!o_stream_send(ctx->output,
+					   str_data(str), str_len(str)))
+				return;
+			str_truncate(str, 0);
+		}
+
+		str_append_c(str, '(');
+		if (!NODE_IS_DUMMY(node))
+			str_printfa(str, "%u", node->id);
+
+		if (node->first_child != NULL) {
+			if (!NODE_IS_DUMMY(node))
+				str_append_c(str, ' ');
+
+			if (!node->u.info->sorted) {
+				node->first_child =
+					sort_nodes(node->first_child);
+			}
+
+			if (!send_nodes(ctx, str, node->first_child))
+				return;
+		}
+
+		str_append_c(str, ')');
+	}
+
+	(void)o_stream_send(ctx->output, str_data(str), str_len(str));
+}
+
+static void save_root_cb(void *key __attr_unused__, void *value, void *context)
+{
+	struct thread_context *ctx = context;
+	struct node *node = value;
+
+	if (node->parent == NULL)
+		add_root(ctx, node);
+}
+
+static void mail_thread_finish(struct thread_context *ctx)
+{
+	struct node *node;
+
+	/* (2) save root nodes and drop the msgids */
+	hash_foreach(ctx->msgid_hash, save_root_cb, ctx);
+
+	/* drop the memory allocated for message-IDs and msgid_hash,
+	   reuse their memory for base subjects */
+	hash_destroy(ctx->msgid_hash);
+	ctx->msgid_hash = NULL;
+
+	p_clear(ctx->temp_pool);
+
+	if (ctx->root_node.first_child == NULL) {
+		/* no messages */
+		mail_thread_deinit(ctx);
+		return;
+	}
+
+	/* (3) */
+	prune_dummy_messages(ctx, &ctx->root_node.first_child);
+
+	/* initialize the node->u.info for all root nodes */
+        node = ctx->root_node.first_child;
+	for (; node != NULL; node = node->next)
+		node->u.info = p_new(ctx->pool, struct root_info, 1);
+
+	/* (4) */
+	sort_root_nodes(ctx);
+
+	/* (5) Gather together messages under the root that have the same
+	   base subject text. */
+	gather_base_subjects(ctx);
+
+	/* (5.C) Merge threads with the same thread subject. */
+	merge_subject_threads(ctx);
+
+	/* (6) Sort and send replies */
+	t_push();
+	send_roots(ctx);
+	t_pop();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-thread.h	Mon Jan 20 16:52:51 2003 +0200
@@ -0,0 +1,7 @@
+#ifndef __IMAP_THREAD_H
+#define __IMAP_THREAD_H
+
+int imap_thread(struct client *client, const char *charset,
+		struct mail_search_arg *args, enum mail_thread_type type);
+
+#endif
--- a/src/lib-imap/Makefile.am	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-imap/Makefile.am	Mon Jan 20 16:52:51 2003 +0200
@@ -11,7 +11,6 @@
 	imap-date.c \
 	imap-envelope.c \
 	imap-match.c \
-	imap-message-cache.c \
 	imap-quote.c \
 	imap-parser.c \
 	imap-util.c
@@ -22,7 +21,6 @@
 	imap-date.h \
 	imap-envelope.h \
 	imap-match.h \
-	imap-message-cache.h \
 	imap-quote.h \
 	imap-parser.h \
 	imap-util.h
--- a/src/lib-imap/imap-envelope.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-imap/imap-envelope.c	Mon Jan 20 16:52:51 2003 +0200
@@ -35,25 +35,31 @@
 
 	switch (name_len) {
 	case 2:
-		if (memcasecmp(name, "To", 2) == 0 && d->to == NULL)
-			d->to = message_address_parse(pool, value, value_len);
-		else if (memcasecmp(name, "Cc", 2) == 0 && d->cc == NULL)
-			d->cc = message_address_parse(pool, value, value_len);
+		if (memcasecmp(name, "To", 2) == 0 && d->to == NULL) {
+			d->to = message_address_parse(pool, value,
+						      value_len, 0);
+		} else if (memcasecmp(name, "Cc", 2) == 0 && d->cc == NULL) {
+			d->cc = message_address_parse(pool, value,
+						      value_len, 0);
+		}
 		break;
 	case 3:
-		if (memcasecmp(name, "Bcc", 3) == 0 && d->bcc == NULL)
-			d->bcc = message_address_parse(pool, value, value_len);
+		if (memcasecmp(name, "Bcc", 3) == 0 && d->bcc == NULL) {
+			d->bcc = message_address_parse(pool, value,
+						       value_len, 0);
+		}
 		break;
 	case 4:
-		if (memcasecmp(name, "From", 4) == 0 && d->from == NULL)
-			d->from = message_address_parse(pool, value, value_len);
-		else if (memcasecmp(name, "Date", 4) == 0 && d->date == NULL)
+		if (memcasecmp(name, "From", 4) == 0 && d->from == NULL) {
+			d->from = message_address_parse(pool, value,
+							value_len, 0);
+		} else if (memcasecmp(name, "Date", 4) == 0 && d->date == NULL)
 			d->date = imap_quote_value(pool, value, value_len);
 		break;
 	case 6:
 		if (memcasecmp(name, "Sender", 6) == 0 && d->sender == NULL) {
 			d->sender = message_address_parse(pool, value,
-							  value_len);
+							  value_len, 0);
 		}
 		break;
 	case 7:
@@ -63,8 +69,8 @@
 	case 8:
 		if (memcasecmp(name, "Reply-To", 8) == 0 &&
 		    d->reply_to == NULL) {
-			d->reply_to =
-				message_address_parse(pool, value, value_len);
+			d->reply_to = message_address_parse(pool, value,
+							    value_len, 0);
 		}
 		break;
 	case 10:
@@ -171,47 +177,55 @@
 			return FALSE;
 	}
 
+	if (*in_group && args[0] == NULL && args[1] == NULL &&
+	    args[2] == NULL && args[3] == NULL) {
+		/* end of group */
+		str_append_c(str, ';');
+		*in_group = FALSE;
+		return TRUE;
+	}
+
 	if (str_len(str) > 0)
 		str_append(str, ", ");
 
-	if (*in_group) {
-		if (args[0] == NULL && args[1] == NULL &&
-		    args[2] == NULL && args[3] == NULL) {
-			/* end of group */
-			str_append_c(str, ';');
-			*in_group = FALSE;
-			return TRUE;
-		}
-	} else {
-		if (args[0] == NULL && args[1] == NULL &&
-		    args[2] != NULL && args[3] == NULL) {
-			/* beginning of group */
-			str_append(str, args[2]);
-			str_append(str, ": ");
-			*in_group = TRUE;
-			return TRUE;
-		}
+	if (!*in_group && args[0] == NULL && args[1] == NULL &&
+	    args[2] != NULL && args[3] == NULL) {
+		/* beginning of group */
+		str_append(str, args[2]);
+		str_append(str, ": ");
+		*in_group = TRUE;
+		return TRUE;
 	}
 
-        /* name <@route:mailbox@domain> */
-	if (args[0] != NULL) {
-		str_append(str, args[0]);
-		str_append_c(str, ' ');
-	}
+	/* a) mailbox@domain
+	   b) name <@route:mailbox@domain> */
+	if (args[0] == NULL && args[1] == NULL) {
+		if (args[2] != NULL)
+			str_append(str, args[2]);
+		if (args[3] != NULL) {
+			str_append_c(str, '@');
+			str_append(str, args[3]);
+		}
+	} else {
+		if (args[0] != NULL) {
+			str_append(str, args[0]);
+			str_append_c(str, ' ');
+		}
 
-	str_append_c(str, '<');
-	if (args[1] != NULL) {
-		str_append_c(str, '@');
-		str_append(str, args[1]);
-		str_append_c(str, ':');
+		str_append_c(str, '<');
+		if (args[1] != NULL) {
+			str_append_c(str, '@');
+			str_append(str, args[1]);
+			str_append_c(str, ':');
+		}
+		if (args[2] != NULL)
+			str_append(str, args[2]);
+		if (args[3] != NULL) {
+			str_append_c(str, '@');
+			str_append(str, args[3]);
+		}
+		str_append_c(str, '>');
 	}
-	if (args[2] != NULL)
-		str_append(str, args[2]);
-	if (args[3] != NULL) {
-		str_append_c(str, '@');
-		str_append(str, args[3]);
-	}
-	str_append_c(str, '>');
 	return TRUE;
 }
 
--- a/src/lib-imap/imap-message-cache.c	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,641 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-#include "lib.h"
-#include "istream.h"
-#include "mmap-util.h"
-#include "message-parser.h"
-#include "message-part-serialize.h"
-#include "message-size.h"
-#include "imap-bodystructure.h"
-#include "imap-envelope.h"
-#include "imap-message-cache.h"
-
-#include <unistd.h>
-
-/* It's not very useful to cache lots of messages, as they're mostly wanted
-   just once. The biggest reason for this cache to exist is to get just the
-   latest message. */
-#define MAX_CACHED_MESSAGES 16
-
-#define DEFAULT_MESSAGE_POOL_SIZE 4096
-
-struct cached_message {
-	struct cached_message *next;
-
-	pool_t pool;
-	unsigned int uid;
-
-	struct message_part *part;
-	struct message_size *hdr_size;
-	struct message_size *body_size;
-	struct message_size *partial_size;
-
-	time_t internal_date;
-	uoff_t full_virtual_size;
-
-	char *cached_body;
-	char *cached_bodystructure;
-	char *cached_envelope;
-
-	struct message_part_envelope_data *envelope;
-};
-
-struct imap_message_cache {
-	struct imap_message_cache_iface *iface;
-
-	struct cached_message *messages;
-	int messages_count;
-
-	struct cached_message *open_msg;
-	struct istream *open_stream;
-
-	void *context;
-};
-
-struct imap_message_cache *
-imap_msgcache_alloc(struct imap_message_cache_iface *iface)
-{
-	struct imap_message_cache *cache;
-
-	cache = i_new(struct imap_message_cache, 1);
-	cache->iface = iface;
-	return cache;
-}
-
-static void cached_message_free(struct cached_message *msg)
-{
-	pool_unref(msg->pool);
-}
-
-void imap_msgcache_clear(struct imap_message_cache *cache)
-{
-	struct cached_message *next;
-
-	imap_msgcache_close(cache);
-
-	while (cache->messages != NULL) {
-		next = cache->messages->next;
-		cached_message_free(cache->messages);
-		cache->messages = next;
-	}
-}
-
-void imap_msgcache_free(struct imap_message_cache *cache)
-{
-	imap_msgcache_clear(cache);
-	i_free(cache);
-}
-
-static struct cached_message *
-cache_new(struct imap_message_cache *cache, unsigned int uid)
-{
-	struct cached_message *msg, **msgp;
-	pool_t pool;
-
-	if (cache->messages_count < MAX_CACHED_MESSAGES)
-		cache->messages_count++;
-	else {
-		/* remove the last message from cache */
-                msgp = &cache->messages;
-		while ((*msgp)->next != NULL)
-			msgp = &(*msgp)->next;
-
-		cached_message_free(*msgp);
-		*msgp = NULL;
-	}
-
-	pool = pool_alloconly_create("cached_message",
-				     DEFAULT_MESSAGE_POOL_SIZE);
-
-	msg = p_new(pool, struct cached_message, 1);
-	msg->pool = pool;
-	msg->uid = uid;
-	msg->internal_date = (time_t)-1;
-	msg->full_virtual_size = (uoff_t)-1;
-
-	msg->next = cache->messages;
-	cache->messages = msg;
-	return msg;
-}
-
-static struct cached_message *
-cache_open_or_create(struct imap_message_cache *cache, unsigned int uid)
-{
-	struct cached_message **pos, *msg;
-
-	pos = &cache->messages;
-	for (; *pos != NULL; pos = &(*pos)->next) {
-		if ((*pos)->uid == uid)
-			break;
-	}
-
-	if (*pos == NULL) {
-		/* not found, add it */
-		msg = cache_new(cache, uid);
-	} else if (*pos != cache->messages) {
-		/* move it to first in list */
-		msg = *pos;
-		*pos = msg->next;
-
-		msg->next = cache->messages;
-		cache->messages = msg;
-	} else {
-		msg = *pos;
-	}
-
-	return msg;
-}
-
-static void parse_envelope_header(struct message_part *part,
-				  const unsigned char *name, size_t name_len,
-				  const unsigned char *value, size_t value_len,
-				  void *context)
-{
-	struct cached_message *msg = context;
-
-	if (part == NULL || part->parent == NULL) {
-		/* parse envelope headers if we're at the root message part */
-		imap_envelope_parse_header(msg->pool, &msg->envelope,
-					   name, name_len, value, value_len);
-	}
-}
-
-static int imap_msgcache_get_stream(struct imap_message_cache *cache,
-				    uoff_t offset)
-{
-	if (cache->open_stream == NULL)
-		cache->open_stream = cache->iface->open_mail(cache->context);
-	else if (offset < cache->open_stream->v_offset) {
-		/* need to rewind */
-		cache->open_stream =
-			cache->iface->stream_rewind(cache->open_stream,
-						    cache->context);
-	}
-
-	if (cache->open_stream == NULL)
-		return FALSE;
-
-	i_assert(offset >= cache->open_stream->v_offset);
-
-	i_stream_skip(cache->open_stream,
-		      offset - cache->open_stream->v_offset);
-	return TRUE;
-}
-
-static void msg_get_part(struct imap_message_cache *cache)
-{
-	if (cache->open_msg->part == NULL) {
-		cache->open_msg->part =
-			cache->iface->get_cached_parts(cache->open_msg->pool,
-						       cache->context);
-	}
-}
-
-/* Caches the fields for given message if possible */
-static int cache_fields(struct imap_message_cache *cache,
-			enum imap_cache_field fields)
-{
-        struct cached_message *msg;
-	const char *value;
-	int failed;
-
-	msg = cache->open_msg;
-	failed = FALSE;
-
-	t_push();
-
-	if ((fields & IMAP_CACHE_BODYSTRUCTURE) &&
-	    msg->cached_bodystructure == NULL) {
-		value = cache->iface->get_cached_field(IMAP_CACHE_BODYSTRUCTURE,
-						       cache->context);
-		if (value == NULL && imap_msgcache_get_stream(cache, 0)) {
-			msg_get_part(cache);
-
-			value = imap_part_get_bodystructure(msg->pool,
-							    &msg->part,
-							    cache->open_stream,
-							    TRUE);
-		}
-
-		msg->cached_bodystructure = p_strdup(msg->pool, value);
-		failed = value == NULL;
-	}
-
-	if ((fields & IMAP_CACHE_BODY) && msg->cached_body == NULL) {
-		value = cache->iface->get_cached_field(IMAP_CACHE_BODY,
-						       cache->context);
-		if (value == NULL && cache->open_stream != NULL) {
-			/* we can generate it from cached BODYSTRUCTURE.
-			   do it only if the file isn't open already, since
-			   this takes more CPU than parsing message headers. */
-			value = cache->iface->get_cached_field(
-						IMAP_CACHE_BODYSTRUCTURE,
-						cache->context);
-			if (value != NULL) {
-				value = imap_body_parse_from_bodystructure(
-									value);
-			}
-		}
-
-		if (value == NULL && imap_msgcache_get_stream(cache, 0)) {
-			msg_get_part(cache);
-
-			value = imap_part_get_bodystructure(msg->pool,
-							    &msg->part,
-							    cache->open_stream,
-							    FALSE);
-		}
-
-		msg->cached_body = p_strdup(msg->pool, value);
-		failed = value == NULL;
-	}
-
-	if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL) {
-		value = cache->iface->get_cached_field(IMAP_CACHE_ENVELOPE,
-						       cache->context);
-		if (value == NULL) {
-			if (msg->envelope == NULL &&
-			    imap_msgcache_get_stream(cache, 0)) {
-				/* envelope isn't parsed yet, do it. header
-				   size is calculated anyway so save it */
-				if (msg->hdr_size == NULL) {
-					msg->hdr_size =
-						p_new(msg->pool,
-						      struct message_size, 1);
-				}
-
-				message_parse_header(NULL, cache->open_stream,
-						     msg->hdr_size,
-						     parse_envelope_header,
-						     msg);
-			}
-
-			value = imap_envelope_get_part_data(msg->envelope);
-		}
-
-		msg->cached_envelope = p_strdup(msg->pool, value);
-		failed = value == NULL;
-	}
-
-	if ((fields & IMAP_CACHE_VIRTUAL_SIZE) &&
-	    msg->full_virtual_size == (uoff_t)-1) {
-		fields |= IMAP_CACHE_MESSAGE_HDR_SIZE |
-			IMAP_CACHE_MESSAGE_BODY_SIZE;
-	}
-
-	if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) && msg->body_size == NULL) {
-		/* we don't have body size. and since we're already going
-		   to scan the whole message body, we might as well build
-		   the message_part. FIXME: this slows down things when it's
-		   not needed, do we really want to? */
-                fields |= IMAP_CACHE_MESSAGE_PART;
-	}
-
-	if (fields & IMAP_CACHE_MESSAGE_PART) {
-		msg_get_part(cache);
-
-		if (msg->part == NULL && imap_msgcache_get_stream(cache, 0)) {
-			/* we need to parse the message */
-			message_header_callback_t callback;
-
-			if ((fields & IMAP_CACHE_ENVELOPE) &&
-			    msg->cached_envelope == NULL) {
-				/* we need envelope too, fill the info
-				   while parsing headers */
-				callback = parse_envelope_header;
-			} else {
-				callback = NULL;
-			}
-
-			msg->part = message_parse(msg->pool, cache->open_stream,
-						  callback, msg);
-		}
-
-		failed = msg->part == NULL;
-	}
-
-	if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) &&
-	    msg->body_size == NULL && msg->part != NULL) {
-		msg->body_size = p_new(msg->pool, struct message_size, 1);
-		if (msg->hdr_size == NULL) {
-			msg->hdr_size = p_new(msg->pool,
-					      struct message_size, 1);
-		}
-
-		*msg->hdr_size = msg->part->header_size;
-		*msg->body_size = msg->part->body_size;
-	}
-
-	if ((fields & IMAP_CACHE_MESSAGE_HDR_SIZE) && msg->hdr_size == NULL) {
-		msg_get_part(cache);
-
-		msg->hdr_size = p_new(msg->pool, struct message_size, 1);
-		if (msg->part != NULL) {
-			/* easy, get it from root part */
-			*msg->hdr_size = msg->part->header_size;
-
-			if (msg->body_size == NULL) {
-				msg->body_size = p_new(msg->pool,
-						       struct message_size, 1);
-				*msg->body_size = msg->part->body_size;
-			}
-		} else {
-			/* need to do some light parsing */
-			if (imap_msgcache_get_stream(cache, 0)) {
-				message_get_header_size(cache->open_stream,
-							msg->hdr_size);
-			} else {
-				failed = TRUE;
-			}
-		}
-	}
-
-	if ((fields & IMAP_CACHE_VIRTUAL_SIZE) &&
-	    msg->full_virtual_size == (uoff_t)-1) {
-		if (msg->hdr_size == NULL || msg->body_size == NULL)
-			failed = TRUE;
-		else {
-			msg->full_virtual_size = msg->hdr_size->virtual_size +
-                                msg->body_size->virtual_size;
-		}
-	}
-
-	if (fields & IMAP_CACHE_MESSAGE_OPEN) {
-		/* this isn't needed for anything else than pre-opening the
-		   mail and seeing if it fails. */
-		failed = !imap_msgcache_get_stream(cache, 0);
-	}
-
-	if ((fields & IMAP_CACHE_INTERNALDATE) &&
-	    msg->internal_date == (time_t)-1) {
-		/* keep this last, since we may get it when mail file is
-		   opened. */
-		msg->internal_date =
-			cache->iface->get_internal_date(cache->context);
-		failed = msg->internal_date == (time_t)-1;
-	}
-
-	t_pop();
-	return !failed;
-}
-
-int imap_msgcache_open(struct imap_message_cache *cache, unsigned int uid,
-		       enum imap_cache_field fields,
-		       uoff_t vp_header_size, uoff_t vp_body_size,
-		       uoff_t full_virtual_size, void *context)
-{
-	struct cached_message *msg;
-
-	msg = cache_open_or_create(cache, uid);
-	if (cache->open_msg != msg) {
-		imap_msgcache_close(cache);
-		cache->open_msg = msg;
-	}
-	cache->context = context;
-
-	if (vp_header_size != (uoff_t)-1 && msg->hdr_size == NULL) {
-		/* physical size == virtual size */
-		msg->hdr_size = p_new(msg->pool, struct message_size, 1);
-		msg->hdr_size->physical_size = msg->hdr_size->virtual_size =
-			vp_header_size;
-	}
-
-	if (vp_body_size != (uoff_t)-1 && msg->body_size == NULL) {
-		/* physical size == virtual size */
-		msg->body_size = p_new(msg->pool, struct message_size, 1);
-		msg->body_size->physical_size = msg->body_size->virtual_size =
-			vp_body_size;
-	}
-
-	msg->full_virtual_size = full_virtual_size;
-
-	return cache_fields(cache, fields);
-}
-
-void imap_msgcache_close(struct imap_message_cache *cache)
-{
-	if (cache->open_stream != NULL) {
-		i_stream_unref(cache->open_stream);
-		cache->open_stream = NULL;
-	}
-
-	cache->open_msg = NULL;
-	cache->context = NULL;
-}
-
-const char *imap_msgcache_get(struct imap_message_cache *cache,
-			      enum imap_cache_field field)
-{
-	struct cached_message *msg;
-
-	i_assert(cache->open_msg != NULL);
-
-	msg = cache->open_msg;
-	switch (field) {
-	case IMAP_CACHE_BODY:
-		if (msg->cached_body == NULL)
-			cache_fields(cache, field);
-		return msg->cached_body;
-	case IMAP_CACHE_BODYSTRUCTURE:
-		if (msg->cached_bodystructure == NULL)
-			cache_fields(cache, field);
-		return msg->cached_bodystructure;
-	case IMAP_CACHE_ENVELOPE:
-		if (msg->cached_envelope == NULL)
-			cache_fields(cache, field);
-		return msg->cached_envelope;
-	default:
-                i_unreached();
-	}
-
-	return NULL;
-}
-
-struct message_part *imap_msgcache_get_parts(struct imap_message_cache *cache)
-{
-	if (cache->open_msg->part == NULL)
-		cache_fields(cache, IMAP_CACHE_MESSAGE_PART);
-	return cache->open_msg->part;
-}
-
-uoff_t imap_msgcache_get_virtual_size(struct imap_message_cache *cache)
-{
-	if (cache->open_msg->full_virtual_size == (uoff_t)-1)
-		cache_fields(cache, IMAP_CACHE_VIRTUAL_SIZE);
-	return cache->open_msg->full_virtual_size;
-}
-
-time_t imap_msgcache_get_internal_date(struct imap_message_cache *cache)
-{
-	if (cache->open_msg->internal_date == (time_t)-1)
-		cache_fields(cache, IMAP_CACHE_INTERNALDATE);
-	return cache->open_msg->internal_date;
-}
-
-int imap_msgcache_get_rfc822(struct imap_message_cache *cache,
-			     struct istream **stream,
-			     struct message_size *hdr_size,
-			     struct message_size *body_size)
-{
-	struct cached_message *msg;
-	uoff_t offset;
-
-	i_assert(cache->open_msg != NULL);
-
-	msg = cache->open_msg;
-	if (stream != NULL) {
-		if (msg->hdr_size == NULL)
-			cache_fields(cache, IMAP_CACHE_MESSAGE_HDR_SIZE);
-		offset = hdr_size != NULL ? 0 :
-			msg->hdr_size->physical_size;
-		if (!imap_msgcache_get_stream(cache, offset))
-			return FALSE;
-                *stream = cache->open_stream;
-	}
-
-	if (body_size != NULL) {
-		if (msg->body_size == NULL)
-			cache_fields(cache, IMAP_CACHE_MESSAGE_BODY_SIZE);
-		if (msg->body_size == NULL)
-			return FALSE;
-		*body_size = *msg->body_size;
-	}
-
-	if (hdr_size != NULL) {
-		if (msg->hdr_size == NULL)
-			cache_fields(cache, IMAP_CACHE_MESSAGE_HDR_SIZE);
-		if (msg->hdr_size == NULL)
-			return FALSE;
-		*hdr_size = *msg->hdr_size;
-	}
-
-	return TRUE;
-}
-
-static uoff_t get_partial_size(struct istream *stream,
-			       uoff_t virtual_skip, uoff_t max_virtual_size,
-			       struct message_size *partial,
-			       struct message_size *dest, int *cr_skipped)
-{
-	uoff_t physical_skip;
-	int last_cr;
-
-	/* see if we can use the existing partial */
-	if (partial->virtual_size > virtual_skip)
-		memset(partial, 0, sizeof(struct message_size));
-	else {
-		i_stream_skip(stream, partial->physical_size);
-		virtual_skip -= partial->virtual_size;
-	}
-
-	message_skip_virtual(stream, virtual_skip, partial, cr_skipped);
-        physical_skip = partial->physical_size;
-
-	if (*cr_skipped && max_virtual_size != (uoff_t)-1) {
-		/* get_body_size() sees \n first, counting it as \r\n */
-		max_virtual_size++;
-	}
-
-	message_get_body_size(stream, dest, max_virtual_size, &last_cr);
-
-	if (*cr_skipped) {
-		/* extra virtual \r counted, drop it */
-		dest->virtual_size--;
-	}
-
-	message_size_add(partial, dest);
-	if (last_cr != 0) {
-		/* we'll see \n as first character next time, so make sure
-		   we don't count the (virtual) \r twice. */
-		i_assert(partial->physical_size > 0);
-
-		if (last_cr == 1)
-			partial->physical_size--;
-		partial->virtual_size--;
-	}
-	return physical_skip;
-}
-
-int imap_msgcache_get_rfc822_partial(struct imap_message_cache *cache,
-				     uoff_t virtual_skip,
-				     uoff_t max_virtual_size,
-				     int get_header, struct message_size *size,
-                                     struct istream **stream, int *cr_skipped)
-{
-	struct cached_message *msg;
-	uoff_t physical_skip, full_size;
-	int size_got;
-
-	i_assert(cache->open_msg != NULL);
-
-	memset(size, 0, sizeof(struct message_size));
-	*stream = NULL;
-	*cr_skipped = FALSE;
-
-	msg = cache->open_msg;
-
-	if (msg->hdr_size == NULL) {
-		cache_fields(cache, IMAP_CACHE_MESSAGE_HDR_SIZE);
-		if (msg->hdr_size == NULL)
-			return FALSE;
-	}
-
-	/* see if we can do this easily */
-	size_got = FALSE;
-	if (virtual_skip == 0) {
-		if (msg->body_size == NULL) {
-			cache_fields(cache, IMAP_CACHE_MESSAGE_BODY_SIZE);
-			if (msg->body_size == NULL)
-				return FALSE;
-		}
-
-		full_size = msg->body_size->virtual_size;
-		if (get_header)
-			full_size += msg->hdr_size->virtual_size;
-
-		if (max_virtual_size >= full_size) {
-			memcpy(size, msg->body_size, sizeof(*size));
-			if (get_header)
-				message_size_add(size, msg->hdr_size);
-			size_got = TRUE;
-		}
-	}
-
-	if (size_got) {
-		physical_skip = get_header ? 0 : msg->hdr_size->physical_size;
-	} else {
-		if (!imap_msgcache_get_stream(cache, 0))
-			return FALSE;
-
-		if (msg->partial_size == NULL) {
-			msg->partial_size =
-				p_new(msg->pool, struct message_size, 1);
-		}
-		if (!get_header)
-			virtual_skip += msg->hdr_size->virtual_size;
-
-		physical_skip =
-			get_partial_size(cache->open_stream, virtual_skip,
-					 max_virtual_size, msg->partial_size,
-					 size, cr_skipped);
-	}
-
-	/* seek to wanted position */
-	if (!imap_msgcache_get_stream(cache, physical_skip))
-		return FALSE;
-
-        *stream = cache->open_stream;
-	return TRUE;
-}
-
-int imap_msgcache_get_data(struct imap_message_cache *cache,
-			   struct istream **stream)
-{
-	i_assert(cache->open_msg != NULL);
-
-	if (!imap_msgcache_get_stream(cache, 0))
-		return FALSE;
-
-        *stream = cache->open_stream;
-	return TRUE;
-}
--- a/src/lib-imap/imap-message-cache.h	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-#ifndef __IMAP_MESSAGE_CACHE_H
-#define __IMAP_MESSAGE_CACHE_H
-
-struct message_part;
-struct message_size;
-
-/* IMAP message cache. Caches are mailbox-specific and must be cleared
-   if UID validity changes. Also if message data may have changed,
-   imap_msgcache_close() must be called.
-
-   Caching is mostly done to avoid parsing the same message multiple times
-   when client fetches the message in parts.
-*/
-
-enum imap_cache_field {
-	IMAP_CACHE_BODY			= 0x0001,
-	IMAP_CACHE_BODYSTRUCTURE	= 0x0002,
-	IMAP_CACHE_ENVELOPE		= 0x0004,
-	IMAP_CACHE_INTERNALDATE		= 0x0008,
-	IMAP_CACHE_VIRTUAL_SIZE		= 0x0010,
-
-	IMAP_CACHE_MESSAGE_OPEN		= 0x0200,
-	IMAP_CACHE_MESSAGE_PART		= 0x0400,
-	IMAP_CACHE_MESSAGE_HDR_SIZE	= 0x0800,
-	IMAP_CACHE_MESSAGE_BODY_SIZE	= 0x0100
-};
-
-struct imap_message_cache_iface {
-	/* Open mail for reading. */
-	struct istream *(*open_mail)(void *context);
-	/* Rewind stream to beginning, possibly closing the old stream
-	   if it can't directly be rewinded. */
-	struct istream *(*stream_rewind)(struct istream *stream, void *context);
-
-	/* Returns field if it's already cached, or NULL. */
-	const char *(*get_cached_field)(enum imap_cache_field field,
-					void *context);
-	/* Returns message_part if it's already cached, or NULL. */
-	struct message_part *(*get_cached_parts)(pool_t pool, void *context);
-
-	/* Returns message's internal date, or (time_t)-1 if error. */
-	time_t (*get_internal_date)(void *context);
-};
-
-struct imap_message_cache;
-
-struct imap_message_cache *
-imap_msgcache_alloc(struct imap_message_cache_iface *iface);
-void imap_msgcache_clear(struct imap_message_cache *cache);
-void imap_msgcache_free(struct imap_message_cache *cache);
-
-/* Open the specified message. Set vp_*_size if both physical and virtual
-   sizes are same, otherwise (uoff_t)-1. If full_virtual_size isn't known,
-   set it to (uoff_t)-1. Returns TRUE if all specified fields were cached.
-   Even if FALSE is returned, it's possible to use the cached data,
-   imap_msgcache_get() just returns NULL for those that weren't. */
-int imap_msgcache_open(struct imap_message_cache *cache, unsigned int uid,
-		       enum imap_cache_field fields,
-		       uoff_t vp_header_size, uoff_t vp_body_size,
-		       uoff_t full_virtual_size, void *context);
-
-/* Close the IOStream for opened message. */
-void imap_msgcache_close(struct imap_message_cache *cache);
-
-/* Returns the field from cache, or NULL if it's not cached. */
-const char *imap_msgcache_get(struct imap_message_cache *cache,
-			      enum imap_cache_field field);
-
-/* Returns the root message_part for message, or NULL if failed. */
-struct message_part *imap_msgcache_get_parts(struct imap_message_cache *cache);
-
-/* Returns the virtual size of message, or (uoff_t)-1 if failed. */
-uoff_t imap_msgcache_get_virtual_size(struct imap_message_cache *cache);
-
-/* Returns the internal date of message, or (time_t)-1 if failed. */
-time_t imap_msgcache_get_internal_date(struct imap_message_cache *cache);
-
-/* Returns TRUE if successful. If stream is not NULL, it's set to point to
-   beginning of message, or to beginning of message body if hdr_size is NULL. */
-int imap_msgcache_get_rfc822(struct imap_message_cache *cache,
-			     struct istream **stream,
-			     struct message_size *hdr_size,
-			     struct message_size *body_size);
-
-/* Returns TRUE if successful. *stream is set to point to the first non-skipped
-   character. size is set to specify the actual message size in
-   virtual_skip..max_virtual_size range. cr_skipped is set to TRUE if first
-   character in stream is LF, and we should NOT treat it as CR+LF. */
-int imap_msgcache_get_rfc822_partial(struct imap_message_cache *cache,
-				     uoff_t virtual_skip,
-				     uoff_t max_virtual_size,
-				     int get_header, struct message_size *size,
-				     struct istream **stream, int *cr_skipped);
-
-/* Returns TRUE if successful. *stream is set to point to beginning of
-   message. */
-int imap_msgcache_get_data(struct imap_message_cache *cache,
-			   struct istream **stream);
-
-#endif
--- a/src/lib-index/mail-index-data.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-index/mail-index-data.c	Mon Jan 20 16:52:51 2003 +0200
@@ -139,12 +139,12 @@
 	}
 
 	if (size != 0) {
+		if (pos + size <= data->mmap_used_length)
+			return TRUE;
+
 		debug_mprotect(data->mmap_base, data->mmap_full_length,
 			       data->index);
 
-		if (pos + size <= data->mmap_used_length)
-			return TRUE;
-
 		if (pos + size <= data->mmap_full_length) {
 			data->mmap_used_length = data->header->used_file_size;
 			if (data->mmap_used_length >=
--- a/src/lib-mail/message-address.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-address.c	Mon Jan 20 16:52:51 2003 +0200
@@ -19,7 +19,8 @@
 }
 
 struct message_address *
-message_address_parse(pool_t pool, const unsigned char *data, size_t size)
+message_address_parse(pool_t pool, const unsigned char *data, size_t size,
+		      unsigned int max_addresses)
 {
 	static const enum message_token stop_tokens_init[] =
 		{ ',', '@', '<', ':', TOKEN_LAST };
@@ -74,8 +75,11 @@
 	ingroup = FALSE; len = 0;
 	stop_tokens = stop_tokens_init;
 
+	if (max_addresses == 0)
+		max_addresses = (unsigned int)-1;
+
 	next_phrase = mailbox; stop = FALSE;
-	while (!stop) {
+	while (!stop && max_addresses > 0) {
 		if (next_phrase == name && str_len(name) > 0) {
 			/* continuing previously started name,
 			   separate it from us with space */
@@ -101,6 +105,7 @@
 			if (str_len(mailbox) > 0 || str_len(domain) > 0 ||
 			    str_len(route) > 0 || str_len(name) > 0) {
 				addr = new_address(pool, &next_addr);
+				max_addresses--;
 				addr->mailbox = p_strdup(pool, str_c(mailbox));
 				addr->domain = str_len(domain) == 0 ? NULL :
 					p_strdup(pool, str_c(domain));
@@ -115,6 +120,7 @@
 				/* end of group - add end of group marker */
 				ingroup = FALSE;
 				(void)new_address(pool, &next_addr);
+				max_addresses--;
 			}
 
 			if (token == TOKEN_LAST) {
@@ -187,6 +193,7 @@
 		case ':':
 			/* beginning of group */
 			addr = new_address(pool, &next_addr);
+			max_addresses--;
 			addr->name = p_strdup(pool, str_c(mailbox));
 
 			str_truncate(mailbox, 0);
@@ -210,3 +217,61 @@
 	return first_addr;
 }
 
+void message_address_write(string_t *str, const struct message_address *addr)
+{
+	int first = TRUE, in_group = FALSE;
+
+	/* a) mailbox@domain
+	   b) name <@route:mailbox@domain>
+	   c) group: .. ; */
+
+	while (addr != NULL) {
+		if (first)
+			first = FALSE;
+		else
+			str_append(str, ", ");
+
+		if (addr->mailbox == NULL && addr->domain == NULL) {
+			if (!in_group) {
+				if (addr->name != NULL)
+					str_append(str, addr->name);
+				str_append(str, ": ");
+				first = TRUE;
+			} else {
+				i_assert(addr->name == NULL);
+
+				/* cut out the ", " */
+				str_truncate(str, str_len(str)-2);
+				str_append_c(str, ';');
+			}
+
+			in_group = !in_group;
+		} else if ((addr->name == NULL || *addr->name == '\0') &&
+			   addr->route == NULL) {
+			i_assert(addr->mailbox != NULL);
+			i_assert(addr->domain != NULL);
+
+			str_append(str, addr->mailbox);
+			str_append_c(str, '@');
+			str_append(str, addr->domain);
+		} else {
+			i_assert(addr->mailbox != NULL);
+			i_assert(addr->domain != NULL);
+
+			if (addr->name != NULL) {
+				str_append(str, addr->name);
+				str_append_c(str, ' ');
+			}
+			str_append_c(str, '<');
+			if (addr->route != NULL) {
+				str_append_c(str, '@');
+				str_append(str, addr->route);
+				str_append_c(str, ':');
+			}
+			str_append(str, addr->mailbox);
+			str_append_c(str, '@');
+			str_append(str, addr->domain);
+			str_append_c(str, '>');
+		}
+	}
+}
--- a/src/lib-mail/message-address.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-address.h	Mon Jan 20 16:52:51 2003 +0200
@@ -1,13 +1,22 @@
 #ifndef __MESSAGE_ADDRESS_H
 #define __MESSAGE_ADDRESS_H
 
+/* group: ... ; will be stored like:
+   {name = "group", NULL, NULL, NULL}, ..., {NULL, NULL, NULL, NULL}
+*/
 struct message_address {
 	struct message_address *next;
 
 	const char *name, *route, *mailbox, *domain;
 };
 
+/* data and size are passed directly to message_tokenize_init(), so (size_t)-1
+   can be given if data is \0 terminated. If there's more than max_addresses,
+   the rest are skipped. Setting max_addresses to 0 disables this. */
 struct message_address *
-message_address_parse(pool_t pool, const unsigned char *data, size_t size);
+message_address_parse(pool_t pool, const unsigned char *data, size_t size,
+		      unsigned int max_addresses);
+
+void message_address_write(string_t *str, const struct message_address *addr);
 
 #endif
--- a/src/lib-mail/message-body-search.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-body-search.c	Mon Jan 20 16:52:51 2003 +0200
@@ -255,7 +255,7 @@
 
 static int message_search_body(struct part_search_context *ctx,
 			       struct istream *input,
-			       struct message_part *part)
+			       const struct message_part *part)
 {
 	const unsigned char *data;
 	buffer_t *decodebuf;
@@ -368,7 +368,7 @@
 
 static int message_body_search_ctx(struct body_search_context *ctx,
 				   struct istream *input,
-				   struct message_part *part)
+				   const struct message_part *part)
 {
 	struct part_search_context part_ctx;
 	int found;
@@ -410,7 +410,7 @@
 
 int message_body_search(const char *key, const char *charset,
 			int *unknown_charset, struct istream *input,
-			struct message_part *part, int search_header)
+			const struct message_part *part, int search_header)
 {
         struct body_search_context ctx;
 
--- a/src/lib-mail/message-body-search.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-body-search.h	Mon Jan 20 16:52:51 2003 +0200
@@ -9,6 +9,6 @@
    specific charset but is compared to message data without any translation. */
 int message_body_search(const char *key, const char *charset,
 			int *unknown_charset, struct istream *input,
-			struct message_part *part, int search_header);
+			const struct message_part *part, int search_header);
 
 #endif
--- a/src/lib-mail/message-date.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-date.c	Mon Jan 20 16:52:51 2003 +0200
@@ -213,7 +213,8 @@
 	return TRUE;
 }
 
-int message_date_parse(const char *data, time_t *time, int *timezone_offset)
+int message_date_parse(const unsigned char *data, size_t size,
+		       time_t *time, int *timezone_offset)
 {
 	struct message_tokenizer *ctx;
 	int ret;
@@ -221,8 +222,7 @@
 	if (data == NULL || *data == '\0')
 		return FALSE;
 
-	ctx = message_tokenize_init((const unsigned char *) data, (size_t)-1,
-				    NULL, NULL);
+	ctx = message_tokenize_init(data, size, NULL, NULL);
 	ret = mail_date_parse_tokens(ctx, time, timezone_offset);
 	message_tokenize_deinit(ctx);
 
--- a/src/lib-mail/message-date.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-date.h	Mon Jan 20 16:52:51 2003 +0200
@@ -3,7 +3,8 @@
 
 /* Parses RFC2822 date/time string. timezone_offset is filled with the
    timezone's difference to UTC in minutes. */
-int message_date_parse(const char *data, time_t *time, int *timezone_offset);
+int message_date_parse(const unsigned char *data, size_t size,
+		       time_t *time, int *timezone_offset);
 
 /* Create RFC2822 date/time string from given time in local timezone. */
 const char *message_date_create(time_t time);
--- a/src/lib-mail/message-parser.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-parser.h	Mon Jan 20 16:52:51 2003 +0200
@@ -1,6 +1,8 @@
 #ifndef __MESSAGE_PARSER_H
 #define __MESSAGE_PARSER_H
 
+#include "message-size.h"
+
 #define IS_LWSP(c) \
 	((c) == ' ' || (c) == '\t')
 
@@ -16,12 +18,6 @@
 	MESSAGE_PART_FLAG_BINARY		= 0x10
 };
 
-struct message_size {
-	uoff_t physical_size;
-	uoff_t virtual_size;
-	unsigned int lines;
-};
-
 struct message_part {
 	struct message_part *parent;
 	struct message_part *next;
--- a/src/lib-mail/message-send.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-send.c	Mon Jan 20 16:52:51 2003 +0200
@@ -8,7 +8,7 @@
 #include "message-size.h"
 
 int message_send(struct ostream *output, struct istream *input,
-		 struct message_size *msg_size,
+		 const struct message_size *msg_size,
 		 uoff_t virtual_skip, uoff_t max_virtual_size)
 {
 	const unsigned char *msg;
--- a/src/lib-mail/message-send.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-send.h	Mon Jan 20 16:52:51 2003 +0200
@@ -8,7 +8,7 @@
    use (uoff_t)-1. Remember that if input begins with LF, CR is inserted
    before it unless virtual_skip = 1. Returns TRUE if successful. */
 int message_send(struct ostream *output, struct istream *input,
-		 struct message_size *msg_size,
+		 const struct message_size *msg_size,
 		 uoff_t virtual_skip, uoff_t max_virtual_size);
 
 #endif
--- a/src/lib-mail/message-size.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-size.h	Mon Jan 20 16:52:51 2003 +0200
@@ -1,7 +1,11 @@
 #ifndef __MESSAGE_SIZE_H
 #define __MESSAGE_SIZE_H
 
-struct message_size;
+struct message_size {
+	uoff_t physical_size;
+	uoff_t virtual_size;
+	unsigned int lines;
+};
 
 /* Calculate size of message header. Leave the input point to first
    character in body. */
--- a/src/lib-mail/message-tokenize.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-mail/message-tokenize.h	Mon Jan 20 16:52:51 2003 +0200
@@ -34,9 +34,9 @@
 						 char missing_char,
 						 void *context);
 
-/* Tokenize the string. Returns NULL if string is empty. Memory for
-   returned array is allocated from data stack. You don't have to use
-   the tokens_count, since last token is always 0. */
+/* Initialize message tokenizer. data is parsed until \0 is found, or size
+   bytes has been parsed, so it's possible to give (size_t)-1 as size
+   if the string is \0 terminated. */
 struct message_tokenizer *
 message_tokenize_init(const unsigned char *data, size_t size,
 		      message_tokenize_error_callback_t error_cb,
--- a/src/lib-storage/Makefile.am	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/Makefile.am	Mon Jan 20 16:52:51 2003 +0200
@@ -9,12 +9,8 @@
 
 libstorage_a_SOURCES = \
 	mail-search.c \
-	mail-sort.c \
-	mail-storage.c \
-	mail-thread.c
+	mail-storage.c
 
 noinst_HEADERS = \
 	mail-search.h \
-	mail-sort.h \
-	mail-storage.h \
-	mail-thread.h
+	mail-storage.h
--- a/src/lib-storage/index/Makefile.am	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/Makefile.am	Mon Jan 20 16:52:51 2003 +0200
@@ -13,20 +13,16 @@
 	index-copy.c \
 	index-expunge.c \
 	index-fetch.c \
-	index-fetch-section.c \
+	index-mail.c \
 	index-mailbox-check.c \
 	index-messageset.c \
-	index-msgcache.c \
 	index-save.c \
 	index-search.c \
-	index-sort.c \
 	index-status.c \
 	index-storage.c \
 	index-sync.c \
 	index-update-flags.c
 
 noinst_HEADERS = \
-	index-fetch.h \
 	index-messageset.h \
-	index-sort.h \
 	index-storage.h
--- a/src/lib-storage/index/index-copy.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-copy.c	Mon Jan 20 16:52:51 2003 +0200
@@ -8,51 +8,49 @@
 
 #include <unistd.h>
 
-struct copy_context {
-	struct mailbox *dest;
-	const char **custom_flags;
-	int copy_inside_mailbox;
-};
-
-static int copy_cb(struct mail_index *index, struct mail_index_record *rec,
-		   unsigned int client_seq __attr_unused__,
-		   unsigned int idx_seq __attr_unused__, void *context)
+static int copy_messageset(struct messageset_context *ctx,
+			   struct index_mailbox *src, struct mailbox *dest)
 {
-	struct copy_context *ctx = context;
-	struct index_mailbox *dest_ibox = NULL;
+        const struct messageset_mail *mail;
+	struct mail_full_flags flags;
 	struct istream *input;
 	time_t internal_date;
 	int failed, deleted;
 
-	input = index->open_mail(index, rec, &internal_date, &deleted);
-	if (input == NULL)
-		return FALSE;
+	memset(&flags, 0, sizeof(flags));
+	flags.custom_flags =
+		mail_custom_flags_list_get(src->index->custom_flags);
+	flags.custom_flags_count = MAIL_CUSTOM_FLAGS_COUNT;
 
-	if (ctx->copy_inside_mailbox) {
-                /* kludgy.. */
-		dest_ibox = (struct index_mailbox *) ctx->dest;
-		dest_ibox->delay_save_unlocking = TRUE;
+	while ((mail = index_messageset_next(ctx)) != NULL) {
+		input = src->index->open_mail(src->index, mail->rec,
+					      &internal_date, &deleted);
+		if (input == NULL) {
+			if (deleted)
+				continue;
+			return FALSE;
+		}
+
+		/* save it in destination mailbox */
+		flags.flags = mail->rec->msg_flags;
+		failed = !dest->save(dest, &flags, internal_date, 0,
+				     input, input->v_limit);
+		i_stream_unref(input);
+
+		if (failed)
+			return FALSE;
 	}
 
-	/* save it in destination mailbox */
-	failed = !ctx->dest->save(ctx->dest, rec->msg_flags,
-				  ctx->custom_flags, internal_date, 0,
-				  input, input->v_limit);
-
-	if (ctx->copy_inside_mailbox)
-		dest_ibox->delay_save_unlocking = FALSE;
-
-	i_stream_unref(input);
-	return !failed;
+	return TRUE;
 }
 
 int index_storage_copy(struct mailbox *box, struct mailbox *destbox,
 		       const char *messageset, int uidset)
 {
 	struct index_mailbox *ibox = (struct index_mailbox *) box;
-        struct copy_context ctx;
+        struct messageset_context *ctx;
 	enum mail_lock_type lock_type;
-	int failed;
+	int ret, copy_inside_mailbox;
 
 	if (destbox->readonly) {
 		mail_storage_set_error(box->storage,
@@ -60,15 +58,18 @@
 		return FALSE;
 	}
 
-	ctx.copy_inside_mailbox =
+	copy_inside_mailbox =
 		destbox->storage == box->storage &&
 		strcmp(destbox->name, box->name) == 0;
 
-	if (ctx.copy_inside_mailbox) {
+	if (copy_inside_mailbox) {
 		/* copying inside same mailbox */
 		if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE))
 			return FALSE;
 
+		/* kludgy.. */
+		((struct index_mailbox *) destbox)->delay_save_unlocking = TRUE;
+
 		lock_type = MAIL_LOCK_EXCLUSIVE;
 	} else {
 		lock_type = MAIL_LOCK_SHARED;
@@ -77,15 +78,16 @@
 	if (!index_storage_sync_and_lock(ibox, TRUE, lock_type))
 		return FALSE;
 
-	ctx.custom_flags =
-		mail_custom_flags_list_get(ibox->index->custom_flags);
-	ctx.dest = destbox;
+	ctx = index_messageset_init(ibox, messageset, uidset);
+	ret = copy_messageset(ctx, ibox, destbox);
+	if (index_messageset_deinit(ctx) < 0)
+		ret = FALSE;
 
-	failed = index_messageset_foreach(ibox, messageset, uidset,
-					  copy_cb, &ctx) <= 0;
+	if (copy_inside_mailbox)
+		((struct index_mailbox *) destbox)->delay_save_unlocking = TRUE;
 
 	if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK))
 		return FALSE;
 
-	return !failed;
+	return ret;
 }
--- a/src/lib-storage/index/index-fetch-section.c	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,491 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-#include "lib.h"
-#include "str.h"
-#include "istream.h"
-#include "ostream.h"
-#include "message-send.h"
-#include "index-storage.h"
-#include "index-fetch.h"
-
-#include <ctype.h>
-#include <unistd.h>
-
-struct fetch_header_field_context {
-	string_t *dest;
-	struct ostream *output;
-	uoff_t dest_size;
-
-	uoff_t skip, max_size;
-	const char *const *fields;
-	int (*match_func) (const char *const *, const unsigned char *, size_t);
-};
-
-/* For FETCH[HEADER.FIELDS*] we need to modify the header data before sending
-   it. We can either save it in memory and then send it, or we can parse it
-   twice, first calculating the size and then send it. This value specifies
-   the maximum amount of memory we allow to allocate before using
-   double-parsing. */
-#define MAX_HEADER_BUFFER_SIZE (32*1024)
-
-#define UNSIGNED_CRLF (const unsigned char *) "\r\n"
-
-enum imap_cache_field index_fetch_body_get_cache(const char *section)
-{
-	if (*section >= '0' && *section <= '9')
-		return IMAP_CACHE_MESSAGE_PART | IMAP_CACHE_MESSAGE_OPEN;
-
-	if (*section == '\0' || strcasecmp(section, "TEXT") == 0) {
-		/* no IMAP_CACHE_MESSAGE_BODY_SIZE, so that we don't
-		   uselessly check it when we want to read partial data */
-		return IMAP_CACHE_MESSAGE_OPEN;
-	}
-
-	if (strncasecmp(section, "HEADER", 6) == 0 ||
-	    strcasecmp(section, "MIME") == 0)
-		return IMAP_CACHE_MESSAGE_HDR_SIZE | IMAP_CACHE_MESSAGE_OPEN;
-
-	/* error */
-	return 0;
-}
-
-/* fetch BODY[] or BODY[TEXT] */
-static int fetch_body(struct mail_index_record *rec,
-		      struct mail_fetch_body_data *sect,
-		      struct fetch_context *ctx,
-		      const char *prefix, int fetch_header)
-{
-	struct message_size size;
-	struct istream *input;
-	const char *str;
-	int cr_skipped;
-
-	if (!imap_msgcache_get_rfc822_partial(ctx->cache, sect->skip,
-					      sect->max_size, fetch_header,
-					      &size, &input, &cr_skipped)) {
-		i_error("Couldn't get BODY[] for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-
-	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
-			      prefix, size.virtual_size);
-	if (o_stream_send_str(ctx->output, str) < 0)
-		return FALSE;
-
-	if (cr_skipped)
-		size.virtual_size++;
-
-	return message_send(ctx->output, input, &size,
-			    cr_skipped ? 1 : 0, sect->max_size);
-}
-
-static const char **get_fields_array(const char *fields)
-{
-	const char **field_list, **field;
-
-	while (*fields == ' ')
-		fields++;
-	if (*fields == '(')
-		fields++;
-
-	field_list = t_strsplit(fields, " )");
-
-	/* array ends at ")" element */
-	for (field = field_list; *field != NULL; field++) {
-		if (strcasecmp(*field, ")") == 0)
-			*field = NULL;
-	}
-
-	return field_list;
-}
-
-static int header_match(const char *const *fields,
-			const unsigned char *name, size_t size)
-{
-	const unsigned char *name_start, *name_end;
-	const char *field;
-
-	if (size == 0)
-		return FALSE;
-
-	name_start = name;
-	name_end = name + size;
-
-	for (; *fields != NULL; fields++) {
-		field = *fields;
-		if (*field == '\0')
-			continue;
-
-		for (name = name_start; name != name_end; name++) {
-			/* field has been uppercased long time ago while
-			   parsing FETCH command */
-			if (i_toupper(*name) != *field)
-				break;
-
-			field++;
-			if (*field == '\0') {
-				if (name+1 == name_end)
-					return TRUE;
-				break;
-			}
-		}
-	}
-
-	return FALSE;
-}
-
-static int header_match_not(const char *const *fields,
-			    const unsigned char *name, size_t size)
-{
-	return !header_match(fields, name, size);
-}
-
-static int header_match_mime(const char *const *fields __attr_unused__,
-			     const unsigned char *name, size_t size)
-{
-	if (size > 8 && memcasecmp(name, "Content-", 8) == 0)
-		return TRUE;
-
-	if (size == 12 && memcasecmp(name, "Mime-Version", 12) == 0)
-		return TRUE;
-
-	return FALSE;
-}
-
-static int fetch_header_append(struct fetch_header_field_context *ctx,
-			       const unsigned char *str, size_t size)
-{
-	if (ctx->skip > 0) {
-		if (ctx->skip >= size) {
-			ctx->skip -= size;
-			return TRUE;
-		}
-
-		str += ctx->skip;
-		size -= ctx->skip;
-		ctx->skip = 0;
-	}
-
-	if (ctx->dest_size + size > ctx->max_size) {
-		i_assert(ctx->dest_size <= ctx->max_size);
-		size = ctx->max_size - ctx->dest_size;
-	}
-
-	if (ctx->dest != NULL)
-		str_append_n(ctx->dest, str, size);
-	ctx->dest_size += size;
-
-	if (ctx->output != NULL) {
-		if (o_stream_send(ctx->output, str, size) < 0)
-			return FALSE;
-	}
-	return ctx->dest_size < ctx->max_size;
-}
-
-static void fetch_header_field(struct message_part *part __attr_unused__,
-			       const unsigned char *name, size_t name_len,
-			       const unsigned char *value __attr_unused__,
-			       size_t value_len __attr_unused__,
-			       void *context)
-{
-	struct fetch_header_field_context *ctx = context;
-	const unsigned char *field_start, *field_end, *cr, *p;
-
-	/* see if we want this field. */
-	if (!ctx->match_func(ctx->fields, name, name_len))
-		return;
-
-	/* add the field, inserting CRs when needed. FIXME: is this too
-	   kludgy? we assume name continues with ": value". but otherwise
-	   we wouldn't reply with correct LWSP between ":". */
-	field_start = name;
-	field_end = value + value_len;
-
-	cr = NULL;
-	for (p = field_start; p != field_end; p++) {
-		if (*p == '\r')
-			cr = p;
-		else if (*p == '\n' && cr != p-1) {
-			/* missing CR */
-			if (!fetch_header_append(ctx, field_start,
-						 (size_t) (p-field_start)))
-				return;
-			if (!fetch_header_append(ctx, UNSIGNED_CRLF, 2))
-				return;
-
-			field_start = p+1;
-		}
-	}
-
-	if (field_start != field_end) {
-		if (!fetch_header_append(ctx, field_start,
-					 (size_t) (field_end-field_start)))
-			return;
-	}
-
-	(void)fetch_header_append(ctx, UNSIGNED_CRLF, 2);
-}
-
-static int fetch_header_fields(struct istream *input, const char *section,
-			       struct fetch_header_field_context *ctx)
-{
-	if (strncasecmp(section, "HEADER.FIELDS ", 14) == 0) {
-		ctx->fields = get_fields_array(section + 14);
-		ctx->match_func = header_match;
-	} else if (strncasecmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
-		ctx->fields = get_fields_array(section + 18);
-		ctx->match_func = header_match_not;
-	} else if (strcasecmp(section, "MIME") == 0) {
-		/* Mime-Version + Content-* fields */
-		ctx->match_func = header_match_mime;
-	} else {
-		/* invalid section given by user - FIXME: tell user about it */
-		return FALSE;
-	}
-
-	ctx->dest_size = 0;
-	message_parse_header(NULL, input, NULL, fetch_header_field, ctx);
-
-	/* FIXME: The blank line must not be filtered, says RFC. However, we
-	   shouldn't add it if it wasn't there in the first place. Not very
-	   easy to know currently so we'll just do it always, it'll be present
-	   in all sane messages anyway.. */
-	(void)fetch_header_append(ctx, UNSIGNED_CRLF, 2);
-
-	i_assert(ctx->dest_size <= ctx->max_size);
-	i_assert(ctx->dest == NULL || str_len(ctx->dest) == ctx->dest_size);
-	return TRUE;
-}
-
-/* fetch wanted headers from given data */
-static int fetch_header_from(struct istream *input, struct ostream *output,
-			     const char *prefix, struct message_size *size,
-			     const char *section,
-			     struct mail_fetch_body_data *sect)
-{
-	struct fetch_header_field_context ctx;
-	const char *str;
-	uoff_t start_offset;
-	int failed;
-
-	/* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-
-	if (strcasecmp(section, "HEADER") == 0) {
-		/* all headers */
-		str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
-				      prefix, size->virtual_size);
-		if (o_stream_send_str(output, str) < 0)
-			return FALSE;
-		return message_send(output, input, size,
-				    sect->skip, sect->max_size);
-	}
-
-	/* partial headers - copy the wanted fields into memory, inserting
-	   missing CRs on the way. If the header is too large, calculate 
-	   the size first and then send the data directly to output stream. */
-
-	memset(&ctx, 0, sizeof(ctx));
-	ctx.skip = sect->skip;
-	ctx.max_size = sect->max_size;
-
-	failed = FALSE;
-	start_offset = input->v_offset;
-
-	t_push();
-
-	/* first pass, we need at least the size */
-	if (size->virtual_size > MAX_HEADER_BUFFER_SIZE &&
-	    sect->max_size > MAX_HEADER_BUFFER_SIZE) {
-		if (!fetch_header_fields(input, section, &ctx))
-			failed = TRUE;
-
-		i_assert(ctx.dest_size <= size->virtual_size);
-	} else {
-		ctx.dest = t_str_new(size->virtual_size < 4096 ?
-				     size->virtual_size : 4096);
-		if (!fetch_header_fields(input, section, &ctx))
-			failed = TRUE;
-	}
-
-	if (!failed) {
-		str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
-				      prefix, ctx.dest_size);
-		if (o_stream_send_str(output, str) < 0)
-			failed = TRUE;
-	}
-
-	if (!failed) {
-		if (ctx.dest == NULL) {
-			/* second pass, write the data to output stream */
-			uoff_t first_size = ctx.dest_size;
-
-			ctx.output = output;
-			i_stream_seek(input, start_offset);
-
-			if (!failed &&
-			    !fetch_header_fields(input, section, &ctx))
-				failed = TRUE;
-
-			i_assert(first_size == ctx.dest_size);
-		} else {
-			if (o_stream_send(output, str_c(ctx.dest),
-					  str_len(ctx.dest)) < 0)
-				failed = TRUE;
-		}
-	}
-
-	t_pop();
-	return !failed;
-}
-
-/* fetch BODY[HEADER...] */
-static int fetch_header(struct mail_fetch_body_data *sect,
-			struct fetch_context *ctx, const char *prefix)
-{
-	struct message_size hdr_size;
-	struct istream *input;
-
-	if (!imap_msgcache_get_rfc822(ctx->cache, &input, &hdr_size, NULL))
-		return FALSE;
-
-	return fetch_header_from(input, ctx->output, prefix, &hdr_size,
-				 sect->section, sect);
-}
-
-/* Find message_part for section (eg. 1.3.4) */
-static struct message_part *
-part_find(struct mail_fetch_body_data *sect, struct fetch_context *ctx,
-	  const char **section)
-{
-	struct message_part *part;
-	const char *path;
-	unsigned int num;
-
-	part = imap_msgcache_get_parts(ctx->cache);
-
-	path = sect->section;
-	while (*path >= '0' && *path <= '9' && part != NULL) {
-		/* get part number */
-		num = 0;
-		while (*path != '\0' && *path != '.') {
-			if (*path < '0' || *path > '9')
-				return NULL;
-			num = num*10 + (*path - '0');
-			path++;
-		}
-
-		if (*path == '.')
-			path++;
-
-		if (part->flags & MESSAGE_PART_FLAG_MULTIPART) {
-			/* find the part */
-			part = part->children;
-			for (; num > 1 && part != NULL; num--)
-				part = part->next;
-		} else {
-			/* only 1 allowed with non-multipart messages */
-			if (num != 1)
-				return NULL;
-		}
-
-		if (part != NULL &&
-		    (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822)) {
-			/* skip the message/rfc822 part */
-			part = part->children;
-		}
-	}
-
-	*section = path;
-	return part;
-}
-
-/* fetch BODY[1.2] or BODY[1.2.TEXT] */
-static int fetch_part_body(struct message_part *part,
-			   struct mail_fetch_body_data *sect,
-			   struct fetch_context *ctx, const char *prefix)
-{
-	struct istream *input;
-	const char *str;
-	uoff_t skip_pos;
-
-	if (!imap_msgcache_get_data(ctx->cache, &input))
-		return FALSE;
-
-	/* jump to beginning of wanted data */
-	skip_pos = part->physical_pos + part->header_size.physical_size;
-	i_stream_skip(input, skip_pos);
-
-	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
-			      prefix, part->body_size.virtual_size);
-	if (o_stream_send_str(ctx->output, str) < 0)
-		return FALSE;
-
-	/* FIXME: potential performance problem with big messages:
-	   FETCH BODY[1]<100000..1024>, hopefully no clients do this */
-	return message_send(ctx->output, input, &part->body_size,
-			    sect->skip, sect->max_size);
-}
-
-/* fetch BODY[1.2.MIME|HEADER...] */
-static int fetch_part_header(struct message_part *part, const char *section,
-			     struct mail_fetch_body_data *sect,
-			     struct fetch_context *ctx, const char *prefix)
-{
-	struct istream *input;
-
-	if (!imap_msgcache_get_data(ctx->cache, &input))
-		return FALSE;
-
-	i_stream_skip(input, part->physical_pos);
-	return fetch_header_from(input, ctx->output, prefix, &part->header_size,
-				 section, sect);
-}
-
-static int fetch_part(struct mail_fetch_body_data *sect,
-		      struct fetch_context *ctx, const char *prefix)
-{
-	struct message_part *part;
-	const char *section;
-
-	part = part_find(sect, ctx, &section);
-	if (part == NULL)
-		return FALSE;
-
-	if (*section == '\0' || strcasecmp(section, "TEXT") == 0)
-		return fetch_part_body(part, sect, ctx, prefix);
-
-	if (strncasecmp(section, "HEADER", 6) == 0)
-		return fetch_part_header(part, section, sect, ctx, prefix);
-	if (strcasecmp(section, "MIME") == 0)
-		return fetch_part_header(part, section, sect, ctx, prefix);
-
-	return FALSE;
-}
-
-int index_fetch_body_section(struct mail_index_record *rec,
-			     struct mail_fetch_body_data *sect,
-			     struct fetch_context *ctx)
-{
-	const char *prefix;
-
-	prefix = !sect->skip_set ?
-		t_strdup_printf(" BODY[%s]", sect->section) :
-		t_strdup_printf(" BODY[%s]<%"PRIuUOFF_T">",
-				sect->section, sect->skip);
-	if (ctx->first) {
-		prefix++; ctx->first = FALSE;
-	}
-
-	if (*sect->section == '\0')
-		return fetch_body(rec, sect, ctx, prefix, TRUE);
-	if (strcasecmp(sect->section, "TEXT") == 0)
-		return fetch_body(rec, sect, ctx, prefix, FALSE);
-	if (strncasecmp(sect->section, "HEADER", 6) == 0)
-		return fetch_header(sect, ctx, prefix);
-	if (*sect->section >= '0' && *sect->section <= '9')
-		return fetch_part(sect, ctx, prefix);
-
-	/* FIXME: point the error to user */
-	return FALSE;
-}
--- a/src/lib-storage/index/index-fetch.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-fetch.c	Mon Jan 20 16:52:51 2003 +0200
@@ -3,414 +3,157 @@
 #include "lib.h"
 #include "ostream.h"
 #include "str.h"
+#include "mail-index.h"
+#include "mail-modifylog.h"
 #include "mail-custom-flags.h"
 #include "index-storage.h"
-#include "index-fetch.h"
 #include "index-messageset.h"
-#include "message-send.h"
-#include "imap-date.h"
-#include "imap-util.h"
-#include "imap-message-cache.h"
-
-#include <unistd.h>
-
-static int index_fetch_internaldate(struct mail_index_record *rec,
-				    struct fetch_context *ctx)
-{
-	time_t date;
-
-	date = imap_msgcache_get_internal_date(ctx->cache);
-	if (date != (time_t)-1) {
-		str_printfa(ctx->str, "INTERNALDATE \"%s\" ",
-			    imap_to_datetime(date));
-		return TRUE;
-	} else {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't generate INTERNALDATE for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-}
-
-static int index_fetch_body(struct mail_index_record *rec,
-			    struct fetch_context *ctx)
-{
-	const char *body;
-
-	body = imap_msgcache_get(ctx->cache, IMAP_CACHE_BODY);
-	if (body != NULL) {
-		str_printfa(ctx->str, "BODY (%s) ", body);
-		return TRUE;
-	} else {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't generate BODY for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-}
-
-static int index_fetch_bodystructure(struct mail_index_record *rec,
-				     struct fetch_context *ctx)
-{
-	const char *bodystructure;
-
-	bodystructure = imap_msgcache_get(ctx->cache, IMAP_CACHE_BODYSTRUCTURE);
-	if (bodystructure != NULL) {
-		str_printfa(ctx->str, "BODYSTRUCTURE (%s) ", bodystructure);
-		return TRUE;
-	} else {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't generate BODYSTRUCTURE for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-}
-
-static int index_fetch_envelope(struct mail_index_record *rec,
-				struct fetch_context *ctx)
-{
-	const char *envelope;
-
-	envelope = imap_msgcache_get(ctx->cache, IMAP_CACHE_ENVELOPE);
-	if (envelope != NULL) {
-		str_printfa(ctx->str, "ENVELOPE (%s) ", envelope);
-		return TRUE;
-	} else {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't generate ENVELOPE for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-}
-
-static int index_fetch_rfc822_size(struct mail_index_record *rec,
-				   struct fetch_context *ctx)
-{
-	uoff_t size;
+#include "index-mail.h"
 
-	size = imap_msgcache_get_virtual_size(ctx->cache);
-	if (size == (uoff_t)-1) {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't get RFC822.SIZE for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-
-	str_printfa(ctx->str, "RFC822.SIZE %"PRIuUOFF_T" ", size);
-	return TRUE;
-}
-
-static void index_fetch_flags(struct mail_index_record *rec,
-			      struct fetch_context *ctx)
-{
-	enum mail_flags flags;
-
-	flags = rec->msg_flags;
-	if (rec->uid >= ctx->index->first_recent_uid)
-		flags |= MAIL_RECENT;
-	if (ctx->update_seen)
-		flags |= MAIL_SEEN;
-
-	str_printfa(ctx->str, "FLAGS (%s) ",
-		    imap_write_flags(flags, ctx->custom_flags,
-				     ctx->custom_flags_count));
-}
-
-static void index_fetch_uid(struct mail_index_record *rec,
-			    struct fetch_context *ctx)
-{
-	str_printfa(ctx->str, "UID %u ", rec->uid);
-}
-
-static int index_fetch_send_rfc822(struct mail_index_record *rec,
-				   struct fetch_context *ctx)
-{
-	struct message_size hdr_size, body_size;
-	struct istream *input;
-	const char *str;
-
-	if (!imap_msgcache_get_rfc822(ctx->cache, &input,
-				      &hdr_size, &body_size)) {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't get RFC822 for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-
-	str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n",
-			      hdr_size.virtual_size + body_size.virtual_size);
-	if (ctx->first) {
-		str++; ctx->first = FALSE;
-	}
-	if (o_stream_send_str(ctx->output, str) < 0)
-		return FALSE;
-
-	body_size.physical_size += hdr_size.physical_size;
-	body_size.virtual_size += hdr_size.virtual_size;
-	return message_send(ctx->output, input, &body_size, 0, (uoff_t)-1);
-}
-
-static int index_fetch_send_rfc822_header(struct mail_index_record *rec,
-					  struct fetch_context *ctx)
-{
-	struct message_size hdr_size;
-	struct istream *input;
-	const char *str;
-
-	if (!imap_msgcache_get_rfc822(ctx->cache, &input, &hdr_size, NULL)) {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't get RFC822.HEADER for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-
-	str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n",
-			      hdr_size.virtual_size);
-	if (ctx->first) {
-		str++; ctx->first = FALSE;
-	}
-	if (o_stream_send_str(ctx->output, str) < 0)
-		return FALSE;
-
-	return message_send(ctx->output, input, &hdr_size, 0, (uoff_t)-1);
-}
+struct mail_fetch_context {
+	struct index_mailbox *ibox;
+	struct mail_index *index;
 
-static int index_fetch_send_rfc822_text(struct mail_index_record *rec,
-					struct fetch_context *ctx)
-{
-	struct message_size body_size;
-	struct istream *input;
-	const char *str;
-
-	if (!imap_msgcache_get_rfc822(ctx->cache, &input, NULL, &body_size)) {
-		mail_storage_set_critical(ctx->storage,
-			"Couldn't get RFC822.TEXT for UID %u (index %s)",
-			rec->uid, ctx->index->filepath);
-		return FALSE;
-	}
-
-	str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n",
-			      body_size.virtual_size);
-	if (ctx->first) {
-		str++; ctx->first = FALSE;
-	}
-	if (o_stream_send_str(ctx->output, str) < 0)
-		return FALSE;
-
-	return message_send(ctx->output, input, &body_size, 0, (uoff_t)-1);
-}
-
-static enum imap_cache_field index_get_cache(struct mail_fetch_data *fetch_data)
-{
-	struct mail_fetch_body_data *sect;
-	enum imap_cache_field field;
-
-	field = 0;
-	if (fetch_data->body)
-		field |= IMAP_CACHE_BODY;
-	if (fetch_data->bodystructure)
-		field |= IMAP_CACHE_BODYSTRUCTURE;
-	if (fetch_data->envelope)
-		field |= IMAP_CACHE_ENVELOPE;
-	if (fetch_data->internaldate)
-		field |= IMAP_CACHE_INTERNALDATE;
-
-	if (fetch_data->rfc822_size)
-		field |= IMAP_CACHE_VIRTUAL_SIZE;
-	if (fetch_data->rfc822) {
-		field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE |
-			IMAP_CACHE_MESSAGE_BODY_SIZE;
-	}
-	if (fetch_data->rfc822_header)
-		field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE;
-	if (fetch_data->rfc822_text)
-		field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_BODY_SIZE;
-
-	/* check what body[] sections want */
-	sect = fetch_data->body_sections;
-	for (; sect != NULL; sect = sect->next)
-		field |= index_fetch_body_get_cache(sect->section);
-	return field;
-}
-
-static int fetch_msgcache_open(struct fetch_context *ctx,
-			       struct mail_index_record *rec)
-{
-	enum imap_cache_field fields;
-
-	fields = index_get_cache(ctx->fetch_data);
-	if (fields == 0)
-		return TRUE;
-
-	return index_msgcache_open(ctx->cache, ctx->index, rec, fields);
-}
-
-static int index_fetch_mail(struct mail_index *index __attr_unused__,
-			    struct mail_index_record *rec,
-			    unsigned int client_seq, unsigned int idx_seq,
-			    void *context)
-{
-	struct fetch_context *ctx = context;
-	struct mail_fetch_body_data *sect;
-	size_t len, orig_len;
-	int failed, data_written, fetch_flags;
+	struct messageset_context *msgset_ctx;
+	struct index_mail mail;
 
-	/* first see what we need to do. this way we don't first do some
-	   light parsing and later notice that we need to do heavier parsing
-	   anyway */
-	if (!fetch_msgcache_open(ctx, rec)) {
-		/* most likely message not found, just ignore it. */
-		imap_msgcache_close(ctx->cache);
-		ctx->failed = TRUE;
-		return TRUE;
-	}
-
-	if (ctx->update_seen && (rec->msg_flags & MAIL_SEEN) == 0) {
-		(void)index->update_flags(index, rec, idx_seq,
-					  rec->msg_flags | MAIL_SEEN, FALSE);
-		fetch_flags = TRUE;
-	} else {
-		fetch_flags = FALSE;
-	}
-
-	ctx->str = t_str_new(2048);
-
-	str_printfa(ctx->str, "* %u FETCH (", client_seq);
-	orig_len = str_len(ctx->str);
-
-	failed = TRUE;
-	data_written = FALSE;
-	do {
-		/* these can't fail */
-		if (ctx->fetch_data->uid)
-			index_fetch_uid(rec, ctx);
-		if (ctx->fetch_data->flags || fetch_flags)
-			index_fetch_flags(rec, ctx);
+	int update_seen;
+};
 
-		/* rest can */
-		if (ctx->fetch_data->internaldate)
-			if (!index_fetch_internaldate(rec, ctx))
-				break;
-		if (ctx->fetch_data->body)
-			if (!index_fetch_body(rec, ctx))
-				break;
-		if (ctx->fetch_data->bodystructure)
-			if (!index_fetch_bodystructure(rec, ctx))
-				break;
-		if (ctx->fetch_data->envelope)
-			if (!index_fetch_envelope(rec, ctx))
-				break;
-		if (ctx->fetch_data->rfc822_size)
-			if (!index_fetch_rfc822_size(rec, ctx))
-				break;
-
-		/* send the data written into temp string,
-		   not including the trailing zero */
-		ctx->first = str_len(ctx->str) == orig_len;
-		len = str_len(ctx->str);
-		if (len > 0) {
-			if (!ctx->first)
-				str_truncate(ctx->str, --len);
-
-			if (o_stream_send(ctx->output,
-					  str_c(ctx->str), len) < 0)
-				break;
-		}
-
-		data_written = TRUE;
-
-		/* large data */
-		if (ctx->fetch_data->rfc822)
-			if (!index_fetch_send_rfc822(rec, ctx))
-				break;
-		if (ctx->fetch_data->rfc822_text)
-			if (!index_fetch_send_rfc822_text(rec, ctx))
-				break;
-		if (ctx->fetch_data->rfc822_header)
-			if (!index_fetch_send_rfc822_header(rec, ctx))
-				break;
-
-		sect = ctx->fetch_data->body_sections;
-		for (; sect != NULL; sect = sect->next) {
-			if (!index_fetch_body_section(rec, sect, ctx))
-				break;
-		}
-
-		failed = FALSE;
-	} while (0);
-
-	if (data_written) {
-		if (o_stream_send(ctx->output, ")\r\n", 3) < 0)
-			failed = TRUE;
-	}
-
-	imap_msgcache_close(ctx->cache);
-	return !failed;
-}
-
-int index_storage_fetch(struct mailbox *box, struct mail_fetch_data *fetch_data,
-			struct ostream *output, int *all_found)
+struct mail_fetch_context *
+index_storage_fetch_init(struct mailbox *box,
+			 enum mail_fetch_field wanted_fields, int *update_seen,
+			 const char *messageset, int uidset)
 {
 	struct index_mailbox *ibox = (struct index_mailbox *) box;
-	struct fetch_context ctx;
-	struct mail_fetch_body_data *sect;
-	int ret;
-
-	memset(&ctx, 0, sizeof(ctx));
+        struct mail_fetch_context *ctx;
 
-	if (!box->readonly) {
-		/* If we have any BODY[..] sections, \Seen flag is added for
-		   all messages */
-		sect = fetch_data->body_sections;
-		for (; sect != NULL; sect = sect->next) {
-			if (!sect->peek) {
-				ctx.update_seen = TRUE;
-				break;
-			}
-		}
+	ctx = i_new(struct mail_fetch_context, 1);
 
-		if (fetch_data->rfc822 || fetch_data->rfc822_text)
-			ctx.update_seen = TRUE;
-	}
+	if (!box->readonly)
+		*update_seen = FALSE;
 
 	/* need exclusive lock to update the \Seen flags */
-	if (ctx.update_seen) {
+	if (*update_seen) {
 		if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE))
-			return FALSE;
+			return NULL;
 	}
 
 	if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED))
-		return FALSE;
+		return NULL;
 
-	if (ctx.update_seen &&
+	if (*update_seen &&
 	    ibox->index->header->messages_count ==
 	    ibox->index->header->seen_messages_count) {
 		/* if all messages are already seen, there's no point in
 		   keeping exclusive lock */
-		ctx.update_seen = FALSE;
+		*update_seen = FALSE;
 		(void)index_storage_lock(ibox, MAIL_LOCK_SHARED);
 	}
 
-	ctx.box = box;
-	ctx.storage = box->storage;
-	ctx.cache = ibox->cache;
-	ctx.index = ibox->index;
-	ctx.custom_flags =
-		mail_custom_flags_list_get(ibox->index->custom_flags);
-        ctx.custom_flags_count = MAIL_CUSTOM_FLAGS_COUNT;
+	ctx->ibox = ibox;
+	ctx->index = ibox->index;
+	ctx->update_seen = *update_seen;
 
-	ctx.fetch_data = fetch_data;
-	ctx.output = output;
+	index_mail_init(ibox, &ctx->mail, wanted_fields, NULL);
+	ctx->msgset_ctx = index_messageset_init(ibox, messageset, uidset);
+	return ctx;
+}
 
-	ret = index_messageset_foreach(ibox, fetch_data->messageset,
-				       fetch_data->uidset,
-				       index_fetch_mail, &ctx);
+int index_storage_fetch_deinit(struct mail_fetch_context *ctx, int *all_found)
+{
+	int ret;
 
-	if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK))
-		return FALSE;
+	ret = index_messageset_deinit(ctx->msgset_ctx);
 
 	if (all_found != NULL)
-		*all_found = ret == 1 && !ctx.failed;
+		*all_found = ret > 0;
+
+	if (!index_storage_lock(ctx->ibox, MAIL_LOCK_UNLOCK))
+		ret = -1;
+
+	if (ctx->ibox->fetch_mail.pool != NULL)
+		index_mail_deinit(&ctx->ibox->fetch_mail);
+	index_mail_deinit(&ctx->mail);
+	i_free(ctx);
+	return ret >= 0;
+}
+
+struct mail *index_storage_fetch_next(struct mail_fetch_context *ctx)
+{
+	const struct messageset_mail *msgset_mail;
+	struct mail_index_record *rec;
+	int ret;
+
+	do {
+		msgset_mail = index_messageset_next(ctx->msgset_ctx);
+		if (msgset_mail == NULL)
+			return NULL;
+
+		rec = msgset_mail->rec;
+		ctx->mail.mail.seen_updated = FALSE;
+		if (ctx->update_seen && (rec->msg_flags & MAIL_SEEN) == 0) {
+			if (ctx->index->update_flags(ctx->index, rec,
+						     msgset_mail->idx_seq,
+						     rec->msg_flags | MAIL_SEEN,
+						     FALSE))
+				ctx->mail.mail.seen_updated = TRUE;
+		}
+
+		ctx->mail.mail.seq = msgset_mail->client_seq;
+		ctx->mail.mail.uid = rec->uid;
+
+		ret = index_mail_next(&ctx->mail, rec);
+	} while (ret == 0);
+
+	return ret < 0 ? NULL : &ctx->mail.mail;
+}
 
-	return ret > 0;
+static struct mail *
+fetch_record(struct index_mailbox *ibox, struct mail_index_record *rec,
+	     enum mail_fetch_field wanted_fields)
+{
+	if (ibox->fetch_mail.pool != NULL)
+		index_mail_deinit(&ibox->fetch_mail);
+
+	index_mail_init(ibox, &ibox->fetch_mail, wanted_fields, NULL);
+	if (index_mail_next(&ibox->fetch_mail, rec) <= 0)
+		return NULL;
+
+	return &ibox->fetch_mail.mail;
 }
+
+struct mail *index_storage_fetch_uid(struct mailbox *box, unsigned int uid,
+				     enum mail_fetch_field wanted_fields)
+{
+	struct index_mailbox *ibox = (struct index_mailbox *) box;
+        struct mail_index_record *rec;
+
+	i_assert(ibox->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	rec = ibox->index->lookup_uid_range(ibox->index, uid, uid, NULL);
+	if (rec == NULL)
+		return NULL;
+
+	return fetch_record(ibox, rec, wanted_fields);
+}
+
+struct mail *index_storage_fetch_seq(struct mailbox *box, unsigned int seq,
+				     enum mail_fetch_field wanted_fields)
+{
+	struct index_mailbox *ibox = (struct index_mailbox *) box;
+        struct mail_index_record *rec;
+	unsigned int expunges_before;
+
+	i_assert(ibox->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (mail_modifylog_seq_get_expunges(ibox->index->modifylog, seq, seq,
+					    &expunges_before) == NULL)
+		return NULL;
+
+	rec = ibox->index->lookup(ibox->index, seq - expunges_before);
+	if (rec == NULL)
+		return NULL;
+
+	return fetch_record(ibox, rec, wanted_fields);
+}
--- a/src/lib-storage/index/index-fetch.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-fetch.h	Mon Jan 20 16:52:51 2003 +0200
@@ -10,7 +10,7 @@
 	const char **custom_flags;
 	unsigned int custom_flags_count;
 
-	struct mail_fetch_data *fetch_data;
+	//struct mail_fetch_data *fetch_data;
 	struct ostream *output;
 	string_t *str;
 	int update_seen, failed;
--- a/src/lib-storage/index/index-messageset.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-messageset.c	Mon Jan 20 16:52:51 2003 +0200
@@ -1,11 +1,87 @@
-/* Copyright (C) 2002 Timo Sirainen */
+/* Copyright (C) 2002-2003 Timo Sirainen */
 
 #include "lib.h"
 #include "mail-index.h"
 #include "mail-index-util.h"
 #include "mail-modifylog.h"
+#include "index-storage.h"
 #include "index-messageset.h"
 
+struct messageset_context {
+	struct index_mailbox *ibox;
+	struct mail_index *index;
+
+	const struct modify_log_expunge *expunges;
+	int expunges_found;
+
+	struct messageset_mail mail;
+	unsigned int messages_count;
+	unsigned int num1, num2;
+
+	const char *messageset, *p;
+	int uidset;
+
+	int first, ret;
+	const char *error;
+};
+
+static int uidset_init(struct messageset_context *ctx);
+static int seqset_init(struct messageset_context *ctx);
+
+struct messageset_context *
+index_messageset_init(struct index_mailbox *ibox,
+		      const char *messageset, int uidset)
+{
+	struct messageset_context *ctx;
+
+	i_assert(ibox->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	ctx = i_new(struct messageset_context, 1);
+	ctx->ibox = ibox;
+	ctx->index = ibox->index;
+	ctx->messages_count = ibox->synced_messages_count;
+	ctx->p = ctx->messageset = messageset;
+	ctx->uidset = uidset;
+
+	/* Reset index errors, we rely on it to check for failures */
+	index_reset_error(ctx->index);
+
+	return ctx;
+}
+
+struct messageset_context *
+index_messageset_init_range(struct index_mailbox *ibox,
+			    unsigned int num1, unsigned int num2, int uidset)
+{
+	struct messageset_context *ctx;
+
+	ctx = index_messageset_init(ibox, NULL, uidset);
+	ctx->num1 = num1;
+	ctx->num2 = num2;
+	return ctx;
+}
+
+int index_messageset_deinit(struct messageset_context *ctx)
+{
+	int ret = ctx->ret;
+
+	if (ret == 1 && ctx->expunges_found) {
+		/* some of the messages weren't found */
+		ret = 0;
+	}
+
+	if (ret == -1)
+		mail_storage_set_index_error(ctx->ibox);
+	else if (ret == -2) {
+		/* user error */
+		mail_storage_set_syntax_error(ctx->ibox->box.storage,
+					      "%s", ctx->error);
+	}
+
+	i_free(ctx);
+	return ret;
+}
+
 static unsigned int get_next_number(const char **str)
 {
 	unsigned int num;
@@ -22,321 +98,194 @@
 	return num;
 }
 
-static int mail_index_foreach(struct mail_index *index,
-			      unsigned int seq, unsigned int seq2,
-			      msgset_foreach_callback_t callback, void *context)
+static int messageset_parse_next(struct messageset_context *ctx)
 {
-	struct mail_index_record *rec;
-	const struct modify_log_expunge *expunges;
-	unsigned int idx_seq, expunges_before, temp;
-	int expunges_found;
+	if (ctx->p == NULL) {
+		/* num1..num2 already set.  */
+		ctx->p = "";
+		return TRUE;
+	}
+
+	if (*ctx->p == '*') {
+		/* last message */
+		ctx->num1 = (unsigned int)-1;
+		ctx->p++;
+	} else {
+		ctx->num1 = get_next_number(&ctx->p);
+		if (ctx->num1 == 0) {
+			ctx->error = t_strconcat("Invalid messageset: ",
+						 ctx->messageset, NULL);
+			return FALSE;
+		}
+	}
+
+	if (*ctx->p != ':')
+		ctx->num2 = ctx->num1;
+	else {
+		/* first:last range */
+		ctx->p++;
+
+		if (*ctx->p == '*') {
+			ctx->num2 = (unsigned int)-1;
+			ctx->p++;
+		} else {
+			ctx->num2 = get_next_number(&ctx->p);
+			if (ctx->num2 == 0) {
+				ctx->error = t_strconcat("Invalid messageset: ",
+							 ctx->messageset, NULL);
+				return FALSE;
+			}
+		}
+	}
+
+	if (*ctx->p == ',')
+		ctx->p++;
+	else if (*ctx->p != '\0') {
+		ctx->error = t_strdup_printf("Unexpected char '%c' "
+					     "with messageset: %s",
+					     *ctx->p, ctx->messageset);
+		return FALSE;
+	}
 
-	if (seq > seq2) {
-		/* swap, as specified by latest IMAP4rev1 spec */
-		temp = seq;
-		seq = seq2;
-		seq2 = temp;
+	if (ctx->num1 > ctx->num2) {
+		/* swap, as specified by latest IMAP4rev1 draft */
+		unsigned int temp = ctx->num1;
+		ctx->num1 = ctx->num2;
+		ctx->num2 = temp;
+	}
+
+	return TRUE;
+}
+
+static int uidset_init(struct messageset_context *ctx)
+{
+	unsigned int expunges_before;
+
+	if (ctx->num1 == (unsigned int)-1) {
+		struct mail_index_record *rec;
+
+		rec = ctx->index->lookup(ctx->index, ctx->messages_count);
+		ctx->num1 = rec == NULL ? 0 : rec->uid;
 	}
 
+	if (ctx->num2 == (unsigned int)-1)
+		ctx->num2 = ctx->index->header->next_uid-1;
+
+	/* get list of expunged messages in our range. */
+	ctx->expunges = mail_modifylog_uid_get_expunges(ctx->index->modifylog,
+							ctx->num1, ctx->num2,
+							&expunges_before);
+	if (ctx->expunges == NULL)
+		return -1;
+
+	if (ctx->expunges->uid1 != 0)
+		ctx->expunges_found = TRUE;
+
+	/* get the first message */
+	ctx->mail.rec = ctx->index->lookup_uid_range(ctx->index,
+						     ctx->num1, ctx->num2,
+						     &ctx->mail.idx_seq);
+	if (ctx->mail.rec == NULL) {
+		return ctx->index->get_last_error(ctx->index) ==
+			MAIL_INDEX_ERROR_NONE ? 1 : -1;
+	}
+
+	ctx->mail.client_seq = ctx->mail.idx_seq + expunges_before;
+	return 0;
+}
+
+static int seqset_init(struct messageset_context *ctx)
+{
+	unsigned int expunges_before;
+
+	if (ctx->num1 == (unsigned int)-1)
+		ctx->num1 = ctx->messages_count;
+
+	if (ctx->num2 == (unsigned int)-1)
+		ctx->num2 = ctx->messages_count;
+
 	/* get list of expunged messages in our range. the expunges_before
 	   can be used to calculate the current real sequence position */
-	expunges = mail_modifylog_seq_get_expunges(index->modifylog, seq, seq2,
-						   &expunges_before);
-	if (expunges == NULL)
+	ctx->expunges = mail_modifylog_seq_get_expunges(ctx->index->modifylog,
+							ctx->num1, ctx->num2,
+							&expunges_before);
+	if (ctx->expunges == NULL)
 		return -1;
 
-	i_assert(expunges_before < seq);
-	expunges_found = expunges->uid1 != 0;
-
-	/* Reset index errors, since we later rely on it to check if failed */
-	index_reset_error(index);
+	i_assert(expunges_before < ctx->num1);
+	if (ctx->expunges->uid1 != 0)
+		ctx->expunges_found = TRUE;
 
 	/* get the first non-expunged message. note that if all messages
 	   were expunged in the range, this points outside wanted range. */
-	idx_seq = seq - expunges_before;
-	rec = index->lookup(index, idx_seq);
-	for (; rec != NULL; seq++, idx_seq++) {
-		/* skip expunged sequences */
-		i_assert(rec->uid != 0);
-
-		while (expunges->uid1 != 0 && expunges->uid1 < rec->uid) {
-			i_assert(expunges->uid2 < rec->uid);
-
-			seq += expunges->seq_count;
-			expunges++;
-		}
-		i_assert(!(expunges->uid1 <= rec->uid &&
-			   expunges->uid2 >= rec->uid));
-
-		if (seq > seq2)
-			break;
-
-		t_push();
-		if (!callback(index, rec, seq, idx_seq, context)) {
-			t_pop();
-			return 0;
-		}
-		t_pop();
-
-		rec = index->next(index, rec);
-	}
-
-	if (rec == NULL &&
-	    index->get_last_error(index) != MAIL_INDEX_ERROR_NONE) {
-		/* error occured */
-		return -1;
-	}
-
-	return !expunges_found && seq > seq2 ? 1 : 2;
-}
-
-static int mail_index_messageset_foreach(struct mail_index *index,
-					 const char *messageset,
-					 unsigned int messages_count,
-					 msgset_foreach_callback_t callback,
-					 void *context, const char **error)
-{
-	const char *input;
-	unsigned int seq, seq2;
-	int ret, all_found;
-
-	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
-
-	*error = NULL;
-	if (messages_count == 0) {
-		*error = "No messages in mailbox";
-		return -2;
-	}
-
-	all_found = TRUE;
-	input = messageset;
-	while (*input != '\0') {
-		if (*input == '*') {
-			/* last message */
-			seq = messages_count;
-			input++;
-		} else {
-			seq = get_next_number(&input);
-			if (seq == 0) {
-				*error = t_strconcat("Invalid messageset: ",
-						     messageset, NULL);
-				return -2;
-			}
-		}
-
-		if (*input != ':')
-			seq2 = seq;
-		else {
-			/* first:last range */
-			input++;
-
-			if (*input != '*') {
-				seq2 = get_next_number(&input);
-				if (seq2 == 0) {
-					*error = t_strconcat("Invalid "
-							     "messageset: ",
-							     messageset, NULL);
-					return -2;
-				}
-			} else {
-				seq2 = messages_count;
-				input++;
-			}
-		}
-
-		if (*input == ',')
-			input++;
-		else if (*input != '\0') {
-			*error = t_strdup_printf("Unexpected char '%c' "
-						 "with messageset: %s",
-						 *input, messageset);
-			return -2;
-		}
-
-		if (seq > messages_count || seq2 > messages_count) {
-			/* non-existent messages requested */
-			if (seq <= messages_count)
-				seq = seq2;
-			*error = t_strdup_printf("Message sequence %u "
-						 "larger than mailbox size %u",
-						 seq, messages_count);
-			return -2;
-		}
-
-		t_push();
-		ret = mail_index_foreach(index, seq, seq2, callback, context);
-		t_pop();
-		if (ret <= 0)
-			return ret;
-		if (ret == 2)
-			all_found = FALSE;
-	}
-
-	return all_found ? 1 : 2;
-}
-
-static int mail_index_uid_foreach(struct mail_index *index,
-				  unsigned int uid, unsigned int uid2,
-				  msgset_foreach_callback_t callback,
-				  void *context)
-{
-	struct mail_index_record *rec;
-	const struct modify_log_expunge *expunges;
-	unsigned int client_seq, idx_seq, expunges_before, temp;
-	int expunges_found;
-
-	if (uid > uid2) {
-		/* swap, as specified by latest IMAP4rev1 spec */
-		temp = uid;
-		uid = uid2;
-		uid2 = temp;
+	ctx->mail.idx_seq = ctx->num1 - expunges_before;
+	ctx->mail.rec = ctx->index->lookup(ctx->index, ctx->mail.idx_seq);
+	if (ctx->mail.rec == NULL) {
+		return ctx->index->get_last_error(ctx->index) ==
+			MAIL_INDEX_ERROR_NONE ? 1 : -1;
 	}
 
-	/* get list of expunged messages in our range. */
-	expunges = mail_modifylog_uid_get_expunges(index->modifylog, uid, uid2,
-						   &expunges_before);
-	if (expunges == NULL)
-		return -1;
-
-	expunges_found = expunges->uid1 != 0;
-
-	rec = index->lookup_uid_range(index, uid, uid2, &idx_seq);
-	if (rec == NULL)
-		return expunges_found ? 2 : 1;
-
-	client_seq = idx_seq + expunges_before;
-	while (rec != NULL && rec->uid <= uid2) {
-		while (expunges->uid1 != 0 && expunges->uid1 < rec->uid) {
-			i_assert(expunges->uid2 < rec->uid);
-
-			client_seq += expunges->seq_count;
-			expunges++;
-		}
-		i_assert(!(expunges->uid1 <= rec->uid &&
-			   expunges->uid2 >= rec->uid));
-
-		t_push();
-		if (!callback(index, rec, client_seq, idx_seq, context)) {
-			t_pop();
-			return 0;
-		}
-		t_pop();
-
-		client_seq++; idx_seq++;
-		rec = index->next(index, rec);
-	}
-
-	if (rec == NULL &&
-	    index->get_last_error(index) != MAIL_INDEX_ERROR_NONE) {
-		/* error occured */
-		return -1;
-	}
-
-	return expunges_found ? 2 : 1;
+	ctx->mail.client_seq = ctx->num1;
+	return 0;
 }
 
-static int mail_index_uidset_foreach(struct mail_index *index,
-				     const char *uidset,
-				     unsigned int messages_count,
-				     msgset_foreach_callback_t callback,
-				     void *context, const char **error)
+const struct messageset_mail *
+index_messageset_next(struct messageset_context *ctx)
 {
-	struct mail_index_record *rec;
-	const char *input;
-	unsigned int uid, uid2;
-	int ret, all_found;
-
-	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
-
-	*error = NULL;
+	struct messageset_mail *mail = &ctx->mail;
+	int last;
 
-	all_found = TRUE;
-	input = uidset;
-	while (*input != '\0') {
-		if (*input == '*') {
-			/* last message */
-			if (messages_count == 0)
-				uid = 0;
-			else {
-				rec = index->lookup(index, messages_count);
-				uid = rec == NULL ? 0 : rec->uid;
-			}
-			input++;
-		} else {
-			uid = get_next_number(&input);
-			if (uid == 0) {
-				*error = t_strconcat("Invalid uidset: ",
-						     uidset, NULL);
-				return -2;
-			}
-		}
+	if (ctx->ret != 0)
+		return NULL;
+
+	if (!ctx->uidset)
+		last = mail->rec == NULL || mail->client_seq >= ctx->num2;
+	else
+		last = mail->rec == NULL || mail->rec->uid >= ctx->num2;
 
-		if (*input != ':')
-			uid2 = uid;
-		else {
-			/* first:last range */
-			input++;
-
-			if (*input != '*') {
-				uid2 = get_next_number(&input);
-				if (uid2 == 0) {
-					*error = t_strconcat("Invalid uidset: ",
-							     uidset, NULL);
-					return -2;
-				}
-			} else {
-				uid2 = index->header->next_uid-1;
-				input++;
+	if (!last) {
+		mail->rec = ctx->index->next(ctx->index, mail->rec);
+		mail->client_seq++;
+		mail->idx_seq++;
+	} else {
+		do {
+			if (ctx->p != NULL && *ctx->p == '\0') {
+				/* finished */
+				ctx->ret = 1;
+				return NULL;
 			}
-		}
+
+			if (!messageset_parse_next(ctx)) {
+				ctx->ret = -2;
+				return NULL;
+			}
 
-		if (*input == ',')
-			input++;
-		else if (*input != '\0') {
-			*error = t_strdup_printf("Unexpected char '%c' with "
-						 "uidset: %s", *input, uidset);
-			return -2;
-		}
+			if (ctx->uidset)
+				ctx->ret = uidset_init(ctx);
+			else
+				ctx->ret = seqset_init(ctx);
+		} while (ctx->ret == 1);
 
-		t_push();
-		ret = mail_index_uid_foreach(index, uid, uid2,
-					     callback, context);
-		t_pop();
-		if (ret <= 0)
-			return ret;
-		if (ret == 2)
-			all_found = FALSE;
+		if (ctx->ret != 0)
+			return NULL;
 	}
 
-	return all_found ? 1 : 2;
-}
+	/* fix client_seq */
+	while (ctx->expunges->uid1 != 0 &&
+	       ctx->expunges->uid1 < mail->rec->uid) {
+		i_assert(ctx->expunges->uid2 < mail->rec->uid);
 
-int index_messageset_foreach(struct index_mailbox *ibox,
-			     const char *messageset, int uidset,
-			     msgset_foreach_callback_t callback, void *context)
-{
-	const char *error;
-	int ret;
-
-	if (uidset) {
-		ret = mail_index_uidset_foreach(ibox->index, messageset,
-						ibox->synced_messages_count,
-						callback, context, &error);
-	} else {
-		ret = mail_index_messageset_foreach(ibox->index, messageset,
-						    ibox->synced_messages_count,
-						    callback, context, &error);
+		mail->client_seq += ctx->expunges->seq_count;
+		ctx->expunges++;
 	}
 
-	if (ret < 0) {
-		if (ret == -2) {
-			/* user error */
-			mail_storage_set_syntax_error(ibox->box.storage,
-						      "%s", error);
-		} else {
-			mail_storage_set_index_error(ibox);
-		}
+	i_assert(!(ctx->expunges->uid1 <= mail->rec->uid &&
+		   ctx->expunges->uid2 >= mail->rec->uid));
+
+	if (!ctx->uidset && mail->client_seq > ctx->num2) {
+		/* finished this set - see if there's more */
+		return index_messageset_next(ctx);
 	}
 
-	return ret;
+	return mail;
 }
--- a/src/lib-storage/index/index-messageset.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-messageset.h	Mon Jan 20 16:52:51 2003 +0200
@@ -1,20 +1,29 @@
 #ifndef __INDEX_MESSAGESET_H
 #define __INDEX_MESSAGESET_H
 
-#include "index-storage.h"
+struct index_mailbox;
+
+struct messageset_mail {
+	struct mail_index_record *rec;
+	unsigned int client_seq;
+	unsigned int idx_seq;
+};
+
+struct messageset_context;
 
-/* If FALSE is returned, the loop is stopped. */
-typedef int (*msgset_foreach_callback_t)(struct mail_index *index,
-					 struct mail_index_record *rec,
-					 unsigned int client_seq,
-					 unsigned int idx_seq,
-					 void *context);
+struct messageset_context *
+index_messageset_init(struct index_mailbox *ibox,
+		      const char *messageset, int uidset);
 
-/* Returns 1 if all were found, 2 if some messages were deleted,
-   0 callback returned FALSE, -1 if internal error occured or -2 if messageset
-   was invalid. */
-int index_messageset_foreach(struct index_mailbox *ibox,
-			     const char *messageset, int uidset,
-			     msgset_foreach_callback_t callback, void *context);
+struct messageset_context *
+index_messageset_init_range(struct index_mailbox *ibox,
+			    unsigned int num1, unsigned int num2, int uidset);
+
+/* Returns 1 if all were found, 0 if some messages were deleted,
+   -1 if internal error occured or -2 if messageset was invalid. */
+int index_messageset_deinit(struct messageset_context *ctx);
+
+const struct messageset_mail *
+index_messageset_next(struct messageset_context *ctx);
 
 #endif
--- a/src/lib-storage/index/index-msgcache.c	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-#include "lib.h"
-#include "istream.h"
-#include "imap-date.h"
-#include "imap-message-cache.h"
-#include "message-part-serialize.h"
-#include "mail-index.h"
-#include "mail-index-util.h"
-#include "index-storage.h"
-
-#include <unistd.h>
-
-struct index_msgcache_context {
-	struct mail_index *index;
-	struct mail_index_record *rec;
-	time_t internal_date;
-};
-
-int index_msgcache_open(struct imap_message_cache *cache,
-			struct mail_index *index, struct mail_index_record *rec,
-			enum imap_cache_field fields)
-{
-	struct index_msgcache_context *ctx;
-	uoff_t vp_header_size, vp_body_size, full_virtual_size;
-	const uoff_t *uoff_p;
-	size_t size;
-
-	ctx = t_new(struct index_msgcache_context, 1);
-	ctx->index = index;
-	ctx->rec = rec;
-	ctx->internal_date = (time_t)-1;
-
-	full_virtual_size = (uoff_t)-1;
-	vp_header_size = (uoff_t)-1;
-	vp_body_size = (uoff_t)-1;
-
-	if ((ctx->rec->index_flags & INDEX_MAIL_FLAG_BINARY_HEADER)) {
-		uoff_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec,
-						      DATA_HDR_HEADER_SIZE,
-						      &size);
-		if (uoff_p != NULL) {
-			i_assert(size == sizeof(*uoff_p));
-			vp_header_size = *uoff_p;
-		}
-	}
-
-	if ((ctx->rec->index_flags & INDEX_MAIL_FLAG_BINARY_BODY)) {
-		uoff_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec,
-						      DATA_HDR_BODY_SIZE,
-						      &size);
-		if (uoff_p != NULL) {
-			i_assert(size == sizeof(*uoff_p));
-			vp_body_size = *uoff_p;
-		}
-	}
-
-	if (vp_header_size != (uoff_t)-1 && vp_body_size != (uoff_t)-1)
-		full_virtual_size = vp_header_size + vp_body_size;
-	else {
-		uoff_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec,
-						      DATA_HDR_VIRTUAL_SIZE,
-						      &size);
-		if (uoff_p != NULL) {
-			i_assert(size == sizeof(*uoff_p));
-			full_virtual_size = *uoff_p;
-		}
-	}
-
-	return imap_msgcache_open(cache, rec->uid, fields,
-				  vp_header_size, vp_body_size,
-				  full_virtual_size, ctx);
-}
-
-static struct istream *index_msgcache_open_mail(void *context)
-{
-	struct index_msgcache_context *ctx = context;
-	int deleted;
-
-	return ctx->index->open_mail(ctx->index, ctx->rec,
-				     &ctx->internal_date, &deleted);
-}
-
-static struct istream *
-index_msgcache_stream_rewind(struct istream *input,
-			     void *context __attr_unused__)
-{
-	i_stream_seek(input, 0);
-	return input;
-}
-
-static const char *
-index_msgcache_get_cached_field(enum imap_cache_field field, void *context)
-{
-	struct index_msgcache_context *ctx = context;
-	enum mail_data_field data_field;
-	const time_t *time_p;
-	const char *ret;
-	size_t size;
-
-	switch (field) {
-	case IMAP_CACHE_INTERNALDATE:
-		if (ctx->internal_date != (time_t)-1)
-			return imap_to_datetime(ctx->internal_date);
-
-		time_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec,
-						      DATA_HDR_INTERNAL_DATE,
-						      &size);
-		if (time_p == NULL) {
-			i_assert(size == sizeof(*time_p));
-			return imap_to_datetime(*time_p);
-		} else {
-			ctx->index->cache_fields_later(ctx->index,
-						       DATA_HDR_INTERNAL_DATE);
-			return NULL;
-		}
-	case IMAP_CACHE_BODY:
-		data_field = DATA_FIELD_BODY;
-		break;
-	case IMAP_CACHE_BODYSTRUCTURE:
-		data_field = DATA_FIELD_BODYSTRUCTURE;
-		break;
-	case IMAP_CACHE_ENVELOPE:
-		data_field = DATA_FIELD_ENVELOPE;
-		break;
-	default:
-		return NULL;
-	}
-
-	ret = ctx->index->lookup_field(ctx->index, ctx->rec, data_field);
-	if (ret == NULL)
-		ctx->index->cache_fields_later(ctx->index, data_field);
-	return ret;
-}
-
-static struct message_part *
-index_msgcache_get_cached_parts(pool_t pool, void *context)
-{
-	struct index_msgcache_context *ctx = context;
-	struct message_part *part;
-	const void *part_data;
-	const char *error;
-	size_t part_size;
-
-	part_data = ctx->index->lookup_field_raw(ctx->index, ctx->rec,
-						 DATA_FIELD_MESSAGEPART,
-						 &part_size);
-	if (part_data == NULL) {
-		ctx->index->cache_fields_later(ctx->index,
-					       DATA_FIELD_MESSAGEPART);
-		return NULL;
-	}
-
-	part = message_part_deserialize(pool, part_data, part_size, &error);
-	if (part == NULL) {
-		index_set_corrupted(ctx->index,
-			"Corrupted cached message_part data (%s)", error);
-		return NULL;
-	}
-
-	return part;
-}
-
-static time_t index_msgcache_get_internal_date(void *context)
-{
-	struct index_msgcache_context *ctx = context;
-
-	if (ctx->internal_date != (time_t)-1)
-		return ctx->internal_date;
-
-	return ctx->index->get_internal_date(ctx->index, ctx->rec);
-}
-
-struct imap_message_cache_iface index_msgcache_iface = {
-	index_msgcache_open_mail,
-	index_msgcache_stream_rewind,
-	index_msgcache_get_cached_field,
-	index_msgcache_get_cached_parts,
-	index_msgcache_get_internal_date
-};
--- a/src/lib-storage/index/index-search.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-search.c	Mon Jan 20 16:52:51 2003 +0200
@@ -2,21 +2,19 @@
 
 #include "lib.h"
 #include "istream.h"
-#include "ostream.h"
-#include "mmap-util.h"
+#include "str.h"
+#include "message-address.h"
 #include "message-date.h"
-#include "message-size.h"
 #include "message-body-search.h"
 #include "message-header-search.h"
 #include "imap-date.h"
 #include "imap-envelope.h"
 #include "index-storage.h"
-#include "index-sort.h"
-#include "mail-index-util.h"
+#include "index-messageset.h"
+#include "index-mail.h"
+#include "mail-custom-flags.h"
 #include "mail-modifylog.h"
-#include "mail-custom-flags.h"
 #include "mail-search.h"
-#include "mail-thread.h"
 
 #include <stdlib.h>
 #include <ctype.h>
@@ -29,24 +27,23 @@
 #define TXT_UNKNOWN_CHARSET "[BADCHARSET] Unknown charset"
 #define TXT_INVALID_SEARCH_KEY "Invalid search key"
 
-struct search_index_context {
-	pool_t hdr_pool;
+struct mail_search_context {
 	struct index_mailbox *ibox;
-	struct mail_index_record *rec;
-	unsigned int client_seq;
-	const char *charset;
+	char *charset;
+	struct mail_search_arg *args;
+
+	struct messageset_context *msgset_ctx;
+	struct index_mail imail;
+	struct mail *mail;
+
+	pool_t hdr_pool;
 	const char *error;
 
-	unsigned int cached:1;
-	unsigned int threading:1;
-
-	/* for threading: */
-	const char *message_id, *in_reply_to, *references;
-	time_t sent_date;
+	int failed;
 };
 
 struct search_header_context {
-        struct search_index_context *index_context;
+        struct mail_search_context *index_context;
 	struct mail_search_arg *args;
 
 	const unsigned char *name, *value;
@@ -57,13 +54,11 @@
 };
 
 struct search_body_context {
-        struct search_index_context *index_ctx;
+        struct mail_search_context *index_ctx;
 	struct istream *input;
-	struct message_part *part;
+	const struct message_part *part;
 };
 
-static enum mail_sort_type sort_unsorted[] = { MAIL_SORT_END };
-
 static int msgset_contains(const char *set, unsigned int match_num,
 			   unsigned int max_num)
 {
@@ -175,7 +170,7 @@
 {
 	switch (type) {
 	case SEARCH_ALL:
-		return TRUE;
+		return 1;
 	case SEARCH_SET:
 		return msgset_contains(value, client_seq,
 				       ibox->synced_messages_count);
@@ -206,9 +201,10 @@
 
 static void search_index_arg(struct mail_search_arg *arg, void *context)
 {
-	struct search_index_context *ctx = context;
+	struct mail_search_context *ctx = context;
 
-	switch (search_arg_match_index(ctx->ibox, ctx->rec, ctx->client_seq,
+	switch (search_arg_match_index(ctx->ibox, ctx->imail.data.rec,
+				       ctx->mail->seq,
 				       arg->type, arg->value.str)) {
 	case -1:
 		/* unknown */
@@ -222,19 +218,8 @@
 	}
 }
 
-static struct imap_message_cache *
-search_open_cache(struct search_index_context *ctx)
-{
-	if (!ctx->cached) {
-		(void)index_msgcache_open(ctx->ibox->cache,
-					  ctx->ibox->index, ctx->rec, 0);
-		ctx->cached = TRUE;
-	}
-	return ctx->ibox->cache;
-}
-
 /* Returns >0 = matched, 0 = not matched, -1 = unknown */
-static int search_arg_match_cached(struct search_index_context *ctx,
+static int search_arg_match_cached(struct mail_search_context *ctx,
 				   enum mail_search_arg_type type,
 				   const char *value)
 {
@@ -246,8 +231,7 @@
 	case SEARCH_BEFORE:
 	case SEARCH_ON:
 	case SEARCH_SINCE:
-		internal_date = imap_msgcache_get_internal_date(
-					search_open_cache(ctx));
+		internal_date = ctx->mail->get_received_date(ctx->mail);
 		if (internal_date == (time_t)-1)
 			return -1;
 
@@ -270,8 +254,7 @@
 	/* sizes */
 	case SEARCH_SMALLER:
 	case SEARCH_LARGER:
-		virtual_size = imap_msgcache_get_virtual_size(
-					search_open_cache(ctx));
+		virtual_size = ctx->mail->get_size(ctx->mail);
 		if (virtual_size == (uoff_t)-1)
 			return -1;
 
@@ -288,7 +271,7 @@
 
 static void search_cached_arg(struct mail_search_arg *arg, void *context)
 {
-	struct search_index_context *ctx = context;
+	struct mail_search_context *ctx = context;
 
 	switch (search_arg_match_cached(ctx, arg->type,
 					arg->value.str)) {
@@ -318,7 +301,8 @@
 
 	/* NOTE: Latest IMAP4rev1 draft specifies that timezone is ignored
 	   in searches. sent_time is returned as UTC, so change it. */
-	if (!message_date_parse(sent_value, &sent_time, &timezone_offset))
+	if (!message_date_parse((const unsigned char *) sent_value, (size_t)-1,
+				&sent_time, &timezone_offset))
 		return 0;
 	sent_time -= timezone_offset * 60;
 
@@ -336,7 +320,7 @@
 }
 
 static struct header_search_context *
-search_header_context(struct search_index_context *ctx,
+search_header_context(struct mail_search_context *ctx,
 		      struct mail_search_arg *arg)
 {
 	int unknown_charset;
@@ -363,7 +347,7 @@
 }
 
 /* Returns >0 = matched, 0 = not matched, -1 = unknown */
-static int search_arg_match_envelope(struct search_index_context *ctx,
+static int search_arg_match_envelope(struct mail_search_context *ctx,
 				     struct mail_search_arg *arg)
 {
 	struct mail_index *index = ctx->ibox->index;
@@ -408,7 +392,8 @@
 	t_push();
 
 	/* get field from hopefully cached envelope */
-	envelope = index->lookup_field(index, ctx->rec, DATA_FIELD_ENVELOPE);
+	envelope = index->lookup_field(index, ctx->imail.data.rec,
+				       DATA_FIELD_ENVELOPE);
 	if (envelope != NULL) {
 		ret = imap_envelope_parse(envelope, env_field,
 					  IMAP_ENVELOPE_RESULT_TYPE_STRING,
@@ -457,7 +442,7 @@
 
 static void search_envelope_arg(struct mail_search_arg *arg, void *context)
 {
-	struct search_index_context *ctx = context;
+	struct mail_search_context *ctx = context;
 
 	switch (search_arg_match_envelope(ctx, arg)) {
 	case -1:
@@ -534,13 +519,24 @@
 	} else {
 		t_push();
 
-		/* then check if the value matches */
 		hdr_search_ctx = search_header_context(ctx->index_context, arg);
 		if (hdr_search_ctx == NULL)
 			ret = 0;
-		else {
-			len = ctx->value_len;
-			ret = message_header_search(ctx->value, len,
+		else if (arg->type == SEARCH_FROM || arg->type == SEARCH_TO ||
+			 arg->type == SEARCH_CC || arg->type == SEARCH_BCC) {
+			/* we have to match against normalized address */
+			struct message_address *addr;
+			string_t *str;
+
+			addr = message_address_parse(data_stack_pool,
+						     ctx->value, ctx->value_len,
+						     0);
+			str = t_str_new(ctx->value_len);
+			message_address_write(str, addr);
+			ret = message_header_search(str_data(str), str_len(str),
+						    hdr_search_ctx) ? 1 : 0;
+		} else {
+			ret = message_header_search(ctx->value, ctx->value_len,
 						    hdr_search_ctx) ? 1 : 0;
 		}
 		t_pop();
@@ -549,34 +545,15 @@
         ARG_SET_RESULT(arg, ret);
 }
 
-static void search_header(struct message_part *part __attr_unused__,
+static void search_header(struct message_part *part,
 			  const unsigned char *name, size_t name_len,
 			  const unsigned char *value, size_t value_len,
 			  void *context)
 {
 	struct search_header_context *ctx = context;
-	int timezone_offset;
 
-	if (ctx->threading) {
-		struct search_index_context *ictx = ctx->index_context;
-
-		if (name_len == 10 && memcasecmp(name, "Message-ID", 10) == 0)
-			ictx->message_id = t_strndup(value, value_len);
-		else if (name_len == 11 &&
-			 memcasecmp(name, "In-Reply-To", 11) == 0)
-			ictx->in_reply_to = t_strndup(value, value_len);
-		else if (name_len == 10 &&
-			 memcasecmp(name, "References", 10) == 0)
-			ictx->references = t_strndup(value, value_len);
-		else if (name_len == 4 && memcasecmp(name, "Date", 4) == 0) {
-			t_push();
-			if (!message_date_parse(t_strndup(value, value_len),
-						&ictx->sent_date,
-						&timezone_offset))
-				ictx->sent_date = 0;
-			t_pop();
-		}
-	}
+	index_mail_parse_header(part, name, name_len, value, value_len,
+				ctx->index_context->mail);
 
 	if ((ctx->custom_header && name_len > 0) ||
 	    (name_len == 4 && memcasecmp(name, "Date", 4) == 0) ||
@@ -620,36 +597,39 @@
 }
 
 static int search_arg_match_text(struct mail_search_arg *args,
-				 struct search_index_context *ctx)
+				 struct mail_search_context *ctx)
 {
 	struct istream *input;
 	int have_headers, have_body, have_text;
 
 	/* first check what we need to use */
 	mail_search_args_analyze(args, &have_headers, &have_body, &have_text);
-	if (ctx->threading)
-		have_headers = TRUE;
 	if (!have_headers && !have_body && !have_text)
 		return TRUE;
 
 	if (have_headers || have_text) {
 		struct search_header_context hdr_ctx;
 
-		if (!imap_msgcache_get_data(search_open_cache(ctx), &input))
+		input = ctx->mail->get_stream(ctx->mail, NULL, NULL);
+		if (input == NULL)
 			return FALSE;
 
 		memset(&hdr_ctx, 0, sizeof(hdr_ctx));
 		hdr_ctx.index_context = ctx;
 		hdr_ctx.custom_header = TRUE;
 		hdr_ctx.args = args;
-		hdr_ctx.threading = ctx->threading;
 
+		index_mail_init_parse_header(&ctx->imail);
 		message_parse_header(NULL, input, NULL,
 				     search_header, &hdr_ctx);
 	} else {
-		if (!imap_msgcache_get_rfc822(search_open_cache(ctx), &input,
-					      NULL, NULL))
+		struct message_size hdr_size;
+
+		input = ctx->mail->get_stream(ctx->mail, &hdr_size, NULL);
+		if (input == NULL)
 			return FALSE;
+
+		i_stream_seek(input, hdr_size.physical_size);
 	}
 
 	if (have_text || have_body) {
@@ -658,7 +638,7 @@
 		memset(&body_ctx, 0, sizeof(body_ctx));
 		body_ctx.index_ctx = ctx;
 		body_ctx.input = input;
-		body_ctx.part = imap_msgcache_get_parts(search_open_cache(ctx));
+		body_ctx.part = ctx->mail->get_parts(ctx->mail);
 
 		mail_search_args_foreach(args, search_body, &body_ctx);
 	}
@@ -706,7 +686,7 @@
 }
 
 static int search_get_sequid(struct index_mailbox *ibox,
-			     struct mail_search_arg *args,
+			     const struct mail_search_arg *args,
 			     unsigned int *first_seq, unsigned int *last_seq,
 			     unsigned int *first_uid, unsigned int *last_uid)
 {
@@ -744,7 +724,7 @@
 }
 
 static int search_limit_by_flags(struct index_mailbox *ibox,
-				 struct mail_search_arg *args,
+				 const struct mail_search_arg *args,
 				 unsigned int *first_uid,
 				 unsigned int *last_uid)
 {
@@ -809,8 +789,10 @@
 		return FALSE;
 	}
 
-	(void)mail_modifylog_seq_get_expunges(ibox->index->modifylog, seq, seq,
-					      &expunges_before);
+	if (mail_modifylog_seq_get_expunges(ibox->index->modifylog, seq, seq,
+					    &expunges_before) == NULL)
+		return FALSE;
+
 	seq -= expunges_before;
 
 	rec = ibox->index->lookup(ibox->index, seq);
@@ -819,7 +801,7 @@
 }
 
 static int search_get_uid_range(struct index_mailbox *ibox,
-				struct mail_search_arg *args,
+				const struct mail_search_arg *args,
 				unsigned int *first_uid, unsigned int *last_uid)
 {
 	unsigned int first_seq, last_seq, uid;
@@ -868,180 +850,143 @@
 	return 1;
 }
 
-static int search_messages(struct index_mailbox *ibox, const char *charset,
-			   struct mail_search_arg *args,
-			   struct mail_sort_context *sort_ctx,
-			   struct mail_thread_context *thread_ctx,
-                           struct index_sort_context *index_sort_ctx,
-			   struct ostream *output, int uid_result)
+int index_storage_search_get_sorting(struct mailbox *box __attr_unused__,
+				     enum mail_sort_type *sort_program)
+{
+	/* currently we don't support sorting */
+	*sort_program = MAIL_SORT_END;
+	return TRUE;
+}
+
+struct mail_search_context *
+index_storage_search_init(struct mailbox *box, const char *charset,
+			  struct mail_search_arg *args,
+			  const enum mail_sort_type *sort_program,
+			  enum mail_fetch_field wanted_fields,
+			  const char *const wanted_headers[])
 {
-	struct search_index_context ctx;
-	struct mail_index_record *rec;
-        struct mail_search_arg *arg;
-	const struct modify_log_expunge *expunges;
-	unsigned int first_uid, last_uid, client_seq, expunges_before;
-	const char *str;
-	int found, failed;
+	struct index_mailbox *ibox = (struct index_mailbox *) box;
+	struct mail_search_context *ctx;
+	unsigned int first_uid, last_uid;
+
+	if (sort_program != NULL && *sort_program != MAIL_SORT_END) {
+		i_error("BUG: index_storage_search_init(): "
+			"invalid sort_program");
+		return NULL;
+	}
+
+	if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED))
+		return NULL;
+
+	ctx = i_new(struct mail_search_context, 1);
+	ctx->ibox = ibox;
+	ctx->charset = i_strdup(charset);
+	ctx->args = args;
+
+	ctx->mail = (struct mail *) &ctx->imail;
+	index_mail_init(ibox, &ctx->imail, wanted_fields, wanted_headers);
 
 	if (ibox->synced_messages_count == 0)
-		return TRUE;
+		return ctx;
 
 	/* see if we can limit the records we look at */
 	switch (search_get_uid_range(ibox, args, &first_uid, &last_uid)) {
 	case -1:
 		/* error */
-		return FALSE;
+		ctx->failed = TRUE;
+		return ctx;
 	case 0:
 		/* nothing found */
-		return TRUE;
+		return ctx;
+	}
+
+	ctx->msgset_ctx =
+		index_messageset_init_range(ibox, first_uid, last_uid, TRUE);
+	return ctx;
+}
+
+int index_storage_search_deinit(struct mail_search_context *ctx)
+{
+	int ret;
+
+	ret = !ctx->failed && ctx->error == NULL;
+
+	if (ctx->msgset_ctx != NULL) {
+		if (index_messageset_deinit(ctx->msgset_ctx) < 0)
+			ret = FALSE;
+	}
+
+	if (!index_storage_lock(ctx->ibox, MAIL_LOCK_UNLOCK))
+		ret = FALSE;
+
+	if (ctx->error != NULL) {
+		mail_storage_set_error(ctx->ibox->box.storage,
+				       "%s", ctx->error);
 	}
 
-	rec = ibox->index->lookup_uid_range(ibox->index, first_uid, last_uid,
-					    &client_seq);
-	if (rec == NULL)
-		return TRUE;
+	if (ctx->hdr_pool != NULL)
+		pool_unref(ctx->hdr_pool);
+
+	if (ctx->ibox->fetch_mail.pool != NULL)
+		index_mail_deinit(&ctx->ibox->fetch_mail);
+        index_mail_deinit(&ctx->imail);
+	i_free(ctx);
+	return ret;
+}
 
-	expunges = mail_modifylog_uid_get_expunges(ibox->index->modifylog,
-						   rec->uid, last_uid,
-						   &expunges_before);
-	client_seq += expunges_before;
-	index_sort_ctx->synced_sequences = expunges->uid1 == 0;
+struct mail *index_storage_search_next(struct mail_search_context *ctx)
+{
+	const struct messageset_mail *msgset_mail;
+        struct mail_search_arg *arg;
+	int found, ret;
 
-	memset(&ctx, 0, sizeof(ctx));
-	ctx.ibox = ibox;
-	ctx.charset = charset;
-	ctx.threading = thread_ctx != NULL;
+	if (ctx->msgset_ctx == NULL) {
+		/* initialization failed or didn't found any messages */
+		return NULL;
+	}
 
-	for (; rec != NULL && rec->uid <= last_uid; client_seq++) {
-		while (expunges->uid1 != 0 && expunges->uid1 < rec->uid) {
-			i_assert(expunges->uid2 < rec->uid);
+	do {
+		msgset_mail = index_messageset_next(ctx->msgset_ctx);
+		if (msgset_mail == NULL)
+			return NULL;
 
-			client_seq += expunges->seq_count;
-			expunges++;
-		}
-		i_assert(!(expunges->uid1 <= rec->uid &&
-			   expunges->uid2 >= rec->uid));
+		ctx->mail->seq = msgset_mail->client_seq;
+		ctx->mail->uid = msgset_mail->rec->uid;
+		ret = index_mail_next(&ctx->imail, msgset_mail->rec);
 
-		ctx.rec = rec;
-		ctx.client_seq = client_seq;
-		ctx.cached = FALSE;
+		if (ret < 0)
+			return NULL;
 
-		ctx.message_id = ctx.in_reply_to = ctx.references = NULL;
+		if (ret == 0)
+			found = FALSE;
+		else {
+			mail_search_args_reset(ctx->args);
 
-		mail_search_args_reset(args);
-
-		t_push();
+			t_push();
 
-		mail_search_args_foreach(args, search_index_arg, &ctx);
-		mail_search_args_foreach(args, search_cached_arg, &ctx);
-		mail_search_args_foreach(args, search_envelope_arg, &ctx);
-		failed = !search_arg_match_text(args, &ctx);
-                imap_msgcache_close(ibox->cache);
+			mail_search_args_foreach(ctx->args, search_index_arg,
+						 ctx);
+			mail_search_args_foreach(ctx->args, search_cached_arg,
+						 ctx);
+			mail_search_args_foreach(ctx->args, search_envelope_arg,
+						 ctx);
+			found = search_arg_match_text(ctx->args, ctx);
 
-		if (ctx.error != NULL) {
 			t_pop();
-			break;
+
+			if (ctx->error != NULL)
+				return NULL;
 		}
 
-		if (!failed) {
-			found = TRUE;
-			for (arg = args; arg != NULL; arg = arg->next) {
+		if (found) {
+			for (arg = ctx->args; arg != NULL; arg = arg->next) {
 				if (arg->result != 1) {
 					found = FALSE;
 					break;
 				}
 			}
-
-			if (found) {
-				unsigned int id = uid_result ?
-					rec->uid : client_seq;
-
-				index_sort_ctx->current_client_seq = client_seq;
-				index_sort_ctx->current_rec = rec;
-
-				if (sort_ctx != NULL)
-					mail_sort_input(sort_ctx, id);
-				else if (thread_ctx != NULL) {
-					mail_thread_input(thread_ctx, id,
-							  ctx.message_id,
-							  ctx.in_reply_to,
-							  ctx.references,
-							  ctx.sent_date);
-				} else {
-					o_stream_send(output, " ", 1);
-
-					str = dec2str(id);
-					o_stream_send_str(output, str);
-				}
-			}
 		}
-		t_pop();
-
-		rec = ibox->index->next(ibox->index, rec);
-	}
-
-	if (ctx.hdr_pool != NULL)
-		pool_unref(ctx.hdr_pool);
-
-	if (ctx.error != NULL)
-		mail_storage_set_error(ibox->box.storage, "%s", ctx.error);
-	return ctx.error == NULL;
-}
+	} while (!found);
 
-int index_storage_search(struct mailbox *box, const char *charset,
-			 struct mail_search_arg *args,
-			 enum mail_sort_type *sorting,
-                         enum mail_thread_type threading,
-			 struct ostream *output, int uid_result)
-{
-	struct index_mailbox *ibox = (struct index_mailbox *) box;
-	struct mail_sort_context *sort_ctx;
-	struct mail_thread_context *thread_ctx;
-	struct index_sort_context index_sort_ctx;
-	int failed;
-
-	if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED))
-		return FALSE;
-
-	if (sorting != NULL) {
-		memset(&index_sort_ctx, 0, sizeof(index_sort_ctx));
-		index_sort_ctx.ibox = ibox;
-		index_sort_ctx.output = output;
-		index_sort_ctx.id_is_uid = uid_result;
-
-		thread_ctx = NULL;
-		sort_ctx = mail_sort_init(sort_unsorted, sorting, output,
-					  &index_sort_callbacks,
-					  &index_sort_ctx);
-		o_stream_send_str(output, "* SORT");
-	} else if (threading != MAIL_THREAD_NONE) {
-		memset(&index_sort_ctx, 0, sizeof(index_sort_ctx));
-		index_sort_ctx.ibox = ibox;
-		index_sort_ctx.id_is_uid = uid_result;
-
-		sort_ctx = NULL;
-		thread_ctx = mail_thread_init(threading, output,
-					      &index_sort_callbacks,
-					      &index_sort_ctx);
-		o_stream_send_str(output, "* THREAD");
-	} else {
-		memset(&index_sort_ctx, 0, sizeof(index_sort_ctx));
-		sort_ctx = NULL;
-		thread_ctx = NULL;
-		o_stream_send_str(output, "* SEARCH");
-	}
-
-	failed = !search_messages(ibox, charset, args, sort_ctx, thread_ctx,
-				  &index_sort_ctx, output, uid_result);
-	if (sort_ctx != NULL)
-		mail_sort_deinit(sort_ctx);
-	if (thread_ctx != NULL)
-		mail_thread_finish(thread_ctx);
-
-	o_stream_send(output, "\r\n", 2);
-
-	if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK))
-		return FALSE;
-
-	return !failed;
+	return ctx->mail;
 }
--- a/src/lib-storage/index/index-sort.c	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-#include "lib.h"
-#include "ostream.h"
-#include "message-date.h"
-#include "imap-envelope.h"
-#include "imap-message-cache.h"
-#include "mail-index.h"
-#include "mail-modifylog.h"
-#include "index-storage.h"
-#include "index-sort.h"
-
-static struct mail_index_record *
-lookup_client_seq(struct index_sort_context *ctx, unsigned int client_seq)
-{
-	struct mail_index_record *rec;
-        unsigned int expunges_before;
-
-	if (ctx->synced_sequences)
-		return ctx->ibox->index->lookup(ctx->ibox->index, client_seq);
-
-	t_push();
-	if (mail_modifylog_seq_get_expunges(ctx->ibox->index->modifylog,
-					    client_seq, client_seq,
-					    &expunges_before) == NULL) {
-		rec = NULL;
-	} else {
-		rec = ctx->ibox->index->lookup(ctx->ibox->index,
-					       client_seq - expunges_before);
-	}
-	t_pop();
-
-	return rec;
-}
-
-static struct imap_message_cache *
-search_open_cache(struct index_sort_context *ctx, unsigned int id)
-{
-	i_assert(id != 0);
-
-	if (ctx->last_id != id) {
-		ctx->cached = FALSE;
-		ctx->last_id = id;
-
-		if ((ctx->id_is_uid && ctx->current_rec->uid == id) ||
-		    (!ctx->id_is_uid && ctx->current_client_seq == id)) {
-			ctx->rec = ctx->current_rec;
-		} else if (ctx->id_is_uid) {
-			ctx->rec = ctx->ibox->index->
-				lookup_uid_range(ctx->ibox->index,
-						 id, id, NULL);
-		} else {
-			ctx->rec = lookup_client_seq(ctx, id);
-		}
-
-		if (ctx->rec == NULL) {
-			ctx->last_id = 0;
-			return NULL;
-		}
-	}
-
-	if (!ctx->cached) {
-		ctx->cached = TRUE;
-		(void)index_msgcache_open(ctx->ibox->cache,
-					  ctx->ibox->index, ctx->rec,
-					  IMAP_CACHE_ENVELOPE);
-	}
-
-	return ctx->ibox->cache;
-}
-
-static uoff_t _input_uofft(enum mail_sort_type type,
-			   unsigned int id, void *context)
-{
-	struct index_sort_context *ctx = context;
-        struct imap_message_cache *cache;
-
-	if (type != MAIL_SORT_SIZE) {
-		i_unreached();
-		return 0;
-	}
-
-        cache = search_open_cache(ctx, id);
-	return cache == NULL ? 0 : imap_msgcache_get_virtual_size(cache);
-}
-
-static const char *_input_mailbox(enum mail_sort_type type, unsigned int id,
-				  void *context)
-{
-	struct index_sort_context *ctx = context;
-	enum imap_envelope_field env_field;
-	const char *envelope, *str;
-
-	switch (type) {
-	case MAIL_SORT_CC:
-		env_field = IMAP_ENVELOPE_CC;
-		break;
-	case MAIL_SORT_FROM:
-                env_field = IMAP_ENVELOPE_FROM;
-		break;
-	case MAIL_SORT_TO:
-                env_field = IMAP_ENVELOPE_TO;
-		break;
-	default:
-		i_unreached();
-		return NULL;
-	}
-
-	/* get field from hopefully cached envelope */
-	envelope = imap_msgcache_get(search_open_cache(ctx, id),
-				     IMAP_CACHE_ENVELOPE);
-	if (envelope == NULL)
-		return NULL;
-
-	if (!imap_envelope_parse(envelope, env_field,
-				 IMAP_ENVELOPE_RESULT_TYPE_FIRST_MAILBOX, &str))
-		return NULL;
-
-	return str;
-}
-
-static const char *_input_str(enum mail_sort_type type,
-			      unsigned int id, void *context)
-{
-	struct index_sort_context *ctx = context;
-	enum imap_envelope_field env_field;
-	const char *envelope, *str;
-
-	switch (type) {
-	case MAIL_SORT_DATE:
-                env_field = IMAP_ENVELOPE_DATE;
-		break;
-	case MAIL_SORT_SUBJECT:
-                env_field = IMAP_ENVELOPE_SUBJECT;
-		break;
-	default:
-		i_unreached();
-		return NULL;
-	}
-
-	/* get field from hopefully cached envelope */
-	envelope = imap_msgcache_get(search_open_cache(ctx, id),
-				     IMAP_CACHE_ENVELOPE);
-	if (envelope == NULL)
-		return NULL;
-
-	if (!imap_envelope_parse(envelope, env_field,
-				 IMAP_ENVELOPE_RESULT_TYPE_STRING, &str))
-		return NULL;
-
-	return str;
-}
-
-static time_t _input_time(enum mail_sort_type type,
-			  unsigned int id, void *context)
-{
-	struct index_sort_context *ctx = context;
-        struct imap_message_cache *cache;
-	const char *str;
-	time_t time;
-	int timezone_offset;
-
-	switch (type) {
-	case MAIL_SORT_ARRIVAL:
-		cache = search_open_cache(ctx, id);
-		return cache == NULL ? 0 :
-			imap_msgcache_get_internal_date(cache);
-	case MAIL_SORT_DATE:
-		str = _input_str(type, id, context);
-		if (str == NULL)
-			return 0;
-
-		if (!message_date_parse(str, &time, &timezone_offset))
-			return 0;
-
-		return time;
-	default:
-		i_unreached();
-		return 0;
-	}
-}
-
-static void _input_reset(void *context)
-{
-	struct index_sort_context *ctx = context;
-
-	ctx->cached = FALSE;
-}
-
-struct mail_sort_callbacks index_sort_callbacks = {
-	_input_time,
-	_input_uofft,
-	_input_mailbox,
-	_input_str,
-	_input_reset
-};
--- a/src/lib-storage/index/index-sort.h	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#ifndef __INDEX_SORT_H
-#define __INDEX_SORT_H
-
-#include "mail-storage.h"
-#include "mail-sort.h"
-
-struct index_sort_context {
-	struct index_mailbox *ibox;
-	struct ostream *output;
-
-	unsigned int current_client_seq;
-	struct mail_index_record *current_rec;
-
-	unsigned int last_id;
-	struct mail_index_record *rec;
-
-	unsigned int cached:1;
-	unsigned int id_is_uid:1;
-	unsigned int synced_sequences:1;
-};
-
-extern struct mail_sort_callbacks index_sort_callbacks;
-
-#endif
--- a/src/lib-storage/index/index-storage.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-storage.c	Mon Jan 20 16:52:51 2003 +0200
@@ -227,7 +227,6 @@
 		ibox->box.allow_custom_flags = TRUE;
 
 		ibox->index = index;
-		ibox->cache = imap_msgcache_alloc(&index_msgcache_iface);
 
 		ibox->next_lock_notify = time(NULL) + LOCK_NOTIFY_INTERVAL;
 		index->set_lock_notify_callback(index, lock_notify, ibox);
@@ -266,7 +265,6 @@
 	struct index_mailbox *ibox = (struct index_mailbox *) box;
 
 	index_mailbox_check_remove(ibox);
-	imap_msgcache_free(ibox->cache);
 	if (ibox->index != NULL)
 		index_storage_unref(ibox->index);
 
@@ -316,13 +314,14 @@
 
 int index_mailbox_fix_custom_flags(struct index_mailbox *ibox,
 				   enum mail_flags *flags,
-                                   const char *custom_flags[])
+				   const char *custom_flags[],
+				   unsigned int custom_flags_count)
 {
 	int ret;
 
 	ret = mail_custom_flags_fix_list(ibox->index->custom_flags,
 					 flags, custom_flags,
-					 MAIL_CUSTOM_FLAGS_COUNT);
+					 custom_flags_count);
 	switch (ret) {
 	case 1:
 		return TRUE;
--- a/src/lib-storage/index/index-storage.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-storage.h	Mon Jan 20 16:52:51 2003 +0200
@@ -3,7 +3,7 @@
 
 #include "mail-storage.h"
 #include "mail-index.h"
-#include "imap-message-cache.h"
+#include "index-mail.h"
 
 struct index_mailbox {
 	struct mailbox box;
@@ -13,13 +13,13 @@
 	int (*expunge_locked)(struct index_mailbox *ibox, int notify);
 
 	struct mail_index *index;
-	struct imap_message_cache *cache;
 
 	char *check_path;
 	struct timeout *check_to;
 	time_t check_file_stamp;
 	time_t last_check;
 
+	struct index_mail fetch_mail; /* fetch_uid() or fetch_seq() */
 	unsigned int synced_messages_count;
 
 	time_t next_lock_notify; /* temporary */
@@ -28,8 +28,6 @@
 	unsigned int delay_save_unlocking:1; /* For COPYing inside mailbox */
 };
 
-extern struct imap_message_cache_iface index_msgcache_iface;
-
 int mail_storage_set_index_error(struct index_mailbox *ibox);
 void index_storage_init_lock_notify(struct index_mailbox *ibox);
 int index_storage_lock(struct index_mailbox *ibox,
@@ -51,7 +49,8 @@
 
 int index_mailbox_fix_custom_flags(struct index_mailbox *ibox,
 				   enum mail_flags *flags,
-                                   const char *custom_flags[]);
+				   const char *custom_flags[],
+				   unsigned int custom_flags_count);
 
 unsigned int index_storage_get_recent_count(struct mail_index *index);
 
@@ -65,10 +64,6 @@
 		       struct istream *input, struct ostream *output,
 		       uoff_t data_size);
 
-int index_msgcache_open(struct imap_message_cache *cache,
-			struct mail_index *index, struct mail_index_record *rec,
-			enum imap_cache_field fields);
-
 void index_mailbox_check_add(struct index_mailbox *ibox, const char *path);
 void index_mailbox_check_remove(struct index_mailbox *ibox);
 
@@ -84,16 +79,31 @@
 			     struct mailbox_status *status);
 int index_storage_sync(struct mailbox *box, int sync_expunges);
 int index_storage_update_flags(struct mailbox *box, const char *messageset,
-			       int uidset, enum mail_flags flags,
-			       const char *custom_flags[],
+			       int uidset, const struct mail_full_flags *flags,
 			       enum modify_type modify_type, int notify,
 			       int *all_found);
-int index_storage_fetch(struct mailbox *box, struct mail_fetch_data *fetch_data,
-			struct ostream *output, int *all_found);
-int index_storage_search(struct mailbox *box, const char *charset,
-			 struct mail_search_arg *args,
-			 enum mail_sort_type *sorting,
-                         enum mail_thread_type threading,
-			 struct ostream *output, int uid_result);
+
+struct mail_fetch_context *
+index_storage_fetch_init(struct mailbox *box,
+			 enum mail_fetch_field wanted_fields, int *update_seen,
+			 const char *messageset, int uidset);
+int index_storage_fetch_deinit(struct mail_fetch_context *ctx, int *all_found);
+struct mail *index_storage_fetch_next(struct mail_fetch_context *ctx);
+
+struct mail *index_storage_fetch_uid(struct mailbox *box, unsigned int uid,
+				     enum mail_fetch_field wanted_fields);
+struct mail *index_storage_fetch_seq(struct mailbox *box, unsigned int seq,
+				     enum mail_fetch_field wanted_fields);
+
+int index_storage_search_get_sorting(struct mailbox *box,
+				     enum mail_sort_type *sort_program);
+struct mail_search_context *
+index_storage_search_init(struct mailbox *box, const char *charset,
+			  struct mail_search_arg *args,
+			  const enum mail_sort_type *sort_program,
+			  enum mail_fetch_field wanted_fields,
+			  const char *const wanted_headers[]);
+int index_storage_search_deinit(struct mail_search_context *ctx);
+struct mail *index_storage_search_next(struct mail_search_context *ctx);
 
 #endif
--- a/src/lib-storage/index/index-update-flags.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/index-update-flags.c	Mon Jan 20 16:52:51 2003 +0200
@@ -5,78 +5,76 @@
 #include "index-messageset.h"
 #include "mail-custom-flags.h"
 
-struct update_context {
-	struct index_mailbox *ibox;
-	enum mail_flags flags;
-	enum modify_type modify_type;
-	int notify;
-};
-
-static int update_cb(struct mail_index *index, struct mail_index_record *rec,
-		     unsigned int client_seq, unsigned int idx_seq,
-		     void *context)
+static int update_messageset(struct messageset_context *ctx,
+			     struct index_mailbox *ibox, enum mail_flags flags,
+			     enum modify_type modify_type, int notify)
 {
-	struct update_context *ctx = context;
 	struct mail_storage *storage;
-	enum mail_flags flags;
+	const struct messageset_mail *mail;
 	const char **custom_flags;
+	enum mail_flags new_flags;
+
+	storage = ibox->box.storage;
+	custom_flags = mail_custom_flags_list_get(ibox->index->custom_flags);
 
-	switch (ctx->modify_type) {
-	case MODIFY_ADD:
-		flags = rec->msg_flags | ctx->flags;
-		break;
-	case MODIFY_REMOVE:
-		flags = rec->msg_flags & ~ctx->flags;
-		break;
-	case MODIFY_REPLACE:
-		flags = ctx->flags;
-		break;
-	default:
-                i_unreached();
+	while ((mail = index_messageset_next(ctx)) != NULL) {
+		switch (modify_type) {
+		case MODIFY_ADD:
+			new_flags = mail->rec->msg_flags | flags;
+			break;
+		case MODIFY_REMOVE:
+			new_flags = mail->rec->msg_flags & ~flags;
+			break;
+		case MODIFY_REPLACE:
+			new_flags = flags;
+			break;
+		default:
+			i_unreached();
+		}
+
+		if (!ibox->index->update_flags(ibox->index, mail->rec,
+					       mail->idx_seq, new_flags, FALSE))
+			return -1;
+
+		if (mail_custom_flags_has_changes(ibox->index->custom_flags)) {
+			storage->callbacks->new_custom_flags(&ibox->box,
+				custom_flags, MAIL_CUSTOM_FLAGS_COUNT,
+				storage->callback_context);
+		}
+
+		if (notify) {
+			if (mail->rec->uid >= ibox->index->first_recent_uid)
+				new_flags |= MAIL_RECENT;
+
+			storage->callbacks->update_flags(&ibox->box,
+				mail->client_seq, mail->rec->uid, new_flags,
+				custom_flags, MAIL_CUSTOM_FLAGS_COUNT,
+				storage->callback_context);
+		}
 	}
 
-	if (!index->update_flags(index, rec, idx_seq, flags, FALSE))
-		return FALSE;
-
-	storage = ctx->ibox->box.storage;
-	if (mail_custom_flags_has_changes(index->custom_flags)) {
-		storage->callbacks->new_custom_flags(&ctx->ibox->box,
-			mail_custom_flags_list_get(index->custom_flags),
-			MAIL_CUSTOM_FLAGS_COUNT, storage->callback_context);
-	}
-
-	if (ctx->notify) {
-		if (rec->uid >= index->first_recent_uid)
-			flags |= MAIL_RECENT;
-
-                custom_flags = mail_custom_flags_list_get(index->custom_flags);
-		storage->callbacks->update_flags(&ctx->ibox->box,
-						 client_seq, rec->uid,
-						 flags, custom_flags,
-						 MAIL_CUSTOM_FLAGS_COUNT,
-						 storage->callback_context);
-	}
-
-	return TRUE;
+	return 1;
 }
 
-int index_storage_update_flags(struct mailbox *box,
-			       const char *messageset, int uidset,
-			       enum mail_flags flags,
-			       const char *custom_flags[],
+int index_storage_update_flags(struct mailbox *box, const char *messageset,
+			       int uidset, const struct mail_full_flags *flags,
 			       enum modify_type modify_type, int notify,
 			       int *all_found)
 {
 	struct index_mailbox *ibox = (struct index_mailbox *) box;
-        struct update_context ctx;
-	int ret;
+        struct messageset_context *ctx;
+	enum mail_flags mail_flags;
+	int ret, ret2;
 
 	if (box->readonly) {
 		mail_storage_set_error(box->storage, "Mailbox is read-only");
 		return FALSE;
 	}
 
-	if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags))
+	mail_flags = flags->flags;
+	if (!index_mailbox_fix_custom_flags(ibox, &mail_flags,
+					    flags->custom_flags,
+					    flags->custom_flags_count))
 		return FALSE;
 
 	if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE))
@@ -85,18 +83,16 @@
 	if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_UNLOCK))
 		return FALSE;
 
-	ctx.ibox = ibox;
-	ctx.flags = flags & ~MAIL_RECENT; /* \Recent can't be changed */
-	ctx.modify_type = modify_type;
-	ctx.notify = notify;
+	mail_flags &= ~MAIL_RECENT; /* \Recent can't be changed */
 
-	ret = index_messageset_foreach(ibox, messageset, uidset,
-				       update_cb, &ctx);
+	ctx = index_messageset_init(ibox, messageset, uidset);
+	ret = update_messageset(ctx, ibox, mail_flags, modify_type, notify);
+	ret2 = index_messageset_deinit(ctx);
 
 	if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK))
 		return FALSE;
 
 	if (all_found != NULL)
-		*all_found = ret == 1;
-	return ret >= 0;
+		*all_found = ret2 > 0;
+	return ret >= 0 && ret2 >= 0;
 }
--- a/src/lib-storage/index/maildir/maildir-copy.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/maildir/maildir-copy.c	Mon Jan 20 16:52:51 2003 +0200
@@ -1,87 +1,86 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "lib.h"
-#include "index-messageset.h"
 #include "maildir-index.h"
 #include "maildir-storage.h"
 #include "mail-custom-flags.h"
+#include "mail-index-util.h"
+#include "index-messageset.h"
 
 #include <stdlib.h>
 #include <unistd.h>
 
-struct copy_hard_context {
-	struct mail_storage *storage;
-	struct index_mailbox *dest;
-	int error;
-	const char **custom_flags;
-};
-
-static int copy_hard_cb(struct mail_index *index,
-			struct mail_index_record *rec,
-			unsigned int client_seq __attr_unused__,
-			unsigned int idx_seq __attr_unused__, void *context)
+static int hardlink_messageset(struct messageset_context *ctx,
+			       struct index_mailbox *src,
+			       struct index_mailbox *dest)
 {
-	struct copy_hard_context *ctx = context;
+        struct mail_index *index = src->index;
+        const struct messageset_mail *mail;
 	enum mail_flags flags;
-	const char *fname;
-	char src[PATH_MAX], dest[PATH_MAX];
+	const char **custom_flags;
+	const char *fname, *src_fname, *dest_fname;
+
+	custom_flags = mail_custom_flags_list_get(index->custom_flags);
 
-	flags = rec->msg_flags;
-	if (!index_mailbox_fix_custom_flags(ctx->dest, &flags,
-					    ctx->custom_flags))
-		return FALSE;
+	while ((mail = index_messageset_next(ctx)) != NULL) {
+		flags = mail->rec->msg_flags;
+		if (!index_mailbox_fix_custom_flags(dest, &flags,
+						    custom_flags,
+						    MAIL_CUSTOM_FLAGS_COUNT))
+			return -1;
 
-	/* link the file */
-	fname = index->lookup_field(index, rec, DATA_FIELD_LOCATION);
-	if (str_ppath(src, sizeof(src),
-		      index->mailbox_path, "cur/", fname) < 0) {
-		mail_storage_set_critical(ctx->storage, "Filename too long: %s",
-					  fname);
-		return FALSE;
+		/* link the file */
+		fname = index->lookup_field(index, mail->rec,
+					    DATA_FIELD_LOCATION);
+		if (fname == NULL) {
+			index_set_corrupted(index,
+				"Missing location field for record %u",
+				mail->rec->uid);
+			return -1;
+		}
+
+		t_push();
+		src_fname = t_strconcat(index->mailbox_path, "cur/",
+					fname, NULL);
+		dest_fname = t_strconcat(dest->index->mailbox_path, "new/",
+                	maildir_filename_set_flags(
+				maildir_generate_tmp_filename(), flags), NULL);
+
+		if (link(src_fname, dest_fname) < 0) {
+			if (errno != EXDEV) {
+				mail_storage_set_critical(src->box.storage,
+					"link(%s, %s) failed: %m",
+					src_fname, dest_fname);
+				t_pop();
+				return -1;
+			}
+			t_pop();
+			return 0;
+		}
+		t_pop();
 	}
 
-	fname = maildir_filename_set_flags(maildir_generate_tmp_filename(),
-					   flags);
-	if (str_ppath(dest, sizeof(dest),
-		      ctx->dest->index->mailbox_path, "new/", fname) < 0) {
-		mail_storage_set_critical(ctx->storage, "Filename too long: %s",
-					  fname);
-		return FALSE;
-	}
-
-	if (link(src, dest) == 0)
-		return TRUE;
-	else {
-		if (errno != EXDEV) {
-			mail_storage_set_critical(ctx->storage, "link(%s, %s) "
-						  "failed: %m", src, dest);
-			ctx->error = TRUE;
-		}
-		return FALSE;
-	}
+	return 1;
 }
 
-static int maildir_copy_with_hardlinks(struct index_mailbox *src,
-				       struct index_mailbox *dest,
-				       const char *messageset, int uidset)
+static int copy_with_hardlinks(struct index_mailbox *src,
+			       struct index_mailbox *dest,
+			       const char *messageset, int uidset)
 {
-	struct copy_hard_context ctx;
+        struct messageset_context *ctx;
 	int ret;
 
 	if (!index_storage_sync_and_lock(src, TRUE, MAIL_LOCK_SHARED))
 		return -1;
 
-	ctx.storage = src->box.storage;
-	ctx.dest = dest;
-	ctx.error = FALSE;
-	ctx.custom_flags = mail_custom_flags_list_get(src->index->custom_flags);
-
-	ret = index_messageset_foreach(src, messageset, uidset,
-				       copy_hard_cb, &ctx);
+	ctx = index_messageset_init(src, messageset, uidset);
+	ret = hardlink_messageset(ctx, src, dest);
+	if (index_messageset_deinit(ctx) < 0)
+		ret = -1;
 
 	(void)index_storage_lock(src, MAIL_LOCK_UNLOCK);
 
-	return ctx.error ? -1 : ret;
+	return ret;
 }
 
 int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox,
@@ -99,7 +98,7 @@
 	    destbox->storage == box->storage) {
 		/* both source and destination mailbox are in maildirs and
 		   copy_with_hardlinks option is on, do it */
-		switch (maildir_copy_with_hardlinks(ibox,
+		switch (copy_with_hardlinks(ibox,
 			(struct index_mailbox *) destbox, messageset, uidset)) {
 		case 1:
 			return TRUE;
--- a/src/lib-storage/index/maildir/maildir-save.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/maildir/maildir-save.c	Mon Jan 20 16:52:51 2003 +0200
@@ -78,12 +78,14 @@
 	return fname;
 }
 
-int maildir_storage_save(struct mailbox *box, enum mail_flags flags,
-			 const char *custom_flags[], time_t internal_date,
+int maildir_storage_save(struct mailbox *box,
+			 const struct mail_full_flags *flags,
+			 time_t internal_date,
 			 int timezone_offset __attr_unused__,
 			 struct istream *data, uoff_t data_size)
 {
         struct index_mailbox *ibox = (struct index_mailbox *) box;
+	enum mail_flags mail_flags;
         struct utimbuf buf;
 	const char *tmpdir, *fname, *tmp_path, *new_path;
 	int failed;
@@ -93,7 +95,10 @@
 		return FALSE;
 	}
 
-	if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags))
+	mail_flags = flags->flags;
+	if (!index_mailbox_fix_custom_flags(ibox, &mail_flags,
+					    flags->custom_flags,
+					    flags->custom_flags_count))
 		return FALSE;
 
 	t_push();
@@ -107,7 +112,7 @@
 	}
 	tmp_path = t_strconcat(tmpdir, "/", fname, NULL);
 
-	fname = maildir_filename_set_flags(fname, flags);
+	fname = maildir_filename_set_flags(fname, mail_flags);
 	new_path = t_strconcat(ibox->index->mailbox_path, "/new/", fname, NULL);
 
 	/* set the internal_date by modifying mtime */
--- a/src/lib-storage/index/maildir/maildir-storage.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Mon Jan 20 16:52:51 2003 +0200
@@ -560,8 +560,15 @@
 	index_storage_expunge,
 	index_storage_update_flags,
 	maildir_storage_copy,
-	index_storage_fetch,
-	index_storage_search,
+	index_storage_fetch_init,
+	index_storage_fetch_deinit,
+	index_storage_fetch_next,
+	index_storage_fetch_uid,
+	index_storage_fetch_seq,
+        index_storage_search_get_sorting,
+	index_storage_search_init,
+	index_storage_search_deinit,
+	index_storage_search_next,
 	maildir_storage_save,
 	mail_storage_is_inconsistency_error,
 
--- a/src/lib-storage/index/maildir/maildir-storage.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/maildir/maildir-storage.h	Mon Jan 20 16:52:51 2003 +0200
@@ -5,8 +5,8 @@
 
 int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox,
 			 const char *messageset, int uidset);
-int maildir_storage_save(struct mailbox *box, enum mail_flags flags,
-			 const char *custom_flags[],
+int maildir_storage_save(struct mailbox *box,
+			 const struct mail_full_flags *flags,
 			 time_t internal_date, int timezone_offset,
 			 struct istream *data, uoff_t data_size);
 
--- a/src/lib-storage/index/mbox/mbox-save.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/mbox/mbox-save.c	Mon Jan 20 16:52:51 2003 +0200
@@ -107,11 +107,12 @@
 
 static int write_flags(struct mail_storage *storage, struct ostream *output,
 		       const char *mbox_path,
-		       enum mail_flags flags, const char *custom_flags[])
+		       const struct mail_full_flags *full_flags)
 {
+	enum mail_flags flags = full_flags->flags;
 	const char *str;
 	unsigned int field;
-	int i;
+	unsigned int i;
 
 	if (flags == 0)
 		return TRUE;
@@ -138,15 +139,18 @@
 			return write_error(storage, mbox_path);
 
 		field = 1 << MAIL_CUSTOM_FLAG_1_BIT;
-		for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++, field <<= 1) {
-			if ((flags & field) && custom_flags[i] != NULL) {
+		for (i = 0; i < full_flags->custom_flags_count; i++) {
+			const char *custom_flag = full_flags->custom_flags[i];
+
+			if ((flags & field) && custom_flag != NULL) {
 				if (o_stream_send(output, " ", 1) < 0)
 					return write_error(storage, mbox_path);
 
-				if (o_stream_send_str(output,
-						      custom_flags[i]) < 0)
+				if (o_stream_send_str(output, custom_flag) < 0)
 					return write_error(storage, mbox_path);
 			}
+
+                        field <<= 1;
 		}
 
 		if (o_stream_send(output, "\n", 1) < 0)
@@ -156,8 +160,8 @@
 	return TRUE;
 }
 
-int mbox_storage_save(struct mailbox *box, enum mail_flags flags,
-		      const char *custom_flags[], time_t internal_date,
+int mbox_storage_save(struct mailbox *box, const struct mail_full_flags *flags,
+		      time_t internal_date,
 		      int timezone_offset __attr_unused__,
 		      struct istream *data, uoff_t data_size)
 {
@@ -176,8 +180,10 @@
 
 	/* we don't need the real flag positions, easier to keep using our own.
 	   they need to be checked/added though. */
-	real_flags = flags;
-	if (!index_mailbox_fix_custom_flags(ibox, &real_flags, custom_flags))
+	real_flags = flags->flags;
+	if (!index_mailbox_fix_custom_flags(ibox, &real_flags,
+					    flags->custom_flags,
+					    flags->custom_flags_count))
 		return FALSE;
 
 	if (!index_storage_sync_and_lock(ibox, FALSE, MAIL_LOCK_EXCLUSIVE))
@@ -198,8 +204,7 @@
 
 		if (!write_from_line(box->storage, output, mbox_path,
 				     internal_date) ||
-		    !write_flags(box->storage, output, mbox_path, flags,
-				 custom_flags) ||
+		    !write_flags(box->storage, output, mbox_path, flags) ||
 		    !index_storage_save(box->storage, mbox_path,
 					data, output, data_size) ||
 		    !mbox_append_lf(box->storage, output, mbox_path)) {
--- a/src/lib-storage/index/mbox/mbox-storage.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Mon Jan 20 16:52:51 2003 +0200
@@ -620,8 +620,15 @@
 	index_storage_expunge,
 	index_storage_update_flags,
 	index_storage_copy,
-	index_storage_fetch,
-	index_storage_search,
+	index_storage_fetch_init,
+	index_storage_fetch_deinit,
+	index_storage_fetch_next,
+	index_storage_fetch_uid,
+	index_storage_fetch_seq,
+        index_storage_search_get_sorting,
+	index_storage_search_init,
+	index_storage_search_deinit,
+	index_storage_search_next,
 	mbox_storage_save,
 	mail_storage_is_inconsistency_error,
 
--- a/src/lib-storage/index/mbox/mbox-storage.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/index/mbox/mbox-storage.h	Mon Jan 20 16:52:51 2003 +0200
@@ -5,9 +5,8 @@
 
 int mbox_storage_copy(struct mailbox *box, struct mailbox *destbox,
 		      const char *messageset, int uidset);
-int mbox_storage_save(struct mailbox *box, enum mail_flags flags,
-		      const char *custom_flags[], time_t internal_date,
-		      int timezone_offset,
+int mbox_storage_save(struct mailbox *box, const struct mail_full_flags *flags,
+		      time_t internal_date, int timezone_offset,
 		      struct istream *data, uoff_t data_size);
 
 int mbox_find_mailboxes(struct mail_storage *storage, const char *mask,
--- a/src/lib-storage/mail-search.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/mail-search.c	Mon Jan 20 16:52:51 2003 +0200
@@ -3,377 +3,6 @@
 #include "lib.h"
 #include "mail-search.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;
-}
-
-#define ARG_NEW(type, value) \
-	arg_new(data, args, next_sarg, type, value)
-
-static int arg_new(struct search_build_data *data, struct imap_arg **args,
-		   struct mail_search_arg **next_sarg,
-		   enum mail_search_arg_type type, int value)
-{
-	struct mail_search_arg *sarg;
-
-	*next_sarg = sarg = search_arg_new(data->pool, type);
-	if (value == 0)
-		return TRUE;
-
-	/* first arg */
-	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;
-	}
-
-	sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
-	*args += 1;
-
-	/* second arg */
-	if (value == 2) {
-		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;
-		}
-
-                sarg->hdr_field_name = sarg->value.str;
-		sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
-		*args += 1;
-	}
-
-	return TRUE;
-}
-
-static int search_arg_build(struct search_build_data *data,
-			    struct imap_arg **args,
-			    struct mail_search_arg **next_sarg)
-{
-	struct mail_search_arg **subargs;
-	struct imap_arg *arg;
-	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) {
-		struct imap_arg *listargs = IMAP_ARG_LIST(arg)->args;
-
-		*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_ucase(str);
-
-	switch (*str) {
-	case 'A':
-		if (strcmp(str, "ANSWERED") == 0)
-			return ARG_NEW(SEARCH_ANSWERED, 0);
-		else if (strcmp(str, "ALL") == 0)
-			return ARG_NEW(SEARCH_ALL, 0);
-		break;
-	case 'B':
-		if (strcmp(str, "BODY") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_BODY, 1);
-		} else if (strcmp(str, "BEFORE") == 0) {
-			/* <date> */
-			return ARG_NEW(SEARCH_BEFORE, 1);
-		} else if (strcmp(str, "BCC") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_BCC, 1);
-		}
-		break;
-	case 'C':
-		if (strcmp(str, "CC") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_CC, 1);
-		}
-		break;
-	case 'D':
-		if (strcmp(str, "DELETED") == 0)
-			return ARG_NEW(SEARCH_DELETED, 0);
-		else if (strcmp(str, "DRAFT") == 0)
-			return ARG_NEW(SEARCH_DRAFT, 0);
-		break;
-	case 'F':
-		if (strcmp(str, "FLAGGED") == 0)
-			return ARG_NEW(SEARCH_FLAGGED, 0);
-		else if (strcmp(str, "FROM") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_FROM, 1);
-		}
-		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 = str_ucase(IMAP_ARG_STR(*args));
-
-			if (strcmp(key, "FROM") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_FROM, 1);
-			} else if (strcmp(key, "TO") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_TO, 1);
-			} else if (strcmp(key, "CC") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_CC, 1);
-			} else if (strcmp(key, "BCC") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_BCC, 1);
-			} else if (strcmp(key, "SUBJECT") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_SUBJECT, 1);
-			} else if (strcmp(key, "IN-REPLY-TO") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_IN_REPLY_TO, 1);
-			} else if (strcmp(key, "MESSAGE-ID") == 0) {
-				*args += 1;
-				return ARG_NEW(SEARCH_MESSAGE_ID, 1);
-			} else {
-				return ARG_NEW(SEARCH_HEADER, 2);
-			}
-		}
-		break;
-	case 'K':
-		if (strcmp(str, "KEYWORD") == 0) {
-			/* <flag> */
-			return ARG_NEW(SEARCH_KEYWORD, 1);
-		}
-		break;
-	case 'L':
-		if (strcmp(str, "LARGER") == 0) {
-			/* <n> */
-			return ARG_NEW(SEARCH_LARGER, 1);
-		}
-		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_RECENT);
-			(*subargs)->next = search_arg_new(data->pool,
-							  SEARCH_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(*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(SEARCH_ON, 1);
-		} if (strcmp(str, "OLD") == 0) {
-			/* OLD == NOT RECENT */
-			if (!ARG_NEW(SEARCH_RECENT, 0))
-				return FALSE;
-
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		}
-		break;
-	case 'R':
-		if (strcmp(str, "RECENT") == 0)
-			return ARG_NEW(SEARCH_RECENT, 0);
-		break;
-	case 'S':
-		if (strcmp(str, "SEEN") == 0)
-			return ARG_NEW(SEARCH_SEEN, 0);
-		else if (strcmp(str, "SUBJECT") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_SUBJECT, 1);
-		} else if (strcmp(str, "SENTBEFORE") == 0) {
-			/* <date> */
-			return ARG_NEW(SEARCH_SENTBEFORE, 1);
-		} else if (strcmp(str, "SENTON") == 0) {
-			/* <date> */
-			return ARG_NEW(SEARCH_SENTON, 1);
-		} else if (strcmp(str, "SENTSINCE") == 0) {
-			/* <date> */
-			return ARG_NEW(SEARCH_SENTSINCE, 1);
-		} else if (strcmp(str, "SINCE") == 0) {
-			/* <date> */
-			return ARG_NEW(SEARCH_SINCE, 1);
-		} else if (strcmp(str, "SMALLER") == 0) {
-			/* <n> */
-			return ARG_NEW(SEARCH_SMALLER, 1);
-		}
-		break;
-	case 'T':
-		if (strcmp(str, "TEXT") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_TEXT, 1);
-		} else if (strcmp(str, "TO") == 0) {
-			/* <string> */
-			return ARG_NEW(SEARCH_TO, 1);
-		}
-		break;
-	case 'U':
-		if (strcmp(str, "UID") == 0) {
-			/* <message set> */
-			return ARG_NEW(SEARCH_UID, 1);
-		} else if (strcmp(str, "UNANSWERED") == 0) {
-			if (!ARG_NEW(SEARCH_ANSWERED, 0))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNDELETED") == 0) {
-			if (!ARG_NEW(SEARCH_DELETED, 0))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNDRAFT") == 0) {
-			if (!ARG_NEW(SEARCH_DRAFT, 0))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNFLAGGED") == 0) {
-			if (!ARG_NEW(SEARCH_FLAGGED, 0))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNKEYWORD") == 0) {
-			if (!ARG_NEW(SEARCH_KEYWORD, 0))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		} else if (strcmp(str, "UNSEEN") == 0) {
-			if (!ARG_NEW(SEARCH_SEEN, 0))
-				return FALSE;
-			(*next_sarg)->not = TRUE;
-			return TRUE;
-		}
-		break;
-	default:
-		if (*str == '*' || (*str >= '0' && *str <= '9')) {
-			/* <message-set> */
-			if (!ARG_NEW(SEARCH_SET, 0))
-				return FALSE;
-
-			(*next_sarg)->value.str = str;
-			return TRUE;
-		}
-		break;
-	}
-
-	data->error = t_strconcat("Unknown argument ", str, NULL);
-	return FALSE;
-}
-
-struct mail_search_arg *
-mail_search_args_build(pool_t pool, struct imap_arg *args, const char **error)
-{
-        struct search_build_data data;
-	struct mail_search_arg *first_sarg, **sargs;
-
-	data.pool = pool;
-	data.error = NULL;
-
-	/* get the first arg */
-	first_sarg = NULL; sargs = &first_sarg;
-	while (args->type != IMAP_ARG_EOL) {
-		if (!search_arg_build(&data, &args, sargs)) {
-			*error = data.error;
-			return NULL;
-		}
-		sargs = &(*sargs)->next;
-	}
-
-	*error = NULL;
-	return first_sarg;
-}
-
 void mail_search_args_reset(struct mail_search_arg *args)
 {
 	while (args != NULL) {
--- a/src/lib-storage/mail-search.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/mail-search.h	Mon Jan 20 16:52:51 2003 +0200
@@ -1,9 +1,6 @@
 #ifndef __MAIL_SEARCH_H
 #define __MAIL_SEARCH_H
 
-#include "imap-parser.h"
-#include "mail-storage.h"
-
 enum mail_search_arg_type {
 	SEARCH_OR,
 	SEARCH_SUB,
@@ -70,10 +67,6 @@
 typedef void (*mail_search_foreach_callback_t)(struct mail_search_arg *arg,
 					       void *context);
 
-/* Builds search arguments based on IMAP arguments. */
-struct mail_search_arg *
-mail_search_args_build(pool_t pool, struct imap_arg *args, const char **error);
-
 /* Reset the results in search arguments */
 void mail_search_args_reset(struct mail_search_arg *args);
 
--- a/src/lib-storage/mail-sort.c	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,588 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-/* Implementation of draft-ietf-imapext-sort-10 sorting algorithm.
-   Pretty messy code actually, adding any sort types requires care.
-   This is pretty fast however and takes only as much memory as needed to be
-   reasonably fast. */
-
-#include "lib.h"
-#include "buffer.h"
-#include "hash.h"
-#include "ostream.h"
-#include "imap-base-subject.h"
-#include "mail-sort.h"
-
-#include <stdlib.h>
-
-#define IS_SORT_STRING(type) \
-	((type) == MAIL_SORT_CC || (type) == MAIL_SORT_FROM || \
-	 (type) == MAIL_SORT_SUBJECT || (type) == MAIL_SORT_TO)
-
-#define IS_SORT_TIME(type) \
-	((type) == MAIL_SORT_ARRIVAL || (type) == MAIL_SORT_DATE)
-
-struct mail_sort_context {
-	enum mail_sort_type output[MAX_SORT_PROGRAM_SIZE];
-	enum mail_sort_type common_mask, cache_mask;
-
-	struct ostream *outstream;
-	const struct mail_sort_callbacks *callbacks;
-	void *func_context;
-
-	buffer_t *sort_buffer;
-	size_t sort_element_size;
-
-	pool_t temp_pool, str_pool;
-	struct hash_table *string_table;
-
-	time_t last_arrival, last_date;
-	uoff_t last_size;
-	char *last_cc, *last_from, *last_subject, *last_to;
-};
-
-static void mail_sort_flush(struct mail_sort_context *ctx);
-
-static enum mail_sort_type
-mail_sort_normalize(const enum mail_sort_type *input, buffer_t *output)
-{
-        enum mail_sort_type type, mask = 0;
-	int pos, reverse;
-
-	reverse = FALSE;
-	for (pos = 0; *input != MAIL_SORT_END; input++) {
-		if (*input == MAIL_SORT_REVERSE)
-			reverse = !reverse;
-		else {
-			if ((mask & *input) == 0) {
-				if (reverse) {
-					type = MAIL_SORT_REVERSE;
-					buffer_append(output,
-						      &type, sizeof(type));
-				}
-
-				buffer_append(output, input, sizeof(*input));
-				mask |= *input;
-			}
-
-			reverse = FALSE;
-		}
-	}
-
-	type = MAIL_SORT_END;
-	buffer_append(output, &type, sizeof(type));
-
-	return mask;
-}
-
-static enum mail_sort_type
-mail_sort_get_common_mask(const enum mail_sort_type *input,
-			  enum mail_sort_type **output)
-{
-	enum mail_sort_type mask = 0;
-
-	while (*input == **output && *input != MAIL_SORT_END) {
-		if (*input != MAIL_SORT_REVERSE)
-			mask |= *input;
-		input++; (*output)++;
-	}
-
-	return mask;
-}
-
-struct mail_sort_context *
-mail_sort_init(const enum mail_sort_type *input, enum mail_sort_type *output,
-	       struct ostream *outstream,
-	       const struct mail_sort_callbacks *callbacks, void *context)
-{
-	/* @UNSAFE */
-	struct mail_sort_context *ctx;
-	enum mail_sort_type norm_input[MAX_SORT_PROGRAM_SIZE];
-	enum mail_sort_type norm_output[MAX_SORT_PROGRAM_SIZE];
-	buffer_t *buf;
-	int i;
-
-	ctx = i_new(struct mail_sort_context, 1);
-	ctx->temp_pool = pool_alloconly_create("sort temp", 8192);
-	ctx->outstream = outstream;
-
-	t_push();
-	buf = buffer_create_data(data_stack_pool,
-				 norm_input, sizeof(norm_input));
-	mail_sort_normalize(input, buf);
-
-	buf = buffer_create_data(data_stack_pool,
-				 norm_output, sizeof(norm_output));
-	mail_sort_normalize(output, buf);
-	t_pop();
-
-	/* remove the common part from output, we already know input is sorted
-	   that much so we don't have to worry about it. */
-	output = norm_output;
-        ctx->common_mask = mail_sort_get_common_mask(norm_input, &output);
-
-	for (i = 0; output[i] != MAIL_SORT_END; i++)
-		ctx->output[i] = output[i];
-	ctx->output[i] = MAIL_SORT_END;
-
-	/* figure out what data we'd like to cache */
-	ctx->sort_element_size = sizeof(unsigned int);
-	ctx->cache_mask = 0;
-
-	for (i = 0; output[i] != MAIL_SORT_END; i++) {
-		if (IS_SORT_STRING(output[i])) {
-			ctx->sort_element_size += sizeof(const char *);
-
-			/* cache the second rule as well, if available */
-			if (ctx->cache_mask != 0) {
-				ctx->cache_mask |= output[i];
-				break;
-			}
-			ctx->cache_mask |= output[i];
-		} else if (IS_SORT_TIME(output[i])) {
-			ctx->sort_element_size += sizeof(time_t);
-			ctx->cache_mask |= output[i];
-			break;
-		} else if (output[i] == MAIL_SORT_SIZE) {
-			ctx->sort_element_size += sizeof(uoff_t);
-			ctx->cache_mask |= output[i];
-			break;
-		}
-	}
-
-	if ((ctx->cache_mask & MAIL_SORT_CC) ||
-	    (ctx->cache_mask & MAIL_SORT_FROM) ||
-	    (ctx->cache_mask & MAIL_SORT_TO) ||
-	    (ctx->cache_mask & MAIL_SORT_SUBJECT)) {
-		ctx->str_pool = pool_alloconly_create("sort str", 8192);
-		ctx->string_table = hash_create(default_pool, ctx->str_pool,
-						0, str_hash,
-						(hash_cmp_callback_t)strcmp);
-	}
-
-	ctx->sort_buffer = buffer_create_dynamic(system_pool,
-						 128 * ctx->sort_element_size,
-						 (size_t)-1);
-
-	ctx->callbacks = callbacks;
-	ctx->func_context = context;
-	return ctx;
-}
-
-void mail_sort_deinit(struct mail_sort_context *ctx)
-{
-	mail_sort_flush(ctx);
-
-	if (ctx->string_table != NULL)
-		hash_destroy(ctx->string_table);
-	if (ctx->str_pool != NULL)
-		pool_unref(ctx->str_pool);
-	buffer_free(ctx->sort_buffer);
-	pool_unref(ctx->temp_pool);
-
-	i_free(ctx->last_cc);
-	i_free(ctx->last_from);
-	i_free(ctx->last_subject);
-	i_free(ctx->last_to);
-
-	i_free(ctx);
-}
-
-static const char *string_table_get(struct mail_sort_context *ctx,
-				    const char *str)
-{
-	char *value;
-
-	if (str == NULL)
-		return NULL;
-	if (*str == '\0')
-		return "";
-
-	value = hash_lookup(ctx->string_table, str);
-	if (value == NULL) {
-		value = p_strdup(ctx->str_pool, str);
-		hash_insert(ctx->string_table, value, value);
-	}
-
-	return value;
-}
-
-static void mail_sort_check_flush(struct mail_sort_context *ctx,
-				  unsigned int id)
-{
-	const char *str;
-	time_t t;
-	uoff_t size;
-	int changed = FALSE;
-
-	if (ctx->common_mask & MAIL_SORT_ARRIVAL) {
-		t = ctx->callbacks->input_time(MAIL_SORT_ARRIVAL, id,
-					       ctx->func_context);
-		if (t != ctx->last_arrival) {
-			ctx->last_arrival = t;
-			changed = TRUE;
-		}
-	}
-
-	if (ctx->common_mask & MAIL_SORT_CC) {
-		str = ctx->callbacks->input_mailbox(MAIL_SORT_CC, id,
-						    ctx->func_context);
-		str = str_ucase(t_strdup_noconst(str));
-		if (strcmp(str, ctx->last_cc) != 0) {
-			i_free(ctx->last_cc);
-			ctx->last_cc = i_strdup(str);
-			changed = TRUE;
-		}
-	}
-
-	if (ctx->common_mask & MAIL_SORT_DATE) {
-		t = ctx->callbacks->input_time(MAIL_SORT_DATE, id,
-					       ctx->func_context);
-		if (t != ctx->last_date) {
-			ctx->last_date = t;
-			changed = TRUE;
-		}
-	}
-
-	if (ctx->common_mask & MAIL_SORT_FROM) {
-		str = ctx->callbacks->input_mailbox(MAIL_SORT_FROM, id,
-						    ctx->func_context);
-		str = str_ucase(t_strdup_noconst(str));
-		if (strcmp(str, ctx->last_from) != 0) {
-			i_free(ctx->last_from);
-			ctx->last_from = i_strdup(str);
-			changed = TRUE;
-		}
-	}
-
-	if (ctx->common_mask & MAIL_SORT_SIZE) {
-		size = ctx->callbacks->input_uofft(MAIL_SORT_SIZE, id,
-						   ctx->func_context);
-		if (size != ctx->last_size) {
-			ctx->last_size = size;
-			changed = TRUE;
-		}
-	}
-
-	if (ctx->common_mask & MAIL_SORT_SUBJECT) {
-		str = ctx->callbacks->input_str(MAIL_SORT_SUBJECT, id,
-						ctx->func_context);
-		p_clear(ctx->temp_pool);
-		str = imap_get_base_subject_cased(ctx->temp_pool, str, NULL);
-
-		if (strcmp(str, ctx->last_subject) != 0) {
-			i_free(ctx->last_subject);
-			ctx->last_subject = i_strdup(str);
-			changed = TRUE;
-		}
-	}
-
-	if (ctx->common_mask & MAIL_SORT_TO) {
-		str = ctx->callbacks->input_mailbox(MAIL_SORT_TO, id,
-						    ctx->func_context);
-		str = str_ucase(t_strdup_noconst(str));
-		if (strcmp(str, ctx->last_to) != 0) {
-			i_free(ctx->last_to);
-			ctx->last_to = i_strdup(str);
-			changed = TRUE;
-		}
-	}
-
-	if (changed)
-		mail_sort_flush(ctx);
-}
-
-void mail_sort_input(struct mail_sort_context *ctx, unsigned int id)
-{
-	/* @UNSAFE */
-	unsigned char *buf;
-	time_t t;
-	uoff_t size;
-	const char *str;
-	size_t pos;
-
-	t_push();
-	if (ctx->common_mask != 0)
-		mail_sort_check_flush(ctx, id);
-
-	buf = buffer_append_space(ctx->sort_buffer, ctx->sort_element_size);
-	memcpy(buf, &id, sizeof(id)); pos = sizeof(id);
-
-	if (ctx->cache_mask & MAIL_SORT_ARRIVAL) {
-		if (ctx->common_mask & MAIL_SORT_ARRIVAL)
-			t = ctx->last_arrival;
-		else {
-			t = ctx->callbacks->input_time(MAIL_SORT_ARRIVAL, id,
-						       ctx->func_context);
-		}
-		memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t);
-	}
-
-	if (ctx->cache_mask & MAIL_SORT_DATE) {
-		if (ctx->common_mask & MAIL_SORT_DATE)
-			t = ctx->last_date;
-		else {
-			t = ctx->callbacks->input_time(MAIL_SORT_DATE, id,
-						       ctx->func_context);
-		}
-		memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t);
-	}
-
-	if (ctx->cache_mask & MAIL_SORT_SIZE) {
-		if (ctx->common_mask & MAIL_SORT_SIZE)
-			size = ctx->last_size;
-		else {
-			size = ctx->callbacks->input_uofft(MAIL_SORT_SIZE, id,
-							   ctx->func_context);
-		}
-
-		memcpy(buf + pos, &size, sizeof(size)); pos += sizeof(size);
-	}
-
-	if (ctx->cache_mask & MAIL_SORT_CC) {
-		if (ctx->common_mask & MAIL_SORT_CC)
-			str = ctx->last_cc;
-		else {
-			str = ctx->callbacks->input_mailbox(MAIL_SORT_CC, id,
-							    ctx->func_context);
-			if (str != NULL)
-				str = str_ucase(t_strdup_noconst(str));
-		}
-		str = string_table_get(ctx, str);
-
-		memcpy(buf + pos, &str, sizeof(const char *));
-		pos += sizeof(const char *);
-	}
-
-	if (ctx->cache_mask & MAIL_SORT_FROM) {
-		if (ctx->common_mask & MAIL_SORT_FROM)
-			str = ctx->last_from;
-		else {
-			str = ctx->callbacks->input_mailbox(MAIL_SORT_FROM, id,
-							    ctx->func_context);
-			if (str != NULL)
-				str = str_ucase(t_strdup_noconst(str));
-		}
-		str = string_table_get(ctx, str);
-
-		memcpy(buf + pos, &str, sizeof(const char *));
-		pos += sizeof(const char *);
-	}
-
-	if (ctx->cache_mask & MAIL_SORT_TO) {
-		if (ctx->common_mask & MAIL_SORT_TO)
-			str = ctx->last_to;
-		else {
-			str = ctx->callbacks->input_mailbox(MAIL_SORT_TO, id,
-							    ctx->func_context);
-			if (str != NULL)
-				str = str_ucase(t_strdup_noconst(str));
-		}
-		str = string_table_get(ctx, str);
-
-		memcpy(buf + pos, &str, sizeof(const char *));
-		pos += sizeof(const char *);
-	}
-
-	if (ctx->cache_mask & MAIL_SORT_SUBJECT) {
-		if (ctx->common_mask & MAIL_SORT_SUBJECT)
-			str = ctx->last_subject;
-		else {
-			str = ctx->callbacks->input_str(MAIL_SORT_SUBJECT, id,
-							ctx->func_context);
-			p_clear(ctx->temp_pool);
-			str = imap_get_base_subject_cased(ctx->temp_pool,
-							  str, NULL);
-		}
-		str = string_table_get(ctx, str);
-
-		memcpy(buf + pos, &str, sizeof(const char *));
-		pos += sizeof(const char *);
-	}
-
-	i_assert(pos == ctx->sort_element_size);
-
-	ctx->callbacks->input_reset(ctx->func_context);
-	t_pop();
-}
-
-static struct mail_sort_context *qsort_context;
-
-static time_t get_time(enum mail_sort_type type, const unsigned char *buf,
-		       struct mail_sort_context *ctx)
-{
-	time_t t;
-
-	if ((ctx->cache_mask & type) == 0) {
-		return ctx->callbacks->
-			input_time(type, *((unsigned int *) buf),
-				   ctx->func_context);
-	}
-
-	/* use memcpy() to avoid any alignment problems */
-	memcpy(&t, buf + sizeof(unsigned int), sizeof(t));
-	return t;
-}
-
-static time_t get_uofft(enum mail_sort_type type, const unsigned char *buf,
-			struct mail_sort_context *ctx)
-{
-	uoff_t size;
-
-	if ((ctx->cache_mask & type) == 0) {
-		return ctx->callbacks->
-			input_uofft(type, *((unsigned int *) buf),
-				    ctx->func_context);
-	}
-
-	/* use memcpy() to avoid any alignment problems */
-	memcpy(&size, buf + sizeof(unsigned int), sizeof(size));
-	return size;
-}
-static const char *get_str(enum mail_sort_type type, const unsigned char *buf,
-			   struct mail_sort_context *ctx)
-{
-	const char *str;
-	enum mail_sort_type type2;
-	int pos;
-
-	if ((ctx->cache_mask & type) == 0) {
-		unsigned int id = *((unsigned int *) buf);
-
-		if (type == MAIL_SORT_SUBJECT) {
-			str = ctx->callbacks->input_str(MAIL_SORT_SUBJECT, id,
-							ctx->func_context);
-			p_clear(ctx->temp_pool);
-			str = imap_get_base_subject_cased(ctx->temp_pool,
-							  str, NULL);
-		} else {
-			str = ctx->callbacks->input_mailbox(type, id,
-							    ctx->func_context);
-			if (str != NULL)
-				str = str_ucase(t_strdup_noconst(str));
-
-		}
-		return str;
-	}
-
-	/* figure out where it is. pretty ugly. */
-	type2 = (ctx->cache_mask & ~type);
-
-	if (type2 == 0)
-		pos = 0;
-	else if (IS_SORT_TIME(type2))
-		pos = sizeof(time_t);
-	else if (type2 == MAIL_SORT_SIZE)
-		pos = sizeof(uoff_t);
-	else {
-		if (type == MAIL_SORT_SUBJECT)
-			pos = sizeof(const char *);
-		else if (type2 != MAIL_SORT_SUBJECT && type > type2)
-			pos = sizeof(const char *);
-		else
-			pos = 0;
-	}
-
-	/* use memcpy() to avoid any alignment problems */
-	memcpy(&str, buf + pos + sizeof(unsigned int), sizeof(const char *));
-	return str;
-}
-
-static int mail_sort_qsort_func(const void *p1, const void *p2)
-{
-	enum mail_sort_type *output;
-	int ret, reverse = FALSE;
-
-	output = qsort_context->output;
-
-	t_push();
-
-	ret = 0;
-	for (; *output != MAIL_SORT_END && ret == 0; output++) {
-		if (*output == MAIL_SORT_REVERSE) {
-			reverse = !reverse;
-			continue;
-		}
-
-		switch (*output) {
-		case MAIL_SORT_ARRIVAL:
-		case MAIL_SORT_DATE: {
-			time_t r1, r2;
-
-			r1 = get_time(*output, p1, qsort_context);
-			r2 = get_time(*output, p2, qsort_context);
-			ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0;
-			break;
-		}
-		case MAIL_SORT_SIZE: {
-			uoff_t r1, r2;
-
-			r1 = get_uofft(*output, p1, qsort_context);
-			r2 = get_uofft(*output, p2, qsort_context);
-			ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0;
-			break;
-		}
-		case MAIL_SORT_CC:
-		case MAIL_SORT_FROM:
-		case MAIL_SORT_TO:
-		case MAIL_SORT_SUBJECT: {
-			const char *s1, *s2;
-
-			s1 = get_str(*output, p1, qsort_context);
-			s2 = get_str(*output, p2, qsort_context);
-			if (s1 == NULL)
-				ret = s2 == NULL ? 0 : -1;
-			else if (s2 == NULL)
-				ret = 1;
-			else
-				ret = strcmp(s1, s2);
-			break;
-		}
-		default:
-			i_unreached();
-		}
-
-		if (reverse) {
-			if (ret > 0)
-				ret = -1;
-			else if (ret < 0)
-				ret = 1;
-		}
-
-		reverse = FALSE;
-	}
-
-	qsort_context->callbacks->input_reset(qsort_context->func_context);
-
-	t_pop();
-
-	return ret != 0 ? ret :
-		(*((unsigned int *) p1) < *((unsigned int *) p2) ? -1 : 1);
-}
-
-static void mail_sort_flush(struct mail_sort_context *ctx)
-{
-	unsigned char *arr;
-	size_t i, count;
-
-	qsort_context = ctx;
-
-	arr = buffer_get_modifyable_data(ctx->sort_buffer, NULL);
-	count = buffer_get_used_size(ctx->sort_buffer) / ctx->sort_element_size;
-	qsort(arr, count, ctx->sort_element_size, mail_sort_qsort_func);
-
-	for (i = 0; i < count; i++, arr += ctx->sort_element_size) {
-		unsigned int id = *((unsigned int *) arr);
-
-		t_push();
-		o_stream_send(ctx->outstream, " ", 1);
-		o_stream_send_str(ctx->outstream, dec2str(id));
-		t_pop();
-	}
-
-	buffer_set_used_size(ctx->sort_buffer, 0);
-
-	if (ctx->string_table != NULL) {
-		hash_clear(ctx->string_table, TRUE);
-		p_clear(ctx->str_pool);
-	}
-}
--- a/src/lib-storage/mail-sort.h	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-#ifndef __MAIL_SORT_H
-#define __MAIL_SORT_H
-
-#include "mail-storage.h"
-
-/* Maximum size for sort program, 2x for reverse + END */
-#define MAX_SORT_PROGRAM_SIZE (2*7 + 1)
-
-struct mail_sort_callbacks {
-	/* arrival, date */
-	time_t (*input_time)(enum mail_sort_type type, unsigned int id,
-			     void *context);
-	/* size */
-	uoff_t (*input_uofft)(enum mail_sort_type type, unsigned int id,
-			      void *context);
-	/* cc, from, to. Return the mailbox of the first address. */
-	const char *(*input_mailbox)(enum mail_sort_type type, unsigned int id,
-				     void *context);
-	/* subject */
-	const char *(*input_str)(enum mail_sort_type type, unsigned int id,
-				 void *context);
-
-	/* done parsing this message, free all resources */
-	void (*input_reset)(void *context);
-};
-
-/* input and output are arrays of sort programs ending with MAIL_SORT_END.
-   input specifies the order in which the messages are arriving to sorting.
-   It may be just MAIL_SORT_END if the order is random. The better the ordering
-   is known, the less memory is used. */
-struct mail_sort_context *
-mail_sort_init(const enum mail_sort_type *input, enum mail_sort_type *output,
-	       struct ostream *outstream,
-	       const struct mail_sort_callbacks *callbacks, void *context);
-void mail_sort_deinit(struct mail_sort_context *ctx);
-
-/* id is either UID or sequence number of message, whichever is preferred
-   in mail_sort_callbacks parameters. */
-void mail_sort_input(struct mail_sort_context *ctx, unsigned int id);
-
-#endif
--- a/src/lib-storage/mail-storage.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib-storage/mail-storage.h	Mon Jan 20 16:52:51 2003 +0200
@@ -1,6 +1,8 @@
 #ifndef __MAIL_STORAGE_H
 #define __MAIL_STORAGE_H
 
+struct message_size;
+
 #include "imap-util.h"
 
 enum mailbox_flags {
@@ -38,6 +40,9 @@
 };
 
 enum mail_sort_type {
+/* Maximum size for sort program, 2x for reverse + END */
+#define MAX_SORT_PROGRAM_SIZE (2*7 + 1)
+
 	MAIL_SORT_ARRIVAL	= 0x0010,
 	MAIL_SORT_CC		= 0x0020,
 	MAIL_SORT_DATE		= 0x0040,
@@ -57,11 +62,36 @@
 	MAIL_THREAD_REFERENCES
 };
 
+enum mail_fetch_field {
+	MAIL_FETCH_FLAGS		= 0x0001,
+	MAIL_FETCH_MESSAGE_PARTS	= 0x0002,
+
+	MAIL_FETCH_RECEIVED_DATE	= 0x0004,
+	MAIL_FETCH_DATE			= 0x0008,
+	MAIL_FETCH_SIZE			= 0x0010,
+
+	MAIL_FETCH_STREAM_HEADER	= 0x0020,
+	MAIL_FETCH_STREAM_BODY		= 0x0040,
+
+	/* specials: */
+	MAIL_FETCH_IMAP_BODY		= 0x1000,
+	MAIL_FETCH_IMAP_BODYSTRUCTURE	= 0x2000,
+	MAIL_FETCH_IMAP_ENVELOPE	= 0x4000
+};
+
+struct mail_full_flags {
+	enum mail_flags flags;
+
+	const char **custom_flags;
+	unsigned int custom_flags_count;
+};
+
 struct mail_storage;
 struct mail_storage_callbacks;
 struct mailbox_status;
-struct mail_fetch_data;
 struct mail_search_arg;
+struct fetch_context;
+struct search_context;
 
 typedef void (*mailbox_list_callback_t)(struct mail_storage *storage,
 					const char *name,
@@ -179,7 +209,7 @@
 	/* Update mail flags, calling update_flags callbacks. */
 	int (*update_flags)(struct mailbox *box,
 			    const char *messageset, int uidset,
-			    enum mail_flags flags, const char *custom_flags[],
+			    const struct mail_full_flags *flags,
 			    enum modify_type modify_type, int notify,
 			    int *all_found);
 
@@ -187,27 +217,62 @@
 	int (*copy)(struct mailbox *box, struct mailbox *destbox,
 		    const char *messageset, int uidset);
 
-	/* Fetch wanted mail data. The results are written into output stream
-	   in RFC2060 FETCH format. */
-	int (*fetch)(struct mailbox *box, struct mail_fetch_data *fetch_data,
-		     struct ostream *output, int *all_found);
+	/* Initialize new fetch request. wanted_fields isn't required, but it
+	   can be used for optimizations. If *update_seen is TRUE, \Seen flag
+	   is set for all fetched mails. *update_seen may be changed back to
+	   FALSE if all mails are already seen, or if it's not possible to
+	   change the flag (eg. read-only mailbox). */
+	struct mail_fetch_context *
+		(*fetch_init)(struct mailbox *box,
+			      enum mail_fetch_field wanted_fields,
+			      int *update_seen,
+			      const char *messageset, int uidset);
+	/* Deinitialize fetch request. all_found is set to TRUE if all of the
+	   fetched messages were found (ie. not just deleted). */
+	int (*fetch_deinit)(struct mail_fetch_context *ctx, int *all_found);
+	/* Fetch the next message. Returned mail object can be used until
+	   the next call to fetch_next() or fetch_deinit(). */
+	struct mail *(*fetch_next)(struct mail_fetch_context *ctx);
+
+	/* Simplified fetching for a single UID or sequence. Must be called
+	   between fetch_init() .. fetch_deinit() or
+	   search_init() .. search_deinit() */
+	struct mail *(*fetch_uid)(struct mailbox *box, unsigned int uid,
+				  enum mail_fetch_field wanted_fields);
+	struct mail *(*fetch_seq)(struct mailbox *box, unsigned int seq,
+				  enum mail_fetch_field wanted_fields);
 
-	/* Search wanted mail data. args contains the search criteria.
-	   Results are written into output stream in RFC2060 SEARCH format.
-	   If charset is NULL, the given search strings are matched without
-	   any conversion. */
-	int (*search)(struct mailbox *box, const char *charset,
-		      struct mail_search_arg *args,
-		      enum mail_sort_type *sorting,
-		      enum mail_thread_type threading,
-		      struct ostream *output, int uid_result);
+	/* Modify sort_program to specify a sort program acceptable for
+	   search_init(). If server supports no sorting, it's simply set to
+	   {MAIL_SORT_END}. */
+	int (*search_get_sorting)(struct mailbox *box,
+				  enum mail_sort_type *sort_program);
+	/* Initialize new search request. Search arguments are given so that
+	   the storage can optimize the searching as it wants.
+
+	   If sort_program is non-NULL, it requests that the returned messages
+	   are sorted by the given criteria. sort_program must have gone
+	   through search_get_sorting().
+
+	   wanted_fields and wanted_headers aren't required, but they can be
+	   used for optimizations. */
+	struct mail_search_context *
+		(*search_init)(struct mailbox *box, const char *charset,
+			       struct mail_search_arg *args,
+			       const enum mail_sort_type *sort_program,
+			       enum mail_fetch_field wanted_fields,
+			       const char *const wanted_headers[]);
+	/* Deinitialize search request. */
+	int (*search_deinit)(struct mail_search_context *ctx);
+	/* Search the next message. Returned mail object can be used until
+	   the next call to search_next() or search_deinit(). */
+	struct mail *(*search_next)(struct mail_search_context *ctx);
 
 	/* Save a new mail into mailbox. timezone_offset specifies the
-	   timezone in minutes which internal_date was originally given
+	   timezone in minutes which received_date was originally given
 	   with. */
-	int (*save)(struct mailbox *box, enum mail_flags flags,
-		    const char *custom_flags[],
-		    time_t internal_date, int timezone_offset,
+	int (*save)(struct mailbox *box, const struct mail_full_flags *flags,
+		    time_t received_date, int timezone_offset,
 		    struct istream *data, uoff_t data_size);
 
 	/* Returns TRUE if mailbox is now in inconsistent state, meaning that
@@ -224,6 +289,48 @@
 	unsigned int inconsistent:1;
 };
 
+struct mail {
+	/* always set */
+	unsigned int seq;
+	unsigned int uid;
+
+	unsigned int seen_updated:1; /* if update_seen was TRUE */
+
+	const struct mail_full_flags *(*get_flags)(struct mail *mail);
+	const struct message_part *(*get_parts)(struct mail *mail);
+
+	/* Get the time message was received (IMAP INTERNALDATE).
+	   Returns (time_t)-1 if error occured. */
+	time_t (*get_received_date)(struct mail *mail);
+	/* Get the Date-header in mail. Timezone is in minutes.
+	   Returns (time_t)-1 if error occured, 0 if field wasn't found or
+	   couldn't be parsed. */
+	time_t (*get_date)(struct mail *mail, int *timezone);
+	/* Get the full virtual size of mail (IMAP RFC822.SIZE).
+	   Returns (uoff_t)-1 if error occured */
+	uoff_t (*get_size)(struct mail *mail);
+
+	/* Get value for single header field */
+	const char *(*get_header)(struct mail *mail, const char *field);
+
+	/* Returns the parsed address for given header field. */
+	const struct message_address *(*get_address)(struct mail *mail,
+						     const char *field);
+	/* Returns the first mailbox (RFC2822 local-part) field for given
+	   address header field. */
+	const char *(*get_first_mailbox)(struct mail *mail, const char *field);
+
+	/* Returns input stream pointing to beginning of message header.
+	   hdr_size and body_size are updated unless they're NULL. */
+	struct istream *(*get_stream)(struct mail *mail,
+				      struct message_size *hdr_size,
+				      struct message_size *body_size);
+
+	/* Get the any of the "special" fields. */
+	const char *(*get_special)(struct mail *mail,
+				   enum mail_fetch_field field);
+};
+
 struct mailbox_status {
 	unsigned int messages;
 	unsigned int recent;
@@ -272,34 +379,6 @@
 
 };
 
-struct mail_fetch_data {
-	const char *messageset;
-	unsigned int uidset:1;
-
-	unsigned int body:1;
-	unsigned int bodystructure:1;
-	unsigned int envelope:1;
-	unsigned int flags:1;
-	unsigned int internaldate:1;
-	unsigned int rfc822:1;
-	unsigned int rfc822_header:1;
-	unsigned int rfc822_size:1;
-	unsigned int rfc822_text:1;
-	unsigned int uid:1;
-
-	struct mail_fetch_body_data *body_sections;
-};
-
-struct mail_fetch_body_data {
-	struct mail_fetch_body_data *next;
-
-	const char *section; /* NOTE: always uppercased */
-	uoff_t skip, max_size; /* if you don't want max_size,
-	                          set it to (uoff_t)-1 */
-	unsigned int skip_set:1;
-	unsigned int peek:1;
-};
-
 /* register all mail storages */
 void mail_storage_register_all(void);
 
--- a/src/lib-storage/mail-thread.c	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,894 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-/*
- * Merge sort code in sort_nodes() is copyright 2001 Simon Tatham.
- * 
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- * 
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-/* Implementation of draft-ietf-imapext-thread-12 threading algorithm */
-
-#include "lib.h"
-#include "hash.h"
-#include "ostream.h"
-#include "str.h"
-#include "message-tokenize.h"
-#include "imap-base-subject.h"
-#include "mail-thread.h"
-
-#include <stdlib.h>
-
-/* how much memory to allocate initially. these are very rough
-   approximations. */
-#define APPROX_MSG_COUNT 128
-#define APPROX_MSGID_SIZE 45
-
-/* Try to buffer this much data before sending it to output stream. */
-#define OUTPUT_BUF_SIZE 2048
-
-#define NODE_IS_DUMMY(node) ((node)->id == 0)
-#define NODE_HAS_PARENT(ctx, node) \
-	((node)->parent != NULL && (node)->parent != &(ctx)->root_node)
-
-struct root_info {
-	char *base_subject;
-	unsigned int reply:1;
-	unsigned int sorted:1;
-};
-
-struct node {
-	struct node *parent, *first_child, *next;
-
-	unsigned int id;
-	time_t sent_date;
-
-	union {
-		char *msgid;
-		struct root_info *info;
-	} u;
-};
-
-struct mail_thread_context {
-	pool_t pool;
-	pool_t temp_pool;
-
-	struct hash_table *msgid_hash;
-	struct hash_table *subject_hash;
-
-	struct node root_node;
-	size_t root_count; /* not exact after prune_dummy_messages() */
-
-	const struct mail_sort_callbacks *callbacks;
-	void *callback_context;
-
-        struct ostream *output;
-};
-
-struct mail_thread_context *
-mail_thread_init(enum mail_thread_type type, struct ostream *output,
-		 const struct mail_sort_callbacks *callbacks,
-		 void *callback_context)
-{
-	struct mail_thread_context *ctx;
-	pool_t pool;
-
-	if (type != MAIL_THREAD_REFERENCES)
-		i_fatal("Only REFERENCES threading supported");
-
-	pool = pool_alloconly_create("mail_thread_context",
-				     sizeof(struct node) * APPROX_MSG_COUNT);
-
-	ctx = p_new(pool, struct mail_thread_context, 1);
-	ctx->pool = pool;
-	ctx->temp_pool = pool_alloconly_create("mail_thread_context temp",
-					       APPROX_MSG_COUNT *
-					       APPROX_MSGID_SIZE);
-	ctx->msgid_hash = hash_create(default_pool, ctx->temp_pool,
-				      APPROX_MSG_COUNT*2, str_hash,
-				      (hash_cmp_callback_t)strcmp);
-	ctx->callbacks = callbacks;
-	ctx->callback_context = callback_context;
-	ctx->output = output;
-	return ctx;
-}
-
-static void mail_thread_deinit(struct mail_thread_context *ctx)
-{
-	if (ctx->msgid_hash != NULL)
-		hash_destroy(ctx->msgid_hash);
-	if (ctx->subject_hash != NULL)
-		hash_destroy(ctx->subject_hash);
-
-	pool_unref(ctx->temp_pool);
-	pool_unref(ctx->pool);
-}
-
-static void add_root(struct mail_thread_context *ctx, struct node *node)
-{
-	node->parent = &ctx->root_node;
-	node->next = ctx->root_node.first_child;
-	ctx->root_node.first_child = node;
-
-	ctx->root_count++;
-}
-
-static struct node *create_node(struct mail_thread_context *ctx,
-				const char *msgid)
-{
-	struct node *node;
-
-	node = p_new(ctx->pool, struct node, 1);
-	node->u.msgid = p_strdup(ctx->temp_pool, msgid);
-
-	hash_insert(ctx->msgid_hash, node->u.msgid, node);
-	return node;
-}
-
-static struct node *create_id_node(struct mail_thread_context *ctx,
-				   unsigned int id, time_t sent_date)
-{
-	struct node *node;
-
-	node = p_new(ctx->pool, struct node, 1);
-	node->id = id;
-	node->sent_date = sent_date;
-
-	add_root(ctx, node);
-	return node;
-}
-
-static struct node *update_message(struct mail_thread_context *ctx,
-				   const char *msgid, time_t sent_date,
-				   unsigned int id)
-{
-	struct node *node;
-
-	if (msgid == NULL)
-		return create_id_node(ctx, id, sent_date);
-
-	node = hash_lookup(ctx->msgid_hash, msgid);
-	if (node == NULL) {
-		/* first time we see this message */
-		node = create_node(ctx, msgid);
-		node->id = id;
-		node->sent_date = sent_date;
-		return node;
-	}
-
-	if (node->id == 0) {
-		/* seen before in references */
-		node->id = id;
-		node->sent_date = sent_date;
-	} else {
-		/* duplicate */
-		node = create_id_node(ctx, id, sent_date);
-	}
-
-	return node;
-}
-
-static int get_untokenized_msgid(const char **msgid_p, string_t *msgid)
-{
-	static const enum message_token stop_tokens[] = { '>', TOKEN_LAST };
-	struct message_tokenizer *tok;
-	int valid_end;
-
-	tok = message_tokenize_init((const unsigned char *) *msgid_p,
-				    (size_t)-1, NULL, NULL);
-	message_tokenize_dot_token(tok, FALSE); /* just a minor speedup */
-
-	message_tokenize_get_string(tok, msgid, NULL, stop_tokens);
-	valid_end = message_tokenize_get(tok) == '>';
-
-	*msgid_p += message_tokenize_get_parse_position(tok);
-	message_tokenize_deinit(tok);
-
-	if (valid_end) {
-		if (strchr(str_c(msgid), '@') != NULL) {
-			/* <xx@xx> - valid message ID found */
-			return TRUE;
-		}
-	}
-
-	return FALSE;
-}
-
-static const char *get_msgid(const char **msgid_p)
-{
-	const char *msgid = *msgid_p;
-	const char *p;
-	string_t *str = NULL;
-	int found_at;
-
-	if (*msgid_p == NULL)
-		return NULL;
-
-	for (;;) {
-		/* skip until '<' */
-		while (*msgid != '<') {
-			if (*msgid == '\0') {
-				*msgid_p = msgid;
-				return NULL;
-			}
-			msgid++;
-		}
-		msgid++;
-
-		/* check it through quickly to see if it's already normalized */
-		p = msgid; found_at = FALSE;
-		for (;; p++) {
-			if ((unsigned char)*p >= 'A') /* matches most */
-				continue;
-
-			if (*p == '@')
-				found_at = TRUE;
-			if (*p == '>' || *p == '"' || *p == '(' || *p == ' ' ||
-			    *p == '\t' || *p == '\r' || *p == '\n')
-				break;
-
-			if (*p == '\0') {
-				*msgid_p = p;
-				return NULL;
-			}
-		}
-
-		if (*p == '>') {
-			*msgid_p = p+1;
-			if (found_at)
-				return t_strdup_until(msgid, p);
-		} else {
-			/* ok, do it the slow way */
-			*msgid_p = msgid;
-
-			if (str == NULL) {
-				/* allocate only once, so we don't leak
-				   with multiple invalid message IDs */
-				str = t_str_new(256);
-			}
-			if (get_untokenized_msgid(msgid_p, str))
-				return str_c(str);
-		}
-
-		/* invalid message id, see if there's another valid one */
-		msgid = *msgid_p;
-	}
-}
-
-static void unlink_child(struct mail_thread_context *ctx,
-			 struct node *child, int add_to_root)
-{
-	struct node **node;
-
-        node = &child->parent->first_child;
-	for (; *node != NULL; node = &(*node)->next) {
-		if (*node == child) {
-			*node = child->next;
-			break;
-		}
-	}
-
-	child->next = NULL;
-	if (!add_to_root)
-		child->parent = NULL;
-	else
-		add_root(ctx, child);
-}
-
-static int find_child(struct node *node, struct node *child)
-{
-	do {
-		if (node == child)
-			return TRUE;
-
-		if (node->first_child != NULL) {
-			if (find_child(node->first_child, child))
-				return TRUE;
-		}
-
-		node = node->next;
-	} while (node != NULL);
-
-	return FALSE;
-}
-
-static void link_node(struct mail_thread_context *ctx, const char *parent_msgid,
-		      struct node *child, int replace)
-{
-	struct node *parent, **node;
-
-	if (NODE_HAS_PARENT(ctx, child) && !replace) {
-		/* already got a parent, don't want to replace it */
-		return;
-	}
-
-	parent = hash_lookup(ctx->msgid_hash, parent_msgid);
-	if (parent == NULL)
-		parent = create_node(ctx, parent_msgid);
-
-	if (child->parent == parent) {
-		/* already have this parent, ignore */
-		return;
-	}
-
-	if (find_child(child, parent)) {
-		/* this would create a loop, not allowed */
-		return;
-	}
-
-	if (child->parent != NULL)
-		unlink_child(ctx, child, FALSE);
-
-	/* link them */
-	child->parent = parent;
-
-	node = &parent->first_child;
-	while (*node != NULL)
-		node = &(*node)->next;
-	*node = child;
-}
-
-static void link_message(struct mail_thread_context *ctx,
-			 const char *parent_msgid, const char *child_msgid,
-			 int replace)
-{
-	struct node *child;
-
-	child = hash_lookup(ctx->msgid_hash, child_msgid);
-	if (child == NULL)
-		child = create_node(ctx, child_msgid);
-
-	link_node(ctx, parent_msgid, child, replace);
-}
-
-static int link_references(struct mail_thread_context *ctx,
-			   struct node *node, const char *references)
-{
-	const char *parent_id, *child_id;
-
-	parent_id = get_msgid(&references);
-	if (parent_id == NULL)
-		return FALSE;
-
-	while ((child_id = get_msgid(&references)) != NULL) {
-		link_message(ctx, parent_id, child_id, FALSE);
-		parent_id = child_id;
-	}
-
-	/* link the last message to us */
-	link_node(ctx, parent_id, node, TRUE);
-	return TRUE;
-}
-
-void mail_thread_input(struct mail_thread_context *ctx, unsigned int id,
-		       const char *message_id, const char *in_reply_to,
-		       const char *references, time_t sent_date)
-{
-	const char *refid;
-	struct node *node;
-
-	i_assert(id > 0);
-
-	node = update_message(ctx, get_msgid(&message_id), sent_date, id);
-
-	/* link references */
-	if (!link_references(ctx, node, references)) {
-		refid = get_msgid(&in_reply_to);
-		if (refid != NULL)
-			link_node(ctx, refid, node, TRUE);
-		else {
-			/* no references, make sure it's not linked */
-			if (node != NULL && NODE_HAS_PARENT(ctx, node))
-				unlink_child(ctx, node, TRUE);
-		}
-	}
-}
-
-static struct node *find_last_child(struct node *node)
-{
-	node = node->first_child;
-	while (node->next != NULL)
-		node = node->next;
-
-	return node;
-}
-
-static struct node **promote_children(struct node **parent)
-{
-	struct node *new_parent, *old_parent, *child;
-
-	old_parent = *parent;
-	new_parent = old_parent->parent;
-
-	child = old_parent->first_child;
-	*parent = child;
-
-	for (;;) {
-		child->parent = new_parent;
-		if (child->next == NULL)
-			break;
-		child = child->next;
-	}
-
-	child->next = old_parent->next;
-	return &child->next;
-}
-
-static void prune_dummy_messages(struct mail_thread_context *ctx,
-				 struct node **node_p)
-{
-	struct node **a;
-
-	a = node_p;
-	while (*node_p != NULL) {
-		if ((*node_p)->first_child != NULL)
-			prune_dummy_messages(ctx, &(*node_p)->first_child);
-
-		if (NODE_IS_DUMMY(*node_p)) {
-			if ((*node_p)->first_child == NULL) {
-				/* no children -> delete */
-				*node_p = (*node_p)->next;
-				continue;
-			} else if (NODE_HAS_PARENT(ctx, *node_p) ||
-				   (*node_p)->first_child->next == NULL) {
-				/* promote children to our level,
-				   deleting the dummy node */
-				node_p = promote_children(node_p);
-				continue;
-			}
-		}
-
-                node_p = &(*node_p)->next;
-	}
-}
-
-static int node_cmp(struct node *a, struct node *b)
-{
-	time_t date_a, date_b;
-	unsigned int id_a, id_b;
-
-	date_a = a->id != 0 ? a->sent_date : a->first_child->sent_date;
-	date_b = b->id != 0 ? b->sent_date : b->first_child->sent_date;
-
-	if (date_a != date_b && date_a != 0 && date_b != 0)
-		return date_a < date_b ? -1 : 1;
-
-	id_a = a->id != 0 ? a->id : a->first_child->id;
-	id_b = b->id != 0 ? b->id : b->first_child->id;
-	return id_a < id_b ? -1 : 1;
-}
-
-static struct node *sort_nodes(struct node *list)
-{
-	struct node *p, *q, *e, *tail;
-	size_t insize, nmerges, psize, qsize, i;
-
-	i_assert(list != NULL);
-
-	if (list->next == NULL)
-		return list; /* just one node */
-
-	insize = 1;
-
-	for (;;) {
-		p = list;
-		list = NULL;
-		tail = NULL;
-
-		nmerges = 0;  /* count number of merges we do in this pass */
-		while (p != 0) {
-			nmerges++;  /* there exists a merge to be done */
-
-			/* step `insize' places along from p */
-			q = p;
-			psize = 0;
-			for (i = 0; i < insize; i++) {
-				psize++;
-				q = q->next;
-				if (q == NULL) break;
-			}
-
-			/* if q hasn't fallen off end, we have two lists to
-			   merge */
-			qsize = insize;
-
-			/* now we have two lists; merge them */
-			while (psize > 0 || (qsize > 0 && q != NULL)) {
-				/* decide whether next element of merge comes
-				   from p or q */
-				if (psize == 0) {
-					/* p is empty; e must come from q. */
-					e = q; q = q->next; qsize--;
-				} else if (qsize == 0 || !q) {
-					/* q is empty; e must come from p. */
-					e = p; p = p->next; psize--;
-				} else if (node_cmp(p, q) <= 0) {
-					/* First element of p is lower
-					   (or same); e must come from p. */
-					e = p; p = p->next; psize--;
-				} else {
-					/* First element of q is lower;
-					   e must come from q. */
-					e = q; q = q->next; qsize--;
-				}
-
-				/* add the next element to the merged list */
-				if (tail)
-					tail->next = e;
-				else
-					list = e;
-				tail = e;
-			}
-
-			/* now p has stepped `insize' places along,
-			   and q has too */
-			p = q;
-		}
-		tail->next = NULL;
-
-		/* If we have done only one merge, we're finished. */
-		if (nmerges <= 1) {
-                        /* allow for nmerges == 0, the empty list case */
-			return list;
-		}
-
-		/* Otherwise repeat, merging lists twice the size */
-		insize *= 2;
-	}
-}
-
-static void add_base_subject(struct mail_thread_context *ctx,
-			     const char *subject, struct node *node)
-{
-	struct node *hash_node;
-	char *hash_subject;
-	void *key, *value;
-	int is_reply_or_forward;
-
-	if (subject == NULL)
-		return;
-
-	subject = imap_get_base_subject_cased(data_stack_pool, subject,
-					      &is_reply_or_forward);
-	if (*subject == '\0')
-		return;
-
-	if (!hash_lookup_full(ctx->subject_hash, subject, &key, &value)) {
-		hash_subject = p_strdup(ctx->temp_pool, subject);
-		hash_insert(ctx->subject_hash, hash_subject, node);
-	} else {
-		hash_subject = key;
-		hash_node = value;
-
-		if (!NODE_IS_DUMMY(hash_node) &&
-		    (NODE_IS_DUMMY(node) ||
-		     (hash_node->u.info->reply && !is_reply_or_forward)))
-			hash_update(ctx->subject_hash, hash_subject, node);
-	}
-
-	node->u.info->base_subject = hash_subject;
-	node->u.info->reply = is_reply_or_forward;
-}
-
-static void gather_base_subjects(struct mail_thread_context *ctx)
-{
-	const struct mail_sort_callbacks *cb;
-	struct node *node;
-	unsigned int id;
-
-	cb = ctx->callbacks;
-	ctx->subject_hash =
-		hash_create(default_pool, ctx->temp_pool, ctx->root_count * 2,
-			    str_hash, (hash_cmp_callback_t)strcmp);
-
-	node = ctx->root_node.first_child;
-	for (; node != NULL; node = node->next) {
-		if (!NODE_IS_DUMMY(node))
-			id = node->id;
-		else {
-			/* sort children, use the first one's id */
-			node->first_child = sort_nodes(node->first_child);
-			id = node->first_child->id;
-
-			node->u.info->sorted = TRUE;
-		}
-
-		t_push();
-
-		add_base_subject(ctx, cb->input_str(MAIL_SORT_SUBJECT, id,
-						    ctx->callback_context),
-				 node);
-
-		cb->input_reset(ctx->callback_context);
-		t_pop();
-	}
-}
-
-static void reset_children_parent(struct node *parent)
-{
-	struct node *node;
-
-	for (node = parent->first_child; node != NULL; node = node->next)
-		node->parent = parent;
-}
-
-static void merge_subject_threads(struct mail_thread_context *ctx)
-{
-	struct node **node_p, *node, *hash_node;
-	char *base_subject;
-
-	for (node_p = &ctx->root_node.first_child; *node_p != NULL; ) {
-		node = *node_p;
-
-		if (node->u.info == NULL) {
-			/* deleted node */
-			*node_p = node->next;
-			continue;
-		}
-
-		/* (ii) If the thread subject is empty, skip this message. */
-		base_subject = node->u.info->base_subject;
-		if (base_subject == NULL) {
-			node_p = &node->next;
-			continue;
-		}
-
-		/* (iii) Lookup the message associated with this thread
-		   subject in the subject table. */
-		hash_node = hash_lookup(ctx->subject_hash, base_subject);
-		i_assert(hash_node != NULL);
-
-		/* (iv) If the message in the subject table is the current
-		   message, skip this message. */
-		if (hash_node == node) {
-			node_p = &node->next;
-			continue;
-		}
-
-		/* Otherwise, merge the current message with the one in the
-		   subject table using the following rules: */
-
-		if (NODE_IS_DUMMY(node) &&
-		    NODE_IS_DUMMY(hash_node)) {
-			/* If both messages are dummies, append the current
-			   message's children to the children of the message in
-			   the subject table (the children of both messages
-			   become siblings), and then delete the current
-			   message. */
-			find_last_child(hash_node)->next = node->first_child;
-
-			*node_p = node->next;
-			hash_node->u.info->sorted = FALSE;
-		} else if (NODE_IS_DUMMY(hash_node) ||
-			   (node->u.info->reply && !hash_node->u.info->reply)) {
-			/* If the message in the subject table is a dummy
-			   and the current message is not, make the current
-			   message a child of the message in the subject table
-			   (a sibling of its children).
-
-			   If the current message is a reply or forward and
-			   the message in the subject table is not, make the
-			   current message a child of the message in the
-			   subject table (a sibling of its children). */
-			*node_p = node->next;
-
-			node->parent = hash_node;
-			node->next = hash_node->first_child;
-			hash_node->first_child = node;
-
-			hash_node->u.info->sorted = FALSE;
-		} else {
-			/* Otherwise, create a new dummy message and make both
-			   the current message and the message in the subject
-			   table children of the dummy.  Then replace the
-			   message in the subject table with the dummy
-			   message. */
-
-			/* create new nodes for the children - reusing
-			   existing ones have problems since the other one
-			   might have been handled already and we'd introduce
-			   loops..
-
-			   current node will be destroyed, hash_node will be
-			   the dummy so we don't need to update hash */
-			struct node *node1, *node2;
-
-			node1 = p_new(ctx->pool, struct node, 1);
-			node2 = p_new(ctx->pool, struct node, 1);
-
-			memcpy(node1, node, sizeof(struct node));
-			memcpy(node2, hash_node, sizeof(struct node));
-
-			node1->parent = hash_node;
-			node2->parent = hash_node;
-			node1->next = node2;
-			node2->next = NULL;
-
-			reset_children_parent(node1);
-			reset_children_parent(node2);
-
-			hash_node->id = 0;
-			hash_node->first_child = node1;
-			hash_node->u.info->reply = FALSE;
-			hash_node->u.info->sorted = FALSE;
-
-			node->first_child = NULL;
-			node->u.info = NULL;
-			*node_p = node->next;
-		}
-	}
-}
-
-static void sort_root_nodes(struct mail_thread_context *ctx)
-{
-	struct node *node;
-
-	/* sort the children first, they're needed to sort dummy root nodes */
-        node = ctx->root_node.first_child;
-	for (; node != NULL; node = node->next) {
-		if (node->u.info == NULL)
-			continue;
-
-		if (NODE_IS_DUMMY(node) && !node->u.info->sorted &&
-		    node->first_child != NULL)
-			node->first_child = sort_nodes(node->first_child);
-	}
-
-	ctx->root_node.first_child = sort_nodes(ctx->root_node.first_child);
-}
-
-static int send_nodes(struct mail_thread_context *ctx,
-		      string_t *str, struct node *node)
-{
-	if (node->next == NULL && NODE_HAS_PARENT(ctx, node)) {
-		/* no siblings - special case to avoid extra paranthesis */
-		if (node->first_child == NULL)
-			str_printfa(str, "%u", node->id);
-		else {
-			str_printfa(str, "%u ", node->id);
-			send_nodes(ctx, str, sort_nodes(node->first_child));
-		}
-		return TRUE;
-	}
-
-	while (node != NULL) {
-		if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) {
-			/* string getting full, flush it */
-			if (!o_stream_send(ctx->output,
-					   str_data(str), str_len(str)))
-				return FALSE;
-			str_truncate(str, 0);
-		}
-
-		if (node->first_child == NULL)
-			str_printfa(str, "(%u)", node->id);
-		else {
-			str_printfa(str, "(%u ", node->id);
-			send_nodes(ctx, str, sort_nodes(node->first_child));
-			str_append_c(str, ')');
-		}
-
-		node = node->next;
-	}
-	return TRUE;
-}
-
-static void send_roots(struct mail_thread_context *ctx)
-{
-	struct node *node;
-	string_t *str;
-
-	str = t_str_new(OUTPUT_BUF_SIZE);
-	str_append_c(str, ' ');
-
-	/* sort root nodes again, they have been modified since the last time */
-	sort_root_nodes(ctx);
-
-        node = ctx->root_node.first_child;
-	for (; node != NULL; node = node->next) {
-		if (node->u.info == NULL)
-			continue;
-
-		if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) {
-			/* string getting full, flush it */
-			if (!o_stream_send(ctx->output,
-					   str_data(str), str_len(str)))
-				return;
-			str_truncate(str, 0);
-		}
-
-		str_append_c(str, '(');
-		if (!NODE_IS_DUMMY(node))
-			str_printfa(str, "%u", node->id);
-
-		if (node->first_child != NULL) {
-			if (!NODE_IS_DUMMY(node))
-				str_append_c(str, ' ');
-
-			if (!node->u.info->sorted) {
-				node->first_child =
-					sort_nodes(node->first_child);
-			}
-
-			if (!send_nodes(ctx, str, node->first_child))
-				return;
-		}
-
-		str_append_c(str, ')');
-	}
-
-	(void)o_stream_send(ctx->output, str_data(str), str_len(str));
-}
-
-static void save_root_cb(void *key __attr_unused__, void *value, void *context)
-{
-	struct mail_thread_context *ctx = context;
-	struct node *node = value;
-
-	if (node->parent == NULL)
-		add_root(ctx, node);
-}
-
-void mail_thread_finish(struct mail_thread_context *ctx)
-{
-	struct node *node;
-
-	/* (2) save root nodes and drop the msgids */
-	hash_foreach(ctx->msgid_hash, save_root_cb, ctx);
-
-	/* drop the memory allocated for message-IDs and msgid_hash,
-	   reuse their memory for base subjects */
-	hash_destroy(ctx->msgid_hash);
-	ctx->msgid_hash = NULL;
-
-	p_clear(ctx->temp_pool);
-
-	if (ctx->root_node.first_child == NULL) {
-		/* no messages */
-		mail_thread_deinit(ctx);
-		return;
-	}
-
-	/* (3) */
-	prune_dummy_messages(ctx, &ctx->root_node.first_child);
-
-	/* initialize the node->u.info for all root nodes */
-        node = ctx->root_node.first_child;
-	for (; node != NULL; node = node->next)
-		node->u.info = p_new(ctx->pool, struct root_info, 1);
-
-	/* (4) */
-	sort_root_nodes(ctx);
-
-	/* (5) Gather together messages under the root that have the same
-	   base subject text. */
-	gather_base_subjects(ctx);
-
-	/* (5.C) Merge threads with the same thread subject. */
-	merge_subject_threads(ctx);
-
-	/* (6) Sort and send replies */
-	t_push();
-	send_roots(ctx);
-	t_pop();
-
-        mail_thread_deinit(ctx);
-}
--- a/src/lib-storage/mail-thread.h	Mon Jan 20 15:56:55 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-#ifndef __MAIL_THREAD_H
-#define __MAIL_THREAD_H
-
-#include "mail-storage.h"
-#include "mail-sort.h"
-
-struct mail_thread_context;
-
-struct mail_thread_context *
-mail_thread_init(enum mail_thread_type type, struct ostream *output,
-		 const struct mail_sort_callbacks *callbacks,
-		 void *callback_context);
-
-/* id is either UID or sequence number of message, whichever is preferred
-   in mail_thread_callbacks parameters. */
-void mail_thread_input(struct mail_thread_context *ctx, unsigned int id,
-		       const char *message_id, const char *in_reply_to,
-		       const char *references, time_t sent_date);
-
-void mail_thread_finish(struct mail_thread_context *ctx);
-
-#endif
--- a/src/lib/strfuncs.c	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib/strfuncs.c	Mon Jan 20 16:52:51 2003 +0200
@@ -462,6 +462,16 @@
         return str;
 }
 
+int null_strcmp(const char *s1, const char *s2)
+{
+	if (s1 == NULL)
+		return s2 == NULL ? 0 : -1;
+	if (s2 == NULL)
+		return 1;
+
+	return strcmp(s1, s1);
+}
+
 int memcasecmp(const void *p1, const void *p2, size_t size)
 {
 	const unsigned char *s1 = p1;
--- a/src/lib/strfuncs.h	Mon Jan 20 15:56:55 2003 +0200
+++ b/src/lib/strfuncs.h	Mon Jan 20 16:52:51 2003 +0200
@@ -51,6 +51,7 @@
 char *str_ucase(char *str);
 char *str_lcase(char *str);
 
+int null_strcmp(const char *s1, const char *s2);
 int memcasecmp(const void *p1, const void *p2, size_t size);
 
 /* seprators is an array of separator characters, not a separator string. */